《ARM Cortex-M0 权威指南》笔记(10)—错误处理
1. 错误异常概述
Cortex-M0处理器只有一种异常用以处理错误:硬件错误处理(HardFault)。硬件错误处理几乎是最高优先级异常,优先级为-1,只有不可屏蔽中断(NMI)可以对其抢占。当它发生时,也就意味着微控制器出现了问题,并且需要采取修复措施。在软件调试阶段,硬件错误处理也非常有用。如果在硬件错误处理中设置了断点,则在故障发生时,程序将停止执行,通过检查栈内容,可以追踪发生故障的位置,并且尝试着确定本次失败的原因。
对于Cortex-M0,可以将潜在的原因分为两类,如下表:


对于存储器相关错误,总线系统的异常响应可以有以下原因:
访问的地址非法;
由于传输的类型非法,总线的从设备不接受此次传输(从设备决定);
由于传输未使能或初始化,总线的从设备无法进行此次传输(例如,若外设的时钟被关闭,那么访问这个外设时,微控制器就能产生错误响应)
当确定了硬件错误异常的直接原因以后,需要花费一些时间来确定问题的根源。例如,总线错误可以由很多种情况引发,例如:错误的指针操作、栈空间损坏、内存溢出、非法存储器映射以及其他原因。
2. 错误分析
为了给分析提供更多信息,可以生成映像的汇编代码,并且利用在栈帧中找到PC值确定错误的位置。如果错误的位置为存储器访问指令,就应该检查寄存器的值确定存储器访问的地址是否合法。除了检查地址范围,也应该确认存储器的地址是否正确对齐。
除了压入栈中的PC值(返回地址),栈帧中也包含了其它有助于调试的寄存器值,例如:
压入栈的IPSR能够反映处理器是否在进行异常处理;
EPSR则代表处理器状态(EPSR的T位为0,则表示错误由意外却换至ARM状态引起);
栈中的LR也可能提供一些信息,例如:
发生错误的函数的返回地址、错误是否发生在异常处理中;
EXC_RETURN的值是否被异常破坏;
另外,当前的寄存器值也可以提供有助于定位错误原因的各种信息,除了当前栈指针的值,当前的链接寄存器(R14)的值也可能有帮助。如果LR中为非法的EXC_RETURN值,就意味着它在前面异常处理中被错误地修改了。
CONTROL寄存器也可以提供帮助。在没有OS的简单应用中,进程栈指针(PSP)不会被用到,并且CONTROL寄存器会一直保持为0。如果CONTROL寄存器被设置为0x2(PSP用于线程状态),这就意味着LR在之前的异常处理中被错误地修改了,或者栈内容被破坏导致EXC_RETURN的值错误。

关于xPSR,参考博文 《ARM Cortex-M0 权威指南 笔记(2)—体系结构 》
3. 意外切换到ARM
许多可以导致硬件错误的常见错误,都与意外切换至ARM状态相关。一般检查栈中xPSR的值来发现这些错误,如果T位清零,则表示错误由意外切换到ARM状态所致。
意外切换至ARM状态各种原因:


4. 硬件错误处理
由于硬件错误可能是栈指针错误引起,而且C代码的运行可能需要依赖栈存储,用C语言编写的硬件错误处理可能无法正常运行。因此,对于高可靠性的系统来说,理想的硬件错误处理应用应使用汇编语言编写,或者部分汇编用于确认进入C程序前栈指针的合法性。


可以使用嵌入式汇编来实现汇编包装,例如:
__asm void HardFault_Handler(void)
{
MOVS r0, #4
MOV r1, LR
TST r0, r1
BEQ stacking_used_MSP
MRS R0, PSP
B get_LR_and_branch
stacking_used_MSP
MRS R0, MSP
get_LR_and_branch
MOV R1, LR
LDR R2, =__cpp(hard_fault_handler_c)
BX R2
}// C语言实现的硬件错误处理,输入参数为栈帧的位置和LR的值
// hardfault_args存的是栈的值,lr_value是LR的值
#include <stdio.h>
void hard_fault_handler_c(unsigned int * hardfault_args, unsigned int lr_value)
{
unsigned int stacked_r0;
unsigned int stacked_r1;
unsigned int stacked_r2;
unsigned int stacked_r3;
unsigned int stacked_r12;
unsigned int stacked_lr;
unsigned int stacked_pc;
unsigned int stacked_psr;
// 按照入栈顺序赋值
stacked_r0 = ((unsigned long) hardfault_args[0]);
stacked_r1 = ((unsigned long) hardfault_args[1]);
stacked_r2 = ((unsigned long) hardfault_args[2]);
stacked_r3 = ((unsigned long) hardfault_args[3]);
stacked_r12 = ((unsigned long) hardfault_args[4]);
stacked_lr = ((unsigned long) hardfault_args[5]);
stacked_pc = ((unsigned long) hardfault_args[6]);
stacked_psr = ((unsigned long) hardfault_args[7]);
// 串口输出
printf ("[HardFault handler]\r\n");
printf (" R0 = %x\r\n", stacked_r0);
printf (" R1 = %x\r\n", stacked_r1);
printf (" R2 = %x\r\n", stacked_r2);
printf (" R3 = %x\r\n", stacked_r3);
printf (" R12 = %x\r\n", stacked_r12);
printf (" Stacked LR = %x\r\n", stacked_lr);
printf (" Stacked PC = %x\r\n", stacked_pc);
printf (" Stacked PSR = %x\r\n", stacked_psr);
printf (" Current LR = %x\r\n", lr_value);
while(1);
}测试HardFault被触发,触发代码:
U32 *p_test = NULL; *p_test = 0x12345678;
串口打印输出:

