在实际的MCU产品开发中,开发人员经常会遇到各种各样的异常问题。发生异常时,有时MCU是真正“死机”了,例如在进行ESD实验时,MCU可能真的死掉,即使通过NRST引脚复位也无法恢复。有时则是程序跑入了某个异常的分支,MCU本身仍在工作,只是表现出异常现象。
当发生异常时,如果能快速复现,调试相对容易。最棘手的是那些难以复现的问题,比如经常遇到“板子上电后工作正常,但持续带电运行很长时间后才出现异常”的情况。此时,既不能给板子断电,也不能让其复位,因为这会破坏现场环境。假设这个异常并非MCU真的死掉,那么有没有什么方法能知道当前MCU的程序运行到哪里了呢?
下面以一个简单的测试代码为例。我们在代码最后一行故意写了一条会导致进入HardFault的语句。
进入HardFault的原因是0x10000000是一个未映射、受保护或总线不可达的地址,对该地址的写访问会引发总线错误。
进入HardFault后,LED灯停止闪烁。那么,在不引起MCU复位的前提下,如何确认程序确实进入了HardFault呢?
我们对Keil MDK工程先进行如下配置,之后通过调试器SWD接口连接MCU
1)取消勾选“Update Target before Debugging”:不勾选此项,启动调试时将不会自动重新烧写固件,而是直接连接到目标设备,并调试目标上当前已有的固件。
2)取消勾选“Reset after Connect”:不勾选此项,调试器只会以“attach”模式连接到正在运行的固件,不会清除RAM和重置外设寄存器,从而保留程序的运行时状态和外设配置。否则,调试器会发出硬件复位信号,将MCU置为初始状态。
3)取消勾选“Load Application at Startup”:不勾选此项,连接调试器后,程序计数器(PC)将指向目标MCU正在执行的指令地址。否则,PC会指向Reset_Handler,即程序入口。
完成上述配置后,进入Debug模式。此时可以在调试界面看到PC指针指向0x080000DE。
查看项目的map文件,可以发现这个地址对应的是HardFault_Handler函数。
补充说明:两个地址末位相差1是因为ARM Cortex-M内核使用Thumb指令集,函数指针/符号通常带有Thumb标志位(最低位bit0=1)。Map文件或符号表有时会保留此标志位,因此你会看到0x080000DF(即0x080000DE|1)。而Keil调试器显示的是实际的字对齐代码地址(忽略最低位),因此显示为0x080000DE。
通过这种方法,可以辅助我们分析和定位部分MCU异常问题。
451