单片机一般是cortex-m3/m4/m0之类的内核,典型的就是stm32,其实其他内核也是一个道理。hardfault错误一般是操作了不该操作的内存,或者执行了不该执行的动作,例如一个非法的函数指针,你非要去调用。
调试这个错误的原理是:
发生hardfault错误后就会进入相应的hardfault中断,进入中断前会在被中断的地址执行压栈动作,将当时的工作环境(就是系统的一些寄存器,r0,r1,r2,r3,lr,pc,r12)压进堆栈,将lr赋值非法数据,然后跳到hardfault中断执行中断服务程序。那么只需要将压栈后的lr读出来,就知道中断服务程序的返回地址,也就是出现hardfault时的程序地址,也就是错误的代码地址。
直接看代码:
/**
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
void HardFault_Handler_C(unsigned int* hardfault_args)
{
printf("\r\nsp =0x%.8X\r\n",hardfault_args);
printf("R0 = 0x%.8X\r\n",hardfault_args[0]);
printf("R1 = 0x%.8X\r\n",hardfault_args[1]);
printf("R2 = 0x%.8X\r\n",hardfault_args[2]);
printf("R3 = 0x%.8X\r\n",hardfault_args[3]);
printf("R12 = 0x%.8X\r\n",hardfault_args[4]);
printf("LR = 0x%.8X\r\n",hardfault_args[5]);
printf("PC = 0x%.8X\r\n",hardfault_args[6]);
printf("PSR = 0x%.8X\r\n",hardfault_args[7]);
printf("BFAR = 0x%.8X\r\n",*(unsigned int*)0xE000ED38);
printf("CFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED28);
printf("HFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED2C);
printf("DFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED30);
printf("AFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED3C);
printf("SHCSR = 0x%.8X\r\n",SCB->SHCSR);
while (1);
}
#undef TST
__ASM void HardFault_Handler_a(void)
{
IMPORT HardFault_Handler_C
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B HardFault_Handler_C
}
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
HardFault_Handler_a();
printf("HardFault_Handler\r\n");
while (1)
{
}
}
HardFault_Handler_a这个函数里面执行过程是:判断lr的值,判断是从msp还是psp中去读堆栈数据,将堆栈指针赋给r0,然后调用HardFault_Handler_C这个函数
HardFault_Handler_C执行过程:就是从堆栈中将堆栈的数据打印出来,重点关注lr
拿到lr以后,打开keil,一半都不习惯使用硬件的jtag,所以设置下软件模拟,目的只是为了看反汇编代码
然后进入调试模式,出现了反汇编代码,在反汇编代码里面右键-"show code at address"-输入刚才得到的lr数值,就跳到出问题的那个代码了!