1.背景
1. 项目中,调试过程或者测试中都会出现程序跑飞问题,这个时候问题特别难查找。
2. 触发硬件错误往往是因为内存错误。这种问题特别难查找,尤其是产品到了测试阶段,而这个异常复现又比较难的情况下,简直头疼。
3. 我目前的工作完全是没有调试器的,代码都是写好了,通过bootloader烧写进去的。也就意味着不能在void HardFault_Handler(void)中打断点,然后通过当前lr寄存器中的值查看异常地址。
参考:
STM32进入HardFault_Handler的调试方法-电子工程世界 (eeworld.com.cn)
Keil环境下STM32定位hardfault位置方法和遇到的情况 - 张士玉小黑屋 (zhangshiyu.com)
还参考了很多文章, 有些写的时候都关掉了, 这里就不列出来了.
2.裸机程序触发硬件错误
裸机触发硬件错误时, R14(LR)寄存器的值为0xFFFFFFF9,此时看的是MSP指针保存的栈顶地址,然后在Memory Window中输入这个值(0x20000738),第6个值就是错误退的地址(也就是我这个空指针的地址),查看.map文件,显然我们的0x080001D1在0x080001cd(my_test在flash的首地址)和0x080001d3(my_test1在flash的首地址)之间。因此我们就可以定位出错误就在my_test()这个函数中。
也可以查看汇报,看到地址(不过这里看起来有点让人蒙圈)
3.RTOS程序触发硬件错误
RTOS触发硬件错误时, R14(LR)寄存器的值为0xFFFFFFFD, 此时看的是PSP中的栈顶地址。同上分析,我们最终也能通过.map定位错误发生的位置。
补充知识:为什么裸机的是0xFFFFFFF9(9:1001)而多线程的是0xFFFFFFFD(1101),如下图,bit2是区别的关键,后面打印错误需要用到这一点。
4.在硬件错误中断打印这些值
我们最终的目的,就是在触发硬件中断的时候,要将这些值打印出来,从而准确定位出是哪个函数造成的错误。
typedef struct
{
u32 R0;
u32 R1;
u32 R2;
u32 R3;
u32 R12;
u32 LR;
u32 PC;
u32 xPSR;
}STACK_DATA_TYPE;
void NotOSHardFault_Handler(u32 msp_addr)
{
STACK_DATA_TYPE *p; //堆栈中存储的数据
printf("\r\n-----------------ERROR -----------------\r\nHardFault_Handler\r\n");
p = (STACK_DATA_TYPE *)msp_addr;
printf("R0:0x%08X\r\n", p->R0);
printf("R1:0x%08X\r\n", p->R1);
printf("R2:0x%08X\r\n", p->R2);
printf("R3:0x%08X\r\n", p->R3);
printf("R12:0x%08X\r\n", p->R12);
printf("LR:0x%08X\r\n", p->LR);
printf("PC:0x%08X\r\n", p->PC);
printf("xPSR:0x%08X\r\n", p->xPSR);
printf("系统即将复位 ...\r\n");
}
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
int msp, psp;
msp = __get_MSP();
NotOSHardFault_Handler(msp);
psp = __get_PSP();
NotOSHardFault_Handler(psp);
while (1)
{
}
}
当前还有一个问题需要解决, 我无法获取lr寄存器中保存的值, 也就是说我无法准确的确定是进程堆栈出错还是主堆栈出错.(我测试了发现在rtos中, 如果让错误放在main函数中, 此时MSP堆栈指向的位置是出错位置, 如果让错误放在任务中, 此时PSP堆栈指向的位置是出错位置, 如果在裸机程序中, 通过MSP查找, 当前我用的是keil编译的,我不知道IAR里面是否有__get_MSP(), __get_PSP())