1.現象
在基於STM32開發一個項目過程中,遇到一個比較奇葩的現象:經常會時不時出現修改上層的應用代碼導致程序運行不起來,進不去main函數。這個STM32程序是分爲bootloader層和APP層,出現這個奇葩現象的時候,bootloader層是可以正常運行的,但是跳轉到APP層的時候,就發現進不了main函數。
2.分析
一開始也是找不到原因何在,通過網上搜索發現也有類似出現STM32進不去main函數的,原因多數是因爲printf函數的應用導致的。但通過代碼檢查發現我遇到的這個奇葩現象,並非printf函數運用導致。這裏也藉此瞭解一下printf函數在stm32中使用注意事項,避免出現這個錯誤。
printf函數:
printf之類的函數,使用了半主機模式,所以要利用目標ARM器件的輸入輸出設備,首先要關掉半主機機制,然後再將輸入輸出重定向到ARM器件上,如printf和scanf,需重寫fputc和fgetc.
具體代碼實現可參考如下(重寫fputc):
#if 1
#pragma import(__use_no_semihosting) //確保沒有從C庫鏈接使用半主機的函數
//標準庫需要支持的函數
struct __FILE
{
int handle;
};
FILE __stdout;
//以避免使用半主機模式
void _sys_exit(int x)
{
x = x;
}
//重定義fputc函數
int fputc(int ch, FILE *f)
{
while((USART1->ISR & 0X40) == 0); //循環發送,直到發送完畢
USART1->TDR = (u8) ch;
return ch;
}
#endif
如果不重寫fputc等函數,也可直接勾選keil工具裏面的Use MicroLIB.
3. 仿真調試
當經過檢查後,發現並非是printf函數使用不當導致程序進不去main函數,於是採用在線仿真的方式一步步查找原因。
第一步:先是全速運行,發現程序直接進入了HardFault_Handler。
接着分析啓動文件startup_stm32xx.s:在執行進main函數之前,會先執行SystemInit函數,進行系統初始化。
在SystemInit函數裏面打斷點,單步執行調試,看程序能否執行進來:發現程序可以執行進來。
於是結合上面分析的printf函數原因, 於是嘗試在fputc函數打一斷點,看是否會跑進fputc函數:發現程序確實跑進了fputc函數
通過SystemInit函數單步執行跑到fputc函數可以分析到,應該是程序哪裏出錯後,執行了打印輸出,而此時串口並未初始化。發現從SystemInit到fputc這樣順序執行下來,不可能會出現打印的函數,那此時有可能就是發生了某種中斷,導致在進入main函數之前,跑進了一箇中斷處理函數裏面去了。
那有可能是哪個中斷導致的呢?: 像定時器、RTC這些中斷,都是進入main函數初始化後纔會開啓的,排除了這類中斷,那應該就是某種系統中斷導致的。該stm32程序用到了FreeRtos系統,那可能中斷就是systick中斷,svc中斷,pendsv中斷。而svc、pendsv中斷都是FreeRtos系統啓動後纔會執行的。那最大可能就是systick中斷了。
接着在systick中斷函數打斷點,重新單步執行,看是否進入該中斷:確實進入了該中斷。
通過對這個中斷函數一步步執行,發現最終在此處出錯了:進入了HardFault_Handler。
定位到是系統滴答中斷systick導致進不去main函數的原因了,那哪裏開啓了這個中斷呢?通過代碼分析發現是bootloader程序開啓了這個中斷,在跳轉到main函數之前並未關閉這個中斷,導致出現了這中異常錯誤。
4.解決辦法
既然是系統滴答中斷導致的進不去main函數,那就要在進入main函數之前關閉這個滴答中斷。
方法一: 在bootloader程序跳轉到app層時就關閉系統滴答中斷:SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
方法二:在SystemInit函數關閉系統滴答中斷:SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; (如下圖所示)
5.總結
在實際項目運用過程中,遇到類似這些奇葩現象,可以嘗試通過仿真調試,一步步嘗試可能出現錯誤的地方,進行打斷點分析,找出根因所在,你的點贊是我最大的動力。