在调试程序时,有时Keil会出现报错弹窗“Target is lockup”,我们就没办法再对MCU进行下载、仿真等操作,这个就有可能是处理器处在锁定状态,今天我们将一起分析MCU为何会出现“lockup”的情况。
1. lockup
如果在HardFault处理期间又发生了另外的一个错误中断或者NMI中断处理期间发生了一个错误中断,则处理器就会进入到锁定状态。这是因为这两个异常处理执行期间,优先级不允许HardFault错误处理抢占。
在锁定状态时,寄存器和存储器都被“冻结”,PC的值被强制为0xFFFF\_FFFx,并且一直循环在取指。与此同时,Cortex-M0的另一条名叫“lockup”的输出信号线将被置为有效,芯片厂商可以检测此信号,并且在系统复位发生器上触发一个复位。
在锁定状态下,处理器停止执行指令并确认锁定状态信号。根据MCU设计不同,“lockup”信号可以被设置为自动复位系统,而不是等看门狗定时溢出后再复位系统,也可以在电机电源等控制应用中,关闭PWM输出,防止MCU在锁定状态期间MOS一直处在导通状态而造成损坏。
锁定状态能够防止失败程序破坏存储器或者外设中的更多数据,有效的保护现场,方便分析定位问题。在软件开发过程中,由于存储器内容中可能包含了关于软件失败的重要信息,也有助于调试定位问题。
2. lockup的原因
许多的情况都有可能导致Cortex-M0处理器被锁定:
在NMI服务中产生错误,出现两个异常错误
在HardFault服务中产生错误(双重faults)
在NMI处理或者HardFault处理时包含了SVC操作
在复位序列(初始的MSP与PC读取)中产生总线错误响应
异常返回期间,使用了主栈指针(MSP)进行xPSR出栈时,产生了总线错误
除了出现错误的情况,在NMI或者HardFault处理中使用SVC也会导致锁定,这是因为SVC优先级总是比这些异常的要低,因此会被阻止。由于程序错误不能被HardFault异常处理,所以系统进入到锁定状态。
锁定状态也可以由复位期间的总线系统错误引发。
如果在操作Flash期间,处理器产生了总线错误,这就意味着处理器无法确认栈指针的初始值或者无法确认复位向量。在这些情况下,处理器无法进行正常的操作,必须进入到锁定状态。
如果在异常处理时产生了总线错误响应,即使进入的时HardFault或者NMI,入口不会引发锁定,不过一旦进入HardFault或者NMI异常处理后,总线错误响应就会引发锁定。因此,对于高可靠性的系统,由于C编译器可能会在处理代码的开始添加栈操作,所以HardFault处理程序最好不要用C语言实现:
HardFault_Handler
push {R4,R5} ;如果MSP被破坏,该指令会引起锁死
在异常退出且使用MSP的xPSR出栈期间,总线错误响应则可能会引起锁定。在这种情况下,由于xPSR无法确定,因此处理器无法获取到系统的正确优先级。因此系统就处于了锁定状态,并且除了复位或者暂停调试外无法将系统复位。
但是在调试阶段也许还能让系统起死回生:如果连接了调试器,则可以喊停(halt)处理器,然后手工修改PC的值。然而这也往往是无力的:因为上下文没有了——所有的寄存器,以及中断系统,都已经物是人非,需要重新初始化,才能返回到正常的操作中。
如果当场就复位了,则所有寄存器的值都归位了,不再有机会去查明当时的情况。
如果不是关键系统,则可以使用一个看门狗复位,它可以使系统从锁定状态中复位。
还要注意的是,如果在响应NMI或HardFault的入栈/出栈阶段触发了总线fault,则不会导致锁定,只是会悬起总线fault。
3. 引发的问题
如果锁定是由双重错误引起的,系统的优先级就会保持在-1。如果此时发生了NMI异常,则NMI就可能会抢占并开始执行。NMI中断完成后,异常处理就会终止,而后系统就会返回到锁定状态。
在其他的锁定状态下,除了复位或者连接的调试器重建之外,系统就不能恢复了。通过复位MCU的配置,MCU研发人员就可以利用“lockup”信号来复位系统。
如果在执行 NMI 或HardFault 处理程序,或者如果系统在取消堆叠 PSR 时产生总线错误使用 MSP 的异常返回。当处理器处于锁定状态时,它不会执行任何指令。处理器保持锁定状态,直到以下任一状态发生以下情况:
- 它被重置,MCU出现断电或复位
- 调试器停止它
- 发生NMI 并且当前锁定在HardFault 处理程序中
如果 NMI 处理程序中出现锁定状态,则后续 NMI 不会导致处理器离开锁定状态。
4. 避免被锁定
对于工程师来说,锁定或者HardFault都是需要避免的,然而许多种原因都有可能导致嵌入式系统错误,而锁定和HardFault则有助于避免问题更加严重。
许多错误或问题都可能导致MCU崩溃,例如:
- 供电不稳定或者电磁干扰
- Flash存储器损坏或者Flash进行了不规范的擦除、写入等动作
- 外部接口信号错误
- 操作环境或者自然老化过程导致的器件损坏
- 错误的时钟或者时钟信号弱
- 软件错误
锁定或者HardFault有助于调试或者错误检测,尽管无法完全避免上述的问题,但是可以采取各种软件错误来提高嵌入式系统的可靠性。
首先,应使得NMI异常处理和HardFault异常处理尽可能地简单。如果有些任务与NMI异常或HardFault异常相关,可以在这些异常中执行紧急的部分,其他的部分则在PendSV之类的异常中处理。通过简化NMI和HardFault错误异常,也可以降低在这些异常中意外使用SVC带来的风险。
其次,对于安全性要求较高的应用,可以在进入C语言的HardFault错误处理之前,利用汇编包装检查SP的值。
如果有必要,可以利用汇编实现整个HardFault错误处理。这样可以避免一些栈存储的访问,从而避免由于栈指针被破坏而指向非法存储器地址引起的锁定。
如果NMI处理简单,也可以用汇编实现NMI处理,并且只使用R0到R3以及R12,由于这些寄存器已经被压栈,这样就能避免栈存储访问。不过多数情况下,栈指针错误很快就会引发HardFault错误异常,所以通过C语言实现NMI时也无需担心。
方法一:
__asm void wait()
{ BX lr //BX 无条件转移指令,返回到发生错误的后面代码中
}
void HardFault(void)
{ /* Go to infinite loop when HardFault exception occurs */
wait();
}
方法二:
void HardFault(void)
{ NVIC_SystemReset();
// or close TIMx and other peripherals
}
5. 检查流程及解决方法
1)如果目标板没有上电,仅靠调试器管脚上的一点电流在运行。这种情况下可能电流不够,导致下载运行的程序根本就不对,也会出现锁定状态。
2)下载烧录建议使用5线制(VDD \ SWDIO \ SWCLK \ NRST \ GND),在一些异常的情况下,下载仿真器会通过NSRT复位MCU之后Hold住处理器。
3)如果有接上MM32-LINK下载仿真并且没有接NRST引脚,如果有勾选方框的内容也会出现锁定状态,建议客户在没有接NRST引脚时,不要勾选以下内容。
4)如果处理器出现一次锁定状态,建议对目标板重新上电,并且重新拔插仿真器,查看是否能够解决,该方法针对只出现一次锁定状态的情况。
5)如果一直循环出现锁定状态,并且没办法再次下载,建议从SRAM启动[BOOT0拉高,MCU上电,使用相关工具(Keil、MM32-LINK Program等上位机]擦除芯片,BOOT0拉低重新上电)擦除芯片里面的程序,然后具体分析程序中什么原因造成上述问题。
出现锁定状态大多数是由于程序编程错误或者外设操作不规范造成的,在调试阶段需要尽量保护现场,可以将处理器的状态信息通过UART等可视化输出log信息,根据输出处理器的状态信息进行分析,在量产程序中在异常中断内进行复位操作或者无条件返回的手段是为了增加软件的健壮性,对异常错误进行预防,实现方式需要结合具体的案例。