STM32程序異常——中斷處理要謹慎


問題背景

最近有一個新項目(車載項目),板子上除了原來的ARM + STM32F030K6Tx又多了一個8bit的mcu的單片機,這可真是嵌入式全家福了。

系統的主要核心工作是由arm來完成,但是在開機早期及休眠、喚醒等過程是由stm32來控制完成的。

開機過程中的ACC打火檢測、高低壓檢測,同時也是為了保證休眠的時候整塊板子的的低功耗(休眠時只有rtc有電及stm32處於深度休眠,其他全部掉電)。

最近添加了一顆tw8836mcu,主要是為了快速開機出預覽,因為我的linux系統開機起來出攝像頭預覽需要11s左右,客戶需求開機2s內出預覽畫面,

這種速度只能依賴屏驅芯片(或者更換成rtos)來實現,所以我們又多了一顆屏驅芯片,其作用就是在上電開機的時候快速輸出攝像頭預覽,等arm開機起來之后接受來自arm的lvds信號切換顯示arm輸出的內容。

考慮到開發費用,沒有采用由芯片廠來完成tw8836的程序開發的合作方式,有考慮到開發時間問題,也沒有自己去重新學習這塊mcu的編程,而是通過iic方式刷參數實現。

 

開機第一階段

stm32通過iic給tw8836刷參數,顯示攝像頭數據,然后交出tw8836的控制權

開機第二階段

arm啟動完成,由arm通過iic給TW8836刷參數,顯示arm輸出的lvds信號。

 

在我開始寫stm32代碼之前,公司有一份成熟的代碼實現的是開機、休眠、喚醒、升級等功能,這部分代碼已經有人一直在維護。

我需要完成的是加上tw8836這部分功能,以及lcd的控制邏輯。

代碼模塊有以下:

1、iic通訊,由於所接的兩個io口不是硬件iic,所以需要寫一個模擬iic通訊。

2、部分io口控制功能。

3、pwm控制lcd背光亮度。

4、光感模塊的adc值檢測。

為了避免新功能與舊邏輯的干擾,我這邊沒有在他們的代碼基礎上去修改,而是重頭寫了一份全新的,待我驗證ok后再交給另外一個維護的同事合並驗證。

————————————————————————————————————————————————————————————————————

上周五把驗證ok的代碼交給同事合並驗證,搞了一天說跑不了,用在線調試,經常停在某處不返回,沒辦法這周叫他吧整個工程代碼擼過來瞧一瞧。

經過調試驗證確實后莫名死在某處不出來,不知道是掉坑里了還是逛窯子去了。

有以下幾個地方會無返回,也有可能還會有其他地方。

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
/*
 * 函數名:Delay_5us
 * 描述: 5us延時函數 單位為5us
 * 輸入: nTime
 * 輸出:無
 * 調用: Delay_5us(1)實現的延時為5us
 *       外部調用
 */
void Delay_5us(__IO uint32_t nTime)  {
    TimingDelay = nTime;    
    while(TimingDelay != 0);
}
/*
 * 函數名:TimingDelay_Decrement
 * 描述:獲取節拍程序
 * 輸入:無
 * 輸出:無
 * 調用:在SysTick  中斷函數SysTick_Handler()調用
 */  
void TimingDelay_Decrement(void)
{
    if (TimingDelay != 0x00) { TimingDelay--; } }

這些詭異的不返回和滴答定時器的初始化使能有關。

/*
 * 函數名:SysTick_Init
 * 描述:配置並啟動系統滴答定時器SysTick
 * 輸入:無
 * 輸出:無
 * 調用:外部調用
 */
void sys_tick_init(void)
{
        RCC_ClocksTypeDef RCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);
    /* SystemFrequency / 1000    1msÖжÏÒ»´Î
     * SystemFrequency / 100000     10usÖжÏÒ»´Î
     * SystemFrequency / 1000000 1usÖжÏÒ»´Î
     */
    if (SysTick_Config(/*SystemCoreClock*/RCC_Clocks.HCLK_Frequency / 200000))    // ST3.5.0¿â°æ±¾,5us/tick
    { 
        /* Capture error */ 
        while (1);
    }
}

