当单片机发生程序异常时,会进入到HardFault_Handler中断,相当于windows的蓝屏,我现在介绍的是如何获取中断位置,并自动记录异常位置(我的做法是将异常的时间,与代码地址存储到备份区,这样哪怕重启了依旧可以查询上一次发生异常的位置,这部分代码需要自己去实现,我现在实现的是获取异常代码位置)。
2019-01-29 发现增加了OS后不一样,增加裸机与OS下的异常处理。
原理如下:当异常时,硬件会将一些CPU的寄存器保存到栈中,通过在异常时获取堆栈指针SP的值,通过SP获取当前栈的位置,然后获取异常之前的PC指针的值,就知道异常的位置了。
//这个就是CPU寄存器在栈中的排列
//栈数据定义
typedef struct
{
u32 R0;
u32 R1;
u32 R2;
u32 R3;
u32 R12;
u32 LR;
u32 PC;
u32 xPSR;
}STACK_DATA_TYPE;
//获取当前CPU堆栈指针MSP
__asm u32 getMSP(void)
{
mrs r0, msp
bx lr
}
//获取当前CPU堆栈指针PSP
__asm u32 getPSP(void)
{
mrs r0, psp
bx lr
}
//没有操作系统时的异常处理
void NotOSHardFault_Handler(u32 msp_addr)
{
STACK_DATA_TYPE *p; //堆栈中存储的数据
msp_addr -= 4; //堆栈指针减去4,因为默认堆栈指针指向的是下一个空的地方-所以必须减去4
uart_printf_enable(); //开启串口调试信息
uart_printf("\r\n-----------------ERROR -----------------\r\nHardFault_Handler\r\n");
if((msp_addr >> 20) != 0x200) //判断地址范围,必须是0x200xxxxx 范围
{
uart_printf("警告:堆栈指针被破坏,无法记录现场!\r\n");
return;
}
msp_addr += 8; //进入中断后,堆栈又进入了2个u32数据,因此需要往后推
p = (STACK_DATA_TYPE *)msp_addr;
p->PC -= 3; //PC指针要减去3
//BackupArea_RecordHardFault(RTC_GetSec(), p->PC); //记录异常pc指针信息到备份区
uart_printf("R0:0x%08X\r\n", p->R0);
uart_printf("R1:0x%08X\r\n", p->R1);
uart_printf("R2:0x%08X\r\n", p->R2);
uart_printf("R3:0x%08X\r\n", p->R3);
uart_printf("R12:0x%08X\r\n", p->R12);
uart_printf("LR:0x%08X\r\n", p->LR);
uart_printf("PC:0x%08X\r\n", p->PC);
uart_printf("xPSR:0x%08X\r\n", p->xPSR);
uart_printf("系统即将复位...\r\n");
}
//有操作系统时的异常处理
void OSHardFault_Handler(u32 psp_addr)
{
STACK_DATA_TYPE *p; //堆栈中存储的数据
psp_addr -= 4; //堆栈指针减去4,因为默认堆栈指针指向的是下一个空的地方-所以必须减去4
uart_printf_enable(); //开启串口调试信息
uart_printf("\r\n-----------------ERROR -----------------\r\nHardFault_Handler\r\n");
if((psp_addr >> 20) != 0x200) //判断地址范围,必须是0x200xxxxx 范围
{
uart_printf("警告:堆栈指针被破坏,无法记录现场!\r\n");
return;
}
p = (STACK_DATA_TYPE *)psp_addr;
p->PC -= 3; //PC指针要减去3
//BackupArea_RecordHardFault(RTC_GetSec(), p->PC); //记录异常pc指针信息到备份区
uart_printf("R0:0x%08X\r\n", p->R0);
uart_printf("R1:0x%08X\r\n", p->R1);
uart_printf("R2:0x%08X\r\n", p->R2);
uart_printf("R3:0x%08X\r\n", p->R3);
uart_printf("R12:0x%08X\r\n", p->R12);
uart_printf("LR:0x%08X\r\n", p->LR);
uart_printf("PC:0x%08X\r\n", p->PC);
uart_printf("xPSR:0x%08X\r\n", p->xPSR);
uart_printf("系统即将复位...\r\n");
}
//硬件中断
void HardFault_Handler (void)
{
u32 msp_addr = getMSP(); //获取线程模式下堆栈指针位置
u32 psp_addr = getPSP(); //获取中断下的堆栈指针位置-用于OS启动后
#if(UCOS_II_EN) //使能了操作系统的一个宏定义,自己去定义
if(SYS_GetOsStartup()) //操作系统运行了-自己定义一个状态,可以获取操作系统是否启动
{
OSHardFault_Handler(psp_addr);
}
else
{
NotOSHardFault_Handler(msp_addr);
}
#else //没有使能操作系统
NotOSHardFault_Handler(msp_addr);
#endif //UCOS_II_EN
Delay_MS(10); //延时一下,防止重启速度太快
SYSTEM_SoftReset(); //复位重启
}
可以自己将PC指针的值存储到备份区,这样可以查询到产品是否发生过异常,并且异常位置,通过代码仿真可以找到异常代码位置。
异常前CPU寄存器数据
异常后,通过堆栈指针获取到堆栈中的数据,存放有异常前的PC指针,也就是异常位置。
获取了PC指针就能定位位置了
串口输出的异常前CPU寄存器数据