STM32F4 程序運行一段時間後死掉 但中斷正常響應(串口一直進中斷導致程序被卡死)

問題描述

控制系統使用的是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 
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章