STM32学习笔记11——HardFault_Handler处理方法

本文探讨了STM32微控制器中HardFault_Handler故障的两大常见原因:内存溢出和堆栈溢出。提供了详细的排查步骤,包括检查LR寄存器以确定使用的是MSP还是PSP堆栈,以及如何通过分析堆栈内容定位故障点。

根据网络资料及自己调试经验总结如下:

STM32 出现 HardFault_Handler 故障的原因主要有两个方面:
1、内存溢出或者访问越界。这个需要自己写程序的时候规范代码,遇到了需要慢慢排查。
2、堆栈溢出。增加堆栈的大小。

排查方法:

发生异常之后可首先查看 LR 寄存器中的值,确定进入异常前一刻使用的堆栈为 MSP 或 PSP,然后找到相应堆栈的指针?
注:在 HardFault_Handler(void)中断里第一条语句打断点,进入中断后,查看 LR 寄存器的值,如果是 0XFFFFFFF9,那么中断前使用的是 MSP,如果是 0XFFFFFFFD,那么中断前使用的是 PSP;
在这里插入图片描述

根据找到的堆栈指针, 在内存中查看相应堆栈里的内容。由于异常发生时,内核将 R0~R3、 R12、 LR、 PC、 XPRS 寄存器依次入栈,其中 LR 即为发生异常前 PC 将要执行的下一条指令地址。
在这里插入图片描述

中断/异常的响应序列

**当 CM3 开始响应一个中断时,会在它看不见的体内奔涌起三股暗流:

  1. 入栈: 把 8 个寄存器的值压入栈。
  2. 取向量:从向量表中找出对应的服务程序入口地址。
  3. 选择堆栈指针 MSP/PSP,更新堆栈指针 SP,更新连接寄存器 LR,更新程序计数器 PC。**

入栈

响应异常的第一个行动,就是自动保存现场的必要部分:依次把 xPSR, PC, LR, R12 以及 R3‐R0 由硬件自动压入适当的堆栈中:如果当响应异常时,
当前的代码正在使用 PSP,则压入 PSP,即使用线程堆栈;否则压入 MSP,使用主堆栈。一旦进入了服务例程,就将一直使用主堆栈。
假设入栈开始时, SP 的值为 N,则在入栈后,堆栈内部的变化如表 9.1 表示。又因为 AHB 接口上的流水线操作本质,地址和数据都在经过一个流水线周
期之后才进入。另外,这种入栈在机器的内部, 并不是严格按堆栈操作的顺序的——但是机器会保证: 正确的寄存器将被保存到正确的位置, 如表 9.1所示。

在这里插入图片描述
测试: 在程序中调用如下函数,会进入异常故障中断。

void StackFlow(void)
{
int a[3],i;
for(i=0; i<10000; i++)
{
a[i]=1;
}
}

