1. 错误异常概述

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

对于Cortex-M0,可以将潜在的原因分为两类,如下表:

错误异常分类错误异常分类.png

对于存储器相关错误,总线系统的异常响应可以有以下原因:

  • 访问的地址非法;

  • 由于传输的类型非法,总线的从设备不接受此次传输(从设备决定);

  • 由于传输未使能或初始化,总线的从设备无法进行此次传输(例如,若外设的时钟被关闭,那么访问这个外设时,微控制器就能产生错误响应)

当确定了硬件错误异常的直接原因以后,需要花费一些时间来确定问题的根源。例如,总线错误可以由很多种情况引发,例如:错误的指针操作、栈空间损坏、内存溢出、非法存储器映射以及其他原因。

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的值错误。

定位错误流程.png

关于xPSR,参考博文  《ARM Cortex-M0 权威指南 笔记(2)—体系结构 》

3. 意外切换到ARM

许多可以导致硬件错误的常见错误,都与意外切换至ARM状态相关。一般检查栈中xPSR的值来发现这些错误,如果T位清零,则表示错误由意外切换到ARM状态所致。

意外切换至ARM状态各种原因:

意外切换至ARM状态各种原因意外切换至ARM状态各种原因.png

4. 硬件错误处理

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

硬件错误的汇编报告流程硬件错误的汇编报告流程.png

可以使用嵌入式汇编来实现汇编包装,例如:

__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;

串口打印输出:

HardFault串口打印输出.png

注意:本站所有文章除特别说明外,均为原创,转载请务必以超链接方式并注明作者出处。 标签:ARM,Cortex-M0,Cortex-M0错误处理