問題描述
控制系統使用的是STM32F4+UCOSII 搶佔型內核,最近一段時間出現了程序跑一段時間之後操作系統直接死掉的問題,表現爲:操作系統中設有優先級很低的呼吸燈任務,只要操作系統在正常工作,呼吸燈就會不停的跳動,但是當出現問題時,呼吸燈停止跳動,控制底盤運動的任務也死掉,底盤處於失控狀態,LCD所在的任務也死掉,不再進行刷新,推測爲所有的操作系統的任務均死掉,不能正常工作,但是中斷仍然可以響應,寫在定時器中斷中的急停操作是可以執行的。
問題分析
-
懷疑是進入了一些錯誤中斷
-
懷疑是指針或者堆棧出了問題
-
懷疑最高優先級的任務中存在死循環
-
懷疑操作系統中存在一些bug
-
懷疑是中斷引起的程序一直在中斷中,不能進入正常任務
解決方案 -
對錯誤中斷進行排查分析,程序中開啓的錯誤中斷響應只有HardFault,在進入HardFault的中斷服務函數後會進行相應的處理
/* * * @brief This function handles Hard Fault exception. * @param None * @retval None */ void HardFault_Handler(void) { /* Go to infinite loop when Hard Fault exception occurs */ while (1) { BEEP_TOGGLE; Delay_ms(500); } }
因此,從表現上看基本可以排除進入Hard Fault的可能,除此之外,Hard Fault的中斷優先級爲 -1,如果程序是死在HardFault中,那麼其他的中斷響應應該也是無法響應的,這與問題的表現,中斷仍然可以響應矛盾,因此可以斷定,不是進入了HardFault和其他的一些問題中斷。
-
關於指針和堆棧的問題,一般有問題會直接進Hard Fault,而此時基本已經排除了Hard Fault的問題,除此之外也嘗試擴大了硬件和操作系統的堆棧,問題沒有解決,確定不是這裏有問題。
-
關於操作系統的Bug在網上查了不少,早期的操作系統確實存在問題但是系統中使用的2.92版本應該是不存在此類Bug的。
總結
上述對問題的解釋其實有點牽強,因爲大多數時候發送和接收都是這樣進行處理的,但是都沒有出現問題,而且DMA的使用也使用了很長時間也沒有出現過問題,並且就算出問題也是隨機的,並不是每次都出問題,所以懷疑出現問題應該是跟各個中斷之間的優先級、響應時間、以及具體的時序有一些複雜的關係,具體是什麼關係就很難尋找了,推薦的做法就是防範於未然,將不用的中斷統統關閉,這樣總是沒有壞處的。
後續:問題的解決
原因
造成該問題的原因是程序進入USART中斷,但觸發中斷的標誌位沒有被清除,一直循環觸發中斷,程序卡在中斷中出不來
當使用
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)
使能串口接收中斷時,實際上 USART_CR1 寄存器中 RXNEIE 被置 1
但是當RXNEIE = 1時 同時還啓動了串口的 ORE 中斷
ORE:上溢錯誤 (Overrun error)
在 RXNE = 1 的情況下,當移位寄存器中當前正在接收的字準備好傳輸到 RDR 寄存器時,該
位由硬件置 1。如果 USART_CR1 寄存器中 RXNEIE = 1,則會生成中斷。該位由軟件序列清
零(讀入 USART_SR 寄存器,然後讀入 USART_DR 寄存器)。
0:無上溢錯誤
1:檢測到上溢錯誤
注意: 當該位置 1 時, RDR 寄存器的內容不會丟失,但移位寄存器會被覆蓋。如果 EIE 位置 1,
則在進行多緩衝區通信時會對 ORE 標誌生成一箇中斷。
當發生上溢錯誤時程序會響應串口中斷進入到如下的中斷函數中
void USART1_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
USART_ClearFlag(USART1, USART_FLAG_RXNE); //USART_FLAG_RXNE
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
temp = USART_ReceiveData(USART1);
}
}
但是由於此時觸發的是ORE中斷,中斷函數中的內容並不能滿足ORE狀態標誌位被清除的條件:
讀入 USART_SR 寄存器,然後讀入 USART_DR 寄存器
因此該標誌位始終存在,無法被清除,串口中斷一直被觸發
程序卡死
解決方案
中斷函數中加入如下內容
void USART1_IRQHandler(void)
{
u8 temp;
if(USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) //讀入USART_SR
{
USART_ReceiveData(USART1); //讀入USART_DR
}
else if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
USART_ClearFlag(USART1, USART_FLAG_RXNE);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
temp = USART_ReceiveData(USART1);
}
}
其中
if(USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) //讀入USART_SR
{
USART_ReceiveData(USART1); //讀入USART_DR
}
滿足了ORE被清除的條件
讀入 USART_SR 寄存器,然後讀入 USART_DR 寄存器
特別注意
根據參考手冊中顯示
ORE:上溢錯誤 (Overrun error)
在 RXNE = 1 的情況下,當移位寄存器中當前正在接收的字準備好傳輸到 RDR 寄存器時,該
位由硬件置 1。如果 USART_CR1 寄存器中 RXNEIE = 1,則會生成中斷。該位由軟件序列清
零(讀入 USART_SR 寄存器,然後讀入 USART_DR 寄存器)。
0:無上溢錯誤
1:檢測到上溢錯誤
注意: 當該位置 1 時, RDR 寄存器的內容不會丟失,但移位寄存器會被覆蓋。如果 EIE 位置 1,
則在進行多緩衝區通信時會對 ORE 標誌生成一箇中斷。
ORE上溢錯誤產生中斷有兩種方式,其中之一是由於普通模式下一個字節尚未處理完,又接收到一個字節。另外一種方式是在開啓了錯誤中斷標誌位EIE時,在使用DMA進行數據傳輸時若發生上溢則會產生中斷
EIE:錯誤中斷使能 (Error interrupt enable)
對於多緩衝區通信( USART_CR3 寄存器中 DMAR = 1),如果發生幀錯誤、上溢錯誤或出
現噪聲標誌( USART_SR 寄存器中 FE = 1 或 ORE = 1 或 NF = 1),則需要使用錯誤中斷
使能位來使能中斷生成。
0:禁止中斷
1:當 USART_CR3 寄存器中的 DMAR = 1 並且 USART_SR 寄存器中的 FE = 1 或 ORE = 1
或 NF = 1 時,將生成中斷。
與此相對應的,庫函數中的中斷定義也有兩種
/** @defgroup USART_Interrupt_definition
* @{
*/
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
#define USART_IT_ORE_RX ((uint16_t)0x0325) /* In case interrupt is generated if the RXNEIE bit is set */
#define USART_IT_IDLE ((uint16_t)0x0424)
#define USART_IT_LBD ((uint16_t)0x0846)
#define USART_IT_CTS ((uint16_t)0x096A)
#define USART_IT_ERR ((uint16_t)0x0060)
#define USART_IT_ORE_ER ((uint16_t)0x0360) /* In case interrupt is generated if the EIE bit is set */
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
/** @defgroup USART_Legacy
* @{
*/
#define USART_IT_ORE USART_IT_ORE_ER
如果在進行中斷標誌位的查詢時使用
if(USART_GetITStatus(USART1, USART_IT_ORE) != RESET)
其實只查詢了打開EIE情況下的溢出中斷標誌位
#define USART_IT_ORE_ER ((uint16_t)0x0360) /* In case interrupt is generated if the EIE bit is set */
而如果沒有打開EIE的話,當然是查不到RXNEIE
#define USART_IT_ORE_RX ((uint16_t)0x0325) /* In case interrupt is generated if the RXNEIE bit is set */
所對應的中斷標誌的
這也是有不少博客中說存在Bug,查詢不到中斷標誌的原因。
因此如果是打開了EIE產生了溢出中斷需要在中斷服務函數中添加的是
if(USART_GetITStatus(USART1, USART_IT_ORE) != RESET) //讀入USART_SR
{
USART_ReceiveData(USART1); //讀入USART_DR
}
或者
if(USART_GetITStatus(USART1, USART_IT_ORE_ER ) != RESET) //讀入USART_SR
{
USART_ReceiveData(USART1); //讀入USART_DR
}
而如果沒有打開EIE,則需要添加的是
if(USART_GetITStatus(USART1, USART_IT_ORE_RX) != RESET) //讀入USART_SR
{
USART_ReceiveData(USART1); //讀入USART_DR
}