STM32 F4xx Fault 異常錯誤定位指南


STM32 F407 采用 Cortex-M4 的內核,該內核的 Fault 異常可以捕獲非法的內存訪問和非法的編程行為。Fault異常能夠檢測到以下幾類非法行為:

  • 總線 Fault: 在取址、數據讀/寫、取中斷變量、進入/退出中斷時寄存器堆棧操作(入棧/出棧)時檢測到內存訪問錯誤。
  • 存儲器管理 Fault: 檢測到內存訪問違反了內存保護單元(MPU, MemoryProtection Unit)定義的區域。
  • 用法 Fault: 檢測到未定義的指令異常,未對其的多重加載/存儲內存訪問。如果使能相應控制位,還可以檢測出除數為零以及其他未對齊的內存訪問。
  • 硬 Fault: 如果上述的總線 Fault、存儲器管理 Fault、用法 Fault 的處理程序不能被執行(例如禁能了總線 Fault、存儲器管理Fault、用法Fault 的異常或者在這些異常處理程序中又出現了新的Fault)則觸發硬Fault。

為了解釋所述的 Fault 中斷處理程序的原理,這里重述一下當系統產生異常時 MCU 的處理過程:

  • 有一個壓棧的過程,若產生異常時使用 PSP(進程棧指針),就壓入到 PSP 中,若產生異常時使用MSP(主棧指針),就壓入MSP 中。
  • 會根據處理器的模式和使用的堆棧,設置 LR 的值(當然設置完的LR 的值再壓棧)。
  • 異常保存,硬件自動把 8 個寄存器的值壓入堆棧(8 個寄存器依次為 xPSR、PC、LR、R12以及 R3~R0)。如果異常發生時,當前的代碼正在使用PSP,則上面8 個寄存器壓入PSP; 否則就壓入MSP。

當系統產生異常時,我們需要兩個關鍵寄存器值,一個是 PC ,一個是 LR (鏈接寄存器),通過 LR找到相應的堆棧,再通過堆棧找到觸發異常的PC 值。將產生異常時壓入棧的 PC 值取出,並與反匯編的代碼對比就能得到哪條指令產生了異常。
  這里解釋一下關於 LR 寄存器的工作原理。如上所述,當 Cortex-M4 處理器接受了一個異常后,寄存器組中的一些寄存器值會被自動壓入當前棧空間里,這其中就包括鏈接寄存器(LR )。這時的 LR 會被更新為異常返回時需要使用的特殊值(EXC_RETURN)。關於EXC_RETURN 的定義如下,其為 32 位數值,高 28 位置 1,第 0 位到第三位則提供了異常返回機制所需的信息,如下表所示。可見其中第 2 位標示着進入異常前使用的棧是 MSP還是PSP。在異常處理過程結束時,MCU 需要根據該值來分配 SP 的值。這也是本方法中用來判斷所使用堆棧的原理,其實現方法可以從后面_init_hardfault_isr 中看到。

  異常處理流程:
首先要定義異常處理函數,在M4和M3核中,這兩個是一樣的,可以直接在stm32_f4xx.s中定義:

.cpu cortex-m3
.thumb

.global HardFault_Handler
.extern hard_fault_handler_c

HardFault_Handler:
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B hard_fault_handler_c

  這里有幾個命令要說明一下含義: TST 是Bit級別的與操作。ITE 是 MRSEQ和MRSNE都是兩個命令的合體,分別可以拆開成:MRS,EQ和MRS,NE,分別的意思是如果兩者相等,則把MSP的值賦值到R0,如果R0和PSP不等,則把PSP賦植到R0.ITE讀為 if-then-else

  關於HardFault_Handler 這個函數,一般在stm32_f4xx.s的中斷向量表中,我的系統中的代碼如下所示:

  

  g_pfnVectors:
  .word  _estack
  .word  Reset_Handler
  .word  NMI_Handler
  .word  HardFault_Handler
  .word  MemManage_Handler
  .word  BusFault_Handler
  .word  UsageFault_Handler

  接下來就是整個流程的代碼實現:

/ hard fault handler in C,
// with stack frame location as input parameter
void hard_fault_handler_c (unsigned int * hardfault_args)
{
unsigned int stacked_r0;
unsigned int stacked_r1;
unsigned int stacked_r2;
unsigned int stacked_r3;
unsigned int stacked_r12;
unsigned int stacked_lr;
unsigned int stacked_pc;
unsigned int stacked_psr;

stacked_r0 = ((unsigned long) hardfault_args[0]);
stacked_r1 = ((unsigned long) hardfault_args[1]);
stacked_r2 = ((unsigned long) hardfault_args[2]);
stacked_r3 = ((unsigned long) hardfault_args[3]);

stacked_r12 = ((unsigned long) hardfault_args[4]);
stacked_lr = ((unsigned long) hardfault_args[5]);
stacked_pc = ((unsigned long) hardfault_args[6]);
stacked_psr = ((unsigned long) hardfault_args[7]);

printf ("\n\n[Hard fault handler - all numbers in hex]\n");
printf (“R0 = %x\n”, stacked_r0);
printf (“R1 = %x\n”, stacked_r1);
printf (“R2 = %x\n”, stacked_r2);
printf (“R3 = %x\n”, stacked_r3);
printf (“R12 = %x\n”, stacked_r12);
printf (“LR [R14] = %x subroutine call return address\n”, stacked_lr);
printf (“PC [R15] = %x program counter\n”, stacked_pc);
printf (“PSR = %x\n”, stacked_psr);
printf (“BFAR = %x\n”, (*((volatile unsigned long )(0xE000ED38))));
printf (“CFSR = %x\n”, (((volatile unsigned long )(0xE000ED28))));
printf (“HFSR = %x\n”, (((volatile unsigned long )(0xE000ED2C))));
printf (“DFSR = %x\n”, (((volatile unsigned long )(0xE000ED30))));
printf (“AFSR = %x\n”, (((volatile unsigned long *)(0xE000ED3C))));
printf (“SCB_SHCSR = %x\n”, SCB->SHCSR);

while (1);
}

/* hard fault interrupt handler */
void _int_hardfault_isr( )
{
__asm(“TST LR, #4”);
__asm(“ITE EQ”);
__asm(“MRSEQ R0,MSP”);
__asm(“MRSNE R0,PSP”);
__asm(“B hard_fault_handler_c”);
}

void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
DEBUG_ERR(" hard fault handler ");
_int_hardfault_isr();
while (1)
{
}
}

  上面的這些代碼,一般的工程師就可以看懂了,就不多做介紹了,假如你有啥這方面的問題,歡迎交流和溝通,反正是我的板子可以正常使用這些代碼了。

參考文檔:

https://community.arm.com/cn/b/blog/posts/3-thumb-2


免責聲明!

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



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