这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【原创】STM32调试手记:HardFault排查思路整理----FROM婉

共1条 1/1 1 跳转至页

【原创】STM32调试手记:HardFault排查思路整理----FROM婉

工程师
2026-10-19 18:07:35     打赏

做嵌入式开发,HardFault几乎是每个人都会遇到的问题。第一次遇到的时候基本都懵,因为程序直接停了,连断点都打不上。今天把我在STM32上排查HardFault的经验整理一下,都是实打实踩坑踩出来的。

一、HardFault是什么

简单来说就是CPU遇到了”不知道怎么处理”的异常。比如访问了非法地址、除零、对齐错误、栈溢出等等。STM32的异常优先级里HardFault属于最低优先级(-2),但它一旦触发基本就意味着程序跑飞了。

二、先分清是哪种Fault

STM32有好几种Fault,触发后要先看CFSR(Configurable Fault Status Register)和HFSR(HardFault Status Register)寄存器来判断类型:

void HardFault_Handler(void)
{
    uint32_t cfsr = SCB->CFSR;
    uint32_t hfsr = SCB->HFSR;
    uint32_t mmfar = SCB->MMFAR;
    uint32_t bfar = SCB->BFAR;

    // CFSR各字段含义:
    // [7:0]   - ICPUTTERR: Instruction cache tag parity error
    // [15:8]  - IACCVIOL:  Instruction access violation
    // [23:16] - IARERR:    Instruction fetch exception
    // [31:24] - CPACERR:   Coprocessor access error

    // [31:24] - USTKERR: Underflow stack fault (MSP)
    // [23:16] - USGERR:  Usage fault (div by zero, unaligned, etc)
    // [15:8]  - UNSTKERR: Unstacking fault (MSP)
    // [7:0]   - STKERR:    Stacking fault (MSP)

    // HFSR:
    // [31]    - FORCED: Force HardFault (比如NMI被配置成HardFault)
    // [30]    - VECTTBL: Vector table read fault

    while(1)
    {
        // 死循环,用调试器查看寄存器
    }
}

三、最常见的原因和排查方法

1. 栈溢出(Stack Overflow)

这是我最常遇到的。STM32的栈是从高地址往低地址增长的,如果栈空间不够用就会覆盖掉全局变量甚至代码区。

排查方法:

• 在Keil里设置Stack Size,然后跑程序看会不会进HardFault

• 用Magic Number法:在栈初始化的时候用特定值填充栈空间,程序跑一会儿后检查有多少值被覆盖了

• 看MSP寄存器的值,如果MSP已经小于你设置的栈底地址,那就是栈溢出了

2. 访问空指针或非法地址

int *p = NULL;
*p = 10;  // 直接HardFault

这种问题在调试器里可以直接定位到出错行。但有时候指针被意外修改了,指向了随机地址,就比较难找。

3. 未对齐访问

STM32F1的某些外设寄存器要求4字节对齐访问。比如:

typedef struct {
    uint8_t a;
    uint32_t b;  // 这个成员可能未对齐
} MyStruct;

MyStruct s;
s.b = 10;  // 如果s的地址不是4字节对齐,可能触发UsageFault

解决:用__attribute__((packed))或者调整结构体成员顺序。

4. 中断里操作了非原子变量

这个坑比较隐蔽。比如一个全局变量在中断和主循环里都被访问,但没有做原子保护:

volatile uint32_t counter = 0;

void TIM2_IRQHandler(void)
{
    counter++;  // 32位变量在Cortex-M3上是原子操作(一条LDR/STR指令)
}

其实32位变量在M3上是原子的(一条指令搞定),但64位变量或者结构体就不是了。这个要看具体情况。

四、一个实用的调试技巧

在HardFault_Handler里不要直接while(1)死循环,把出错时的寄存器现场保存下来,这样用调试器连接上去可以看到程序停在哪一行:

void HardFault_Handler(void)
{
    // 保存出错时的上下文
    hardfault_context.psr     = __get_PSR();
    hardfault_context.pc      = __get_PC();
    hardfault_context.lr      = __get_LR();
    hardfault_context.r0      = __get_R0();
    hardfault_context.r1      = __get_R1();
    hardfault_context.r2      = __get_R2();
    hardfault_context.r3      = __get_R3();
    hardfault_context.r12     = __get_R12();
    hardfault_context.sp      = __get_MSP();

    // 用串口把信息发出去,或者存在Flash里
    // 然后用J-Link连接,查看hardfault_context结构体
    // 从pc值就能定位到出错的源代码行

    while(1);
}

五、几个经验之谈

1. 调试HardFault的时候,先把Keil的Options->Debug里的”Use: J-Link Debugger”选对,然后在Options->Debugger里勾选”Flash”和”Verify”,这样能看到Flash编程是否成功。

2. 如果你用了FreeRTOS之类的操作系统,HardFault_Handler要改成FreeRTOS的宏定义形式:void HardFault_HandlerC(void),不然会递归中断把自己搞死。

3. 有时候HardFault不是你的代码问题,是启动文件里中断向量表没排好。特别是用了多个中断的时候,检查一下vector table的offset对不对。

4. 最后一条:别怕HardFault。每次排查出一个HardFault,你对STM32的理解就加深一层。我当年被HardFault折磨了半年,现在看到HardFault反而觉得亲切了。



共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]