如何在MCU工程中启用HardFault硬错误中断
文章目录
- 一、HardFault出现场景
- 二、启动HardFault
- 三、C代码示例
一、HardFault出现场景
HardFault(硬故障) 错误中断是 ARM Cortex-M 系列微控制器中一个较为严重的错误中断,一旦触发,表明系统遇到了无法由其他异常处理机制解决的问题。
HardFault 错误中断常见于以下几种触发情况:
-
内存访问错误
- 访问非法地址
当代码尝试访问未映射的内存区域,例如访问一个超出芯片内存范围的地址,就会触发 HardFault。比如,使用了一个未初始化的指针,该指针指向一个随机的无效地址,当对这个地址进行读写操作时,就会产生错误。访问受保护的内存区域也会引发问题。一些内存区域可能被设置为只读或者受特殊权限保护,若代码试图对其进行写操作,就会触发 HardFault。 - 内存对齐错误
ARM Cortex - M 处理器对某些数据类型有内存对齐要求。例如,32 位的数据访问需要在 4 字节对齐的地址上进行。如果代码试图在非对齐的地址上进行 32 位数据的读写操作,就可能触发 HardFault。
- 访问非法地址
-
总线错误
- 总线传输错误
在数据通过总线进行传输时,可能会因为电气干扰、信号衰减、硬件故障等原因,导致数据传输错误。比如,在外部存储器读写操作中,由于总线线路故障,数据无法正确传输,就会触发 HardFault。 - 总线仲裁失败
当多个主设备同时请求使用总线时,需要进行总线仲裁。如果仲裁机制出现问题,导致某个主设备无法正常获得总线使用权,就可能引发总线错误,进而触发 HardFault。
- 总线传输错误
-
堆栈溢出或损坏
- 堆栈溢出
堆栈用于保存函数调用时的局部变量、返回地址等信息。如果函数调用层次过深或者局部变量占用空间过大,就可能导致堆栈溢出。当堆栈溢出发生时,新的数据会覆盖其他重要的内存区域,从而引发 HardFault。 - 堆栈损坏
代码中对堆栈指针的错误操作,例如意外修改了堆栈指针的值,会导致堆栈结构被破坏。后续的函数调用和返回操作就会出现异常,最终触发 HardFault。
- 堆栈溢出
-
异常处理错误
- 异常向量表错误
异常向量表存储了各个异常处理函数的入口地址。如果异常向量表被错误修改,或者其地址设置不正确,当发生异常时,处理器无法正确跳转到相应的异常处理函数,就可能触发 HardFault。 - 异常嵌套错误
在异常处理过程中,如果发生了新的异常,并且异常嵌套处理机制出现问题,例如嵌套层数超过了处理器的限制,就会导致系统混乱,触发 HardFault。
- 异常向量表错误
-
指令执行错误
- 未定义指令
当处理器执行到一条未定义的指令时,会触发 HardFault。这可能是由于代码中包含了无效的机器码,或者是在程序加载过程中出现了错误。 - 非法指令访问
某些指令可能需要特定的权限才能执行。如果代码在不具备相应权限的情况下尝试执行这些指令,就会触发 HardFault。
- 未定义指令
-
硬件故障
- 时钟故障
处理器的正常运行依赖于稳定的时钟信号。如果时钟源出现故障,例如晶振停振、时钟频率偏移过大,会导致处理器的指令执行和数据处理出现异常,从而触发 HardFault。 - 电源故障
电源电压不稳定、过压或欠压等情况,可能会影响处理器内部电路的正常工作,导致数据处理错误,最终引发 HardFault。
- 时钟故障
二、启动HardFault
1. 打开启动文件(.s文件)
在启动文件中,定义中断向量表,为 HardFault 提供占位符处理程序
__Vectors DCD __initial_sp ; 栈顶指针(Top of Stack)
DCD HardFault_Handler ; HardFault 处理程序
__initial_sp
是栈顶指针的初始化值,在复位时由处理器自动加载到 SP
寄存器。
HardFault_Handler
是 HardFault
的中断服务程序,在 CPU 触发 HardFault 时会执行这个函数。
2. 占位符的弱定义
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
EXPORT HardFault_Handler [WEAK]
将 HardFault_Handler 导出,并标记为弱定义(WEAK)。
B .
代表无限循环,相当于死循环,防止处理器进入未知状态。
Tips: 由于该 HardFault_Handler只是一个占位符,用户可以在 C 代码中重新定义 HardFault_Handler,而不会与启动文件冲突。
3. 定义源文件 HardFault 处理程序
在 C 代码中,需要实现 HardFault_Handler
以便进行错误分析和调试。
void HardFault_Handler_t(uint8_t * hardfault_args)
{
......
while(1);
}
在HardFault 中断中一般可以去查看相关寄存器的数值用于分析错误原因,常见寄存器参考如下:
R0 ~ R3:通用寄存器
R12:特殊寄存器
LR(Link Register):返回地址
PC(Program Counter):导致 HardFault 的指令地址
PSR(Program Status Register):状态寄存器
Cortex-M 的故障状态寄存器:
BFAR(Bus Fault Address Register):总线错误的地址
CFSR(Configurable Fault Status Register):配置错误状态寄存器
HFSR(Hard Fault Status Register):硬错误状态寄存器
DFSR(Debug Fault Status Register):调试故障状态寄存器
AFSR(Auxiliary Fault Status Register):辅助故障状态寄存器
4. 汇编代码提取堆栈帧
__asm void hard_fault_handler_asm(void)
{
IMPORT HardFault_Handler_t
TST LR, #4 ; 检查异常发生时使用的堆栈
ITE EQ
MRSEQ R0, MSP ; 如果是主堆栈,获取 MSP
MRSNE R0, PSP ; 如果是进程堆栈,获取 PSP
B HardFault_Handler_t
}
TST LR, #4
:检查 LR(异常返回指针)的 bit 2,判断异常发生时使用的堆栈:
0
:使用主堆栈(MSP)1
:使用进程堆栈(PSP)
ITE EQ
:条件执行语句
MRSEQ R0, MSP
:如果使用 MSP,则将 MSP 传入 R0MRSNE R0, PSP
:如果使用 PSP,则将 PSP 传入 R0
B HardFault_Handler_t
:跳转到 HardFault_Handler_t
,并将 R0
作为参数传递。
综上:
- Cortex-M 发生 HardFault 时,处理器跳转到 HardFault_Handler。
- HardFault_Handler由 startup.s 文件定义,但只是一个占位符。
- hard_fault_handler_asm 通过汇编代码获取当前堆栈指针,并传递给 HardFault_Handler_t。
- HardFault_Handler_t解析故障信息,读取寄存器内容,调试解错。
启动文件 负责 定义中断向量表,并提供一个默认的弱定义 HardFault_Handler,允许用户在 C 代码中覆盖它。
汇编代码 负责 提取故障发生时的堆栈帧 并传递给 C 代码。C 代码 负责 解析寄存器信息并打印故障信息,便于调试和分析故障原因。
三、C代码示例
C代码示例可参考如下:
__asm void hard_fault_handler_asm(void)
{
IMPORT HardFault_Handler_t
TST LR, #4 ; 检查异常发生时使用的堆栈
ITE EQ
MRSEQ R0, MSP ; 如果是主堆栈,获取 MSP
MRSNE R0, PSP ; 如果是进程堆栈,获取 PSP
B HardFault_Handler_t
}
typedef struct
{
uint8_t r0;
uint8_t r12;
uint8_t lr;
uint8_t pc;
uint8_t psr;
} hardfault_t;
hardfault_t hardfault;
void HardFault_Handler_t(unint8_t * hardfault_args)
{
hardfault.stacked_r0 = ((uint32_t) hardfault_args[0]);
hardfault.stacked_r12 = ((uint32_t) hardfault_args[4]);
hardfault.stacked_lr = ((uint32_t) hardfault_args[5]);
hardfault.stacked_pc = ((uint32_t) hardfault_args[6]);
hardfault.stacked_psr = ((uint32_t) hardfault_args[7]);
printf("HardFault_Handler_t!\r\n");
printf("R0 = %x\r\n", hardfault.r0);
printf("R12 = %x\r\n", hardfault.r12);
printf("LR = %x\r\n", hardfault.lr);
printf("PC = %x\r\n", hardfault.pc);
printf("PSR = %x\r\n", hardfault.psr);
while(1);
}