简介
众所周知,除数为0是不合法的运算,在一般情况下编译器、单片机中,对除0的运算结果为0。在某些情况下为了方便debug,Cortex-M系列单片机支持除0异常中断。如何使用,请参考《M4 Devices Generic User Guide.pdf》。
下面以Cortex-M4作为硬件平台,IAR作为编译工具。简单介绍如何使用除0中断异常。
1. Cortex-M Fault异常
每个符合CMSIS规范的编译器所提供的启动文件(Startup_device)都会定义好设备所有的异常和中断向量。这些向量表定义了异常或中断处理程序的入口地址。参考《M4 Devices Generic User Guide.pdf》中的p34~37。
其中HardFault异常处理程序在非硬Fault异常(BusFault、MemMange和UsageFault)被禁能,并且这些Fault异常被触发时,非硬Fault异常将*为HardFault。
除0异常将触发UsageFault,如UsageFault未使能,将触发HardFault。
2. 打开除0异常中断,SCB->CCR 寄存器
除0异常捕捉需要通过使能CCR寄存器bit4(div_0_trp)打开,参考《M4 Devices Generic User Guide.pdf》p236。
DIV_0_TRP:
默认的情况下,div_0_trp=0,除0不会产生异常,商总是为0;当div_0_trp=1,将触发除0中断。
3.打开UsageFault中断,SCB->SHCSR寄存器
除0异常将触发UsageFault,默认情况下,UsageFault是被禁能,需要通过使能SHCSR寄存器bit18打开,参考《M4 Devices Generic User Guide.pdf》p239。
4. 中断前使用MSP还是PSP?
在中断中判断LR(即EXC_RETURN)bit2,可以推断使用的是MSP还是PSP,确定使用的栈后,通过栈找死机发生时的PC指针及LR。
5. 确定异常中断时的PC指针
获得了栈指针后(SP),sp+6即为PC指针,通过PC指针可以定位除0发生的为止,参考《M4 Devices Generic User Guide.pdf》p40
6. 参考代码
#include "am_mcu_apollo.h" void div0_test(void) { volatile uint32_t i =0, k; am_util_stdio_printf("\r\nexample start %s\r\n", __func__); //未开启除0异常中断,除0的结果为0 k = 6/i; am_util_stdio_printf("before div 0 trp 6/0=%d\r\n", k); //使能除0异常上报 SCB->CCR |= 0x10; //使能UsageFault_Handler SCB->SHCSR |= (1<<18); //打开总中断,因为UsageFault可以被关闭 __enable_irq();
//除0,将进入UsageFault_Handler am_util_stdio_printf("after div 0 trp 6/0=%d\r\n"); k = 6/i; }
void call_UsageFault(uint32_t u32IsrSP) { uint16_t ufsr; uint32_t *p; volatile uint32_t k; am_util_stdio_printf("\r\nenter %s\r\n", __func__); ufsr = (SCB->CFSR>>16); if (ufsr & (1<<9)) { //除0错误 am_util_stdio_printf("div 0 error\r\n"); } p = (uint32_t *)u32IsrSP;
am_util_stdio_printf("LR=0x%x\r\n", p[5]); am_util_stdio_printf("PC=0x%x\r\n", p[6]); am_util_stdio_printf("example end\r\n");
while(1); }
void UsageFault_Handler(void) { __asm(" push {r0,lr}"); __asm(" tst lr, #4"); //lr = exc_return; bit2--0:msp 1:psp __asm( "ITET EQ \n" "MRSEQ R0, MSP \n" "MRSNE R0, PSP \n" "ADDSEQ R0, R0, #8 \n" ); __asm(" bl call_UsageFault"); __asm(" pop {r0,pc}"); } int main(void) { div0_test(); }
7. 运行结果
打印结果
通过打印结果的PC指针,在模拟仿真界面,打开汇编窗口,输入0x84112,查找触发异常中断位置。从图片可以看出,改异常触发在div0_test(),对应的指令为 UDIV R0, R0, R1。可以推断出div0_test最后一行k = 6/i; 触发了除0中断。