做项目的过程中难免会遇到各式各样的死机问题,KEIL提供了很多好用的方法,串口打印,debug调试等等。然而有时一些问题往往难以复现,我们可以hardfault中断产生时信息保存在本地从而更好的定位分析问题
一、为什么会产生hardfault
死机问题的原因有多种例如
电源不稳定导致的死机
电磁干扰(EMI),静电放电导致的死机
甚至是一些极端环境(温度,湿度,辐射等)
或者由于元器件老化,使用寿命完结
最后是由于编程的不规范,没有按照芯片手册,程序规则来书写代码导致的
前面四种情况,我们暂且不讨论,对于第五种情况,我们可以通过加入看门狗,重启系统来保护我们程序。但是在一些不是很重要的场景这确实效,在一些工控领域,死机重启都是致命的问题,所以我们必须得一一排查问题所在。在Cortex-m处理器中加入了异常中断处理,可以有效的帮助我们定位分析问题。
• MemManage (Memory Management) Fault
• Bus Fault
• Usage Fault
我们开启这些中断后,系统会根据错误异常跳进相应的中断中去,而这些异常中断我们也是能编辑,和更改优先级的。
如上面的图所示这三个异常中断系统是默认关闭的。当这些中断关闭以后,系统会跳过其他的异常中断直接到HardFault中去,所以我们如果在跳入HardFault之前保存好现场信息就能较为容易的定位异常发生的点了,当然你如果想开启这也异常中断逐一分析也是可以的你可以通过这几天指令来开启中断。
SCB->SHCSR j= SCB_SHCSR_MEMFAULTENA_Msk; //Set bit 16
SCB->SHCSR j= SCB_SHCSR_BUSFAULTENA_Msk; //Set bit 17
SCB->SHCSR j= SCB_SHCSR_USGFAULTENA_Msk; //Set bit 18
配置中断优先级
NVIC_SetPriority(MemoryManagement_IRQn, priority);
NVIC_SetPriority(BusFault_IRQn, priority);
NVIC_SetPriority(UsageFault_IRQn, priority);
我们这里就不详细介绍了,主要讨论HardFault中断
二、异常中断发生,保存现场
在Keil我们可以看到这样一栏
这是系统运行时函数的堆栈调用情况,LR指针可以简单的理解为函数当前运行的地址,PC指针为函数接下来要运行的地址,当我们发生异常中断时系统会将LR自动保存
首先当发生异常中断,系统会读取LR中的bit[2]如果是0则判断时MSP指针,如果是1则是PSP指针,然后将指针从上往下压入栈中如上图所示。根据这个思路我们只要读取到xPSR指针我们就能获取我们需要的信息了。
三、修改.s文件将HardFoult_Handler传入xPSR指针
在.s文件中我们可以看到这样一段
这中间的EXPORT HardFault_Handler [WEAK]表示声明一个HardFault_Handle函数(其他的函数也一样)为一个虚函数,虚函数的意思是用户可以定义一个同名的函数,若用户没定义则执行原本的实体,若用户定义了则执行用户的函数实体,末尾则是这样的
这里的HardFault_Handler,表示定义了一个空的HardFault_Handle函数(其他的函数也一样),类似于void HardFault_Handler(void) { };
我们则是要改写这个函数,首先通过;号注释掉原来的函数,然后添加我们自定义HardFault_Handler函数,要顶格书写!!!,当然改写函数名也是可以的你可以改成HardFault_Handler_C之类的.
HardFault_Handler
TST LR,#4
ITE EQ
MRSEQ R0,MSP
MRSNE R0,PSP
MOV R1,LR
B HardFault_Handler_C
END
解释一下 首先是检查LR寄存器的bit[2]是0还是1之前我们说过,发生异常中断后系统会自动保存L根据LR中的BIT[2]来判断是返回MSP还是PSP指针
然后根据LR中的bit[2]来保存到 R0的地址然后将R0作为形参传入HardFault_Handler函数 ,B HardFault_Handler返回HardFault_Handler函数指针
B相当于 return。
之后我们要在.c文件中重新定义一下HardFault_Handler_C函数
void HardFault_Handler_C(unsignedlong* hardfault_args,unsigned int lr_value){
unsigned long stacked_r0;
unsigned long stacked_r1;
unsigned long stacked_r2;
unsigned long stacked_r3;
unsigned long stacked_r12;
unsigned long stacked_lr;
unsigned long stacked_pc;
unsigned long stacked_psr;
unsigned long cfsr;
unsigned long bus_fault_address;
unsigned long memmanage_fault_address;
bus_fault_address = SCB->BFAR;
memmanage_fault_address = SCB->MMFAR;
cfsr = SCB->CFSR;
stacked_r0 = ((unsignedlong)hardfault_args[0]);
stacked_r1 = ((unsignedlong)hardfault_args[1]);
stacked_r2 = ((unsignedlong)hardfault_args[2]);
stacked_r3 = ((unsignedlong)hardfault_args[3]);
stacked_r12 = ((unsignedlong)hardfault_args[4]);
stacked_lr = ((unsignedlong)hardfault_args[5]);
stacked_pc = ((unsignedlong)hardfault_args[6]);
stacked_psr =((unsignedlong)hardfault_args[7]);
while(1);
}
然后就可以在HardFault_Handler_C中的信息,通过打印或者写入flash来保存我们的关键信息了,至于获取到这些信息后怎么定位问题,这个又是另一个话题了