DEBUG 如下图
在这里插入图片描述
SP 值为 0x20008d88,查看堆栈里面的值依次为 R0~R3、 R12、 LR、 PC、 XPRS, 例如 R0(00001C97), 根据表 9.1 里 LR 的位置, 得到地址 0x0800087D 即为异
常前 PC 将要执行的下一条指令地址(即 StackFlow()后面的语句处 RTC_Init().

另一种方法:

默认的 HardFault_Handler 处理方法不是死循环么? 将它改成 BX LR 直接返回的形式。然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句那儿。

__asm void wait()
{
BX lr //BX 无条件转移指令,返回到发生错误的后面代码中
}
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
wait();
}
在Keil环境下开发STM32项目时,遇到`HardFault_Handler`是一种常见的硬件异常,通常表示程序执行过程中发生了不可恢复的错误,例如非法指令、内存访问冲突、堆栈溢出或未对齐的内存访问等。处理这类错误需要系统性地排查代码和硬件配置。 ### 调试步骤与解决方案 #### 1. 启用调试器并查看堆栈信息 使用Keil的调试功能,配合ST-Link或其他调试器,进入`HardFault_Handler`中断服务程序后,查看调用堆栈和寄存器状态。特别是`PC`(程序计数器)和`LR`(链接寄存器)的值,可以帮助定位发生错误的代码位置。 #### 2. 检查内存访问问题 确保所有指针访问都指向有效的内存区域,避免访问未初始化的指针或越界访问数组。例如,以下代码可能导致HardFault: ```c int *ptr = NULL; *ptr = 10; // 非法内存写入,触发HardFault ``` #### 3. 配置默认异常处理 在启动文件(如`startup_stm32f103xb.s`)中,可以修改`HardFault_Handler`的实现,添加调试输出或断点,以便更早地捕获错误源头。例如: ```c void HardFault_Handler(void) { __disable_irq(); while (1) { // 添加调试输出或断点 } } ``` #### 4. 使用Fault异常寄存器分析 STM32的Cortex-M内核提供了多个故障状态寄存器,如`HFSR`(HardFault Status Register)、`CFSR`(Configurable Fault Status Register)和`MMAR`(MemManage Fault Address Register)等。通过读取这些寄存器的值,可以更精确地判断错误类型。例如: ```c #include "core_cm3.h" void HardFault_Handler(void) { uint32_t hfsr = SCB->HFSR; uint32_t cfsr = SCB->CFSR; uint32_t mmar = SCB->MMAR; // 打印寄存器值用于分析 while (1); } ``` #### 5. 检查堆栈溢出 如果程序中存在递归调用或局部变量占用过多栈空间,可能导致堆栈溢出。检查`stack`段的大小配置,确保堆栈足够大。可以在链接脚本或启动文件中调整堆栈大小。 #### 6. 检查中断配置 确保所有中断服务程序正确实现,且未发生中断嵌套冲突。例如,未正确配置NVIC优先级可能导致高优先级中断打断低优先级中断,从而引发异常。 #### 7. 使用静态代码分析工具 Keil MDK提供了静态代码分析工具,可以在编译阶段发现潜在的代码问题,如未初始化变量、指针越界等。 #### 8. 启用看门狗复位 在某些情况下,可以启用看门狗定时器,当程序卡死在`HardFault_Handler`时自动复位系统,提高系统的健壮性。 --- ### 示例代码:增强的HardFault_Handler 以下是一个增强版的`HardFault_Handler`实现,包含基本的寄存器打印功能,有助于调试: ```c #include "core_cm3.h" #include <stdio.h> void HardFault_Handler(void) { uint32_t hfsr = SCB->HFSR; uint32_t cfsr = SCB->CFSR; uint32_t mmar = SCB->MMAR; uint32_t bfar = SCB->BFAR; uint32_t lr = 0, pc = 0; // 获取当前PC和LR值 asm volatile ("mov %0, lr" : "=r" (lr)); asm volatile ("mov %0, pc" : "=r" (pc)); printf("HardFault occurred!\n"); printf("HFSR: 0x%08X\n", hfsr); printf("CFSR: 0x%08X\n", cfsr); printf("MMAR: 0x%08X\n", mmar); printf("BFAR: 0x%08X\n", bfar); printf("LR: 0x%08X\n", lr); printf("PC: 0x%08X\n", pc); while (1); } ``` --- ### 常见错误类型与排查建议 - **MemManage Fault**:通常与内存访问错误有关,如访问非法地址或未对齐访问。检查指针操作和数组边界。 - **BusFault**:可能由指令或数据访问失败引起,常见于外部存储器访问错误或中断向量表配置不当。 - **UsageFault**:多由未定义指令、除零操作或未对齐访问引起,检查代码逻辑和编译器优化设置。 --- ### 总结 处理`HardFault_Handler`的关键在于结合调试工具、寄存器分析和代码审查,逐步缩小问题范围。通过启用详细的异常信息打印和合理配置堆栈、中断优先级,可以有效提升调试效率。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值