問題背景
最近有一個新項目(車載項目),板子上除了原來的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
深圳南山科技工業園