我的代碼系統時鍾是采用pll倍頻道72M的,滴答定時器原始的中斷函數如下:

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
      TimingDelay_Decrement(); 
}

舊代碼系統時鍾沒有配置,采用默認的48M,合並代碼后采用了我這邊的時鍾配置,提高到72M。

舊代碼systick中斷是設置的1ms一次,合並后采用5us的中斷(因為模擬iic需要用的us級別的精准延時函數),

最終合並了舊程序的邏輯后中斷函數變成了如下,增加了一大堆用來做狀態機的標志:

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
      TimingDelay_Decrement();
   
      sysTickCtrl++;
    
    /* 2MS LOOP */
    if(!(sysTickCtrl%COUNT_2MS_MAX))
    {
        F_SYS_2MS=1;
    }
    
     /* 4MS LOOP */
    if(!(sysTickCtrl%COUNT_4MS_MAX))
    {
        F_SYS_4MS=1;
    }
    /* 8MS LOOP */
    if(!(sysTickCtrl%COUNT_8MS_MAX))
    {
        F_SYS_8MS=1;
    }
    /* 12MS LOOP */
    if(!(sysTickCtrl%COUNT_12MS_MAX))
    {
        F_SYS_12MS=1;
    }
    /* 12MS LOOP */
    if(!(sysTickCtrl%COUNT_40MS_MAX))
    {
        F_SYS_40MS=1;
    }
    /* 100MS LOOP */
    if(!(sysTickCtrl%COUNT_100MS_MAX))
    {
          F_SYS_100MS+=1;
    }

}

於是上面所述的詭異問題就產生了。

經過進一步驗證,如果在初始化systick的時候設置中斷時間大於10us程序就可以正常運行。

void sys_tick_init(void)
{
        RCC_ClocksTypeDef RCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);
    /* SystemFrequency / 1000    1msÖжÏÒ»´Î
     * SystemFrequency / 100000     10usÖжÏÒ»´Î
     * SystemFrequency / 1000000 1usÖжÏÒ»´Î
     */
    if (SysTick_Config(/*SystemCoreClock*/RCC_Clocks.HCLK_Frequency / 200000))    // ST3.5.0¿â°æ±¾,5us/tick
    { 
        /* Capture error */ 
        while (1);
    }
}

想來想去不應該是中斷頻率太高導致的,由此想到了linux系統內核中的中斷處理函數的幾點說明。

內核里面的中斷處理函數:

1、不能做延時delay操作。

2、不能做耗時過長的操作。

3、最好不用打印(調用printf打印其實也是很費時間的操作)

4、不能用死循環等待。

所以在內核驅動里面一般都是采用中斷上下午來處理,中斷上文喚醒處理線程,下午都在線程里干活。

這條鐵律同樣適用於單片機,即時——中斷處理函數一定不能做耗時較長的操作。

現在中斷函數里面進行了一大堆的取模運算,這是耗時比較長(相對而言)的運算。據此提出以下猜想:

 

中斷函數耗時太長,在中斷頻率又高的時情況下占據了大部分cpu資源。

 

驗證

將中斷后面部分的操作屏蔽,將中斷時間調整到5us 1us都沒問題。

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
void SysTick_Handler(void)
{
      TimingDelay_Decrement();
   
      sysTickCtrl++;

    /*only for test */
return;

    /* 2MS LOOP */
    if(!(sysTickCtrl%COUNT_2MS_MAX))
    {
        F_SYS_2MS=1;
    }
.............
}

所以真音就是;中斷函數耗時較長占據了大部分cpu。

解決方法:優化中斷處理函數

 

——————————————————————————————

有關加減乘除運算耗時的說明:

了解匯編的人應該都知道加減乘除中的加減運算是比較快的,但是要實現乘除運算需要n多條指令才能實現,這是一種效率比較低下的操作,所以在可以的情況下盡量避免乘除運算,或者轉換成效率較高的移位運算(<< >>)

例如

/*乘除預算裝換移位運算
*、
num * 8   ==> num << 3
num / 8   ==> num >> 3
    

移位運算相對於乘除運算效率高的多得多。

 

 

2018年6月26

深圳南山科技工業園

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM