STM32卡死、跑飞、进入HardFault_Handler如何精准的确定问题

目录

前言

一、程序跑飞原因

软件原因

[1. 堆栈溢出](#1. 堆栈溢出)

[2. 指针操作错误](#2. 指针操作错误)

[3. 中断优先级配置错误](#3. 中断优先级配置错误)

[4. 外设错误配置](#4. 外设错误配置)

[5. 存储器管理问题](#5. 存储器管理问题)

[6. 代码逻辑错误](#6. 代码逻辑错误)

[7. 时钟配置错误](#7. 时钟配置错误)

[8. Watchdog 超时](#8. Watchdog 超时)

硬件原因

[1. 电气问题](#1. 电气问题)

[2. 硬件故障](#2. 硬件故障)

软件复位与硬件问题结合

二、调试工具

如何进入仿真?

进入仿真

1.Registers工具

2.Memory工具

3.Disassembly工具

[4.Call Stack工具](#4.Call Stack工具)

5.Watch窗口工具

三、找到程序跑飞位置(HardFault_Handler)

1.为什么会产生HardFault_Handler

2.出现HardFault_Handler怎么办

我遇到的HardFault_Handler

总结


前言

我们在使用STM32的时候,代码难免会出现疏忽,导致程序跑飞,不再正常运行,那么都是什么情况会导致STM32程序跑飞呢?或者我们调试的时候,发现代码进入了HardFault_Handler();导致了死循环,我们该如何去找到问题呢?

一、程序跑飞原因

软件原因

软件导致STM32跑飞(程序失控或死机)的原因有多种,主要包括以下几个方面:

STM32程序"跑飞"通常是指程序跳转到了意外的内存区域,导致异常行为或无法正常工作。这种现象可能由多种原因引起,以下是常见的原因及分析:

1. 堆栈溢出

  • 原因

    1.递归函数没有正确退出,导致栈空间耗尽。

    2.动态分配的内存过多,超出了系统栈的大小。

    3.中断嵌套过深,占用过多的栈空间。

  • 解决方法

    1.检查栈的使用情况,确保分配足够的栈空间(在启动文件 .ld.s 中设置)。

    2.使用调试器监控栈指针的位置,确认栈未超出分配范围。

    3.避免递归或深度嵌套的函数调用。

2. 指针操作错误

  • 原因

    1.空指针(NULL)被解引用。

    2.未初始化的指针或悬空指针(指向已释放的内存)。

    3.指针越界访问内存。

  • 解决方法

    1.确保所有指针都被正确初始化。

    2.检查数组和指针操作是否超出范围。

    3.使用静态分析工具(如 PC-Lint)查找潜在的指针错误。

3. 中断优先级配置错误

  • 原因

    1.中断优先级配置冲突(尤其是 FreeRTOS 等系统对优先级有要求)。

    2.高优先级中断执行时间过长,导致系统其他部分无法正常运行。

  • 解决方法

    1.确保中断优先级分配合理,并遵循 ARM Cortex-M 的优先级规则(例如,高优先级数值小)。

    2.在中断中执行时间敏感或关键任务,避免复杂逻辑。

4. 外设错误配置

  • 原因

    1.外设初始化错误(如定时器、DMA、USART 等)。

    2.外设中断未正确配置,导致程序跳转到错误的地址。

  • 解决方法

    1.检查所有外设的初始化代码,确保参数正确。

    2.确保外设的中断处理程序已正确设置。

5. 存储器管理问题

  • 原因

    1.动态内存分配失败(malloc 返回 NULL)。

    2.超出 SRAM 的限制,访问非法地址。

    3.堆栈和堆重叠。

  • 解决方法

    1.避免在嵌入式系统中频繁使用动态内存分配。

    2.检查链接脚本,确保堆和栈区域不重叠。

    3.使用静态分配代替动态分配。

6. 代码逻辑错误

  • 原因

    1.无意的无限循环。

    2.缺少对边界条件的处理。

    3.在某些异常路径中,没有返回合适的地址。

  • 解决方法

    1.使用单步调试工具检查代码逻辑。

    2.在代码中加入断点或日志输出,验证实际运行流程。

7. 时钟配置错误

  • 原因

    1.系统时钟未正确配置,导致外设或 CPU 的行为异常。

    2.时钟频率超出芯片的允许范围。

  • 解决方法

    1.使用 STM32CubeMX 或数据手册验证时钟配置。

    2.确保主时钟频率(HCLK)、外设时钟(PCLK1/PCLK2)等都在芯片允许范围内。

8. Watchdog 超时

  • 原因

    1.程序未及时喂狗,导致复位或跳转。

  • 解决方法

    2.检查喂狗逻辑,确保在程序关键路径中正常运行。

硬件原因

1. 电气问题

  • 原因

    1.电源不稳定或电压过低,导致 CPU 或外设复位。

    2.电磁干扰(EMI)导致程序异常。

  • 解决方法

    1.使用示波器监测电源电压的稳定性。

    2.在 PCB 设计中加强抗干扰能力(如加电容、屏蔽、合理布线)。

2. 硬件故障

  • 原因

    1.芯片或外部存储器损坏。

    2.电气连接问题(如虚焊或断线)。

    3.外部晶振无法起振。

  • 解决方法

    1.检查硬件连接,确保引脚焊接可靠。

    2.在相同代码运行于其他硬件平台上验证是否正常。

    3.时钟采用芯片的内部时钟。

软件复位与硬件问题结合

  • 原因

    1.软件错误触发硬件复位。

    2.电压问题导致复位。

  • 解决方法

    在调试时检查复位原因寄存器(如 RCC_CSR),区分是看门狗复位、掉电复位,还是其他问题。

二、调试工具

我们要发现程序在哪跑飞了,肯定是要借助调试工具了,我们用的是keil5,相信大家已经对他非常非常熟悉了,我们接下来讲解如何使用keil5进行仿真调试,找出我们程序跑死的问题。

如何进入仿真?

我们插好我们的仿真器,常见的STM32仿真器有很多种,有STLINK或者DAP等等,我这里使用的是STLINK_V2这款仿真器,只能说便宜又好用了。插好仿真器,点击魔术棒->Debug->Setting,如果SW Device下面显示了芯片的ID,说明我们成功用仿真器连接到芯片。

进入仿真

我们成功进入了仿真界面,接下来我开始给大家介绍一下这里面的各个功能和工具。

1.Registers工具

我们可以在这里可以查看CPU寄存器的值。

(1)通用寄存器(R0~R12)

R0~R7:这些是低组寄存器,所有指令都可以访问。它们的大小为32位,复位后的初始值不定。 R8~R12:这些是高组寄存器,只有部分的16位Thumb指令可以访问,而32位Thumb-2指令则不受限制。它们的大小同样为32位,复位后的初始值也不定。 (2)特殊功能寄存器

堆栈指针(SP):也称为R13,在Cortex-M4内核中,有两个堆栈指针------主堆栈指针(MSP)进程堆栈指针(PSP) 。MSP用于异常服务例程和需要特权访问的应用程序代码,而PSP则用于常规的应用代码。 连接寄存器(LR):即R14,主要作用是保存子程序的返回地址,以便在执行完子程序时恢复现场。 程序计数器(PC):即R15,用于指示当前执行的指令地址。在Cortex-M系列中,由于采用指令流水线技术,读取PC会返回当前指令地址+4(以兼容Thumb代码)。 (3)程序状态寄存器(xPSR)

xPSR是程序状态寄存器,它又被分为三个子状态寄存器:

应用程序状态寄存器(APSR) 中断状态寄存器(IPSR) PRIMASK:只有1个位,置1时关闭所有可屏蔽的异常。 FAULTMASK:只有1个位,置1时只有NMI(非屏蔽中断)可以响应。 BASEPRI:8位寄存器,定义了被屏蔽优先级的阈值。 (4)控制寄存器(Control)

控制寄存器用于控制FPU(浮点单元)的激活、堆栈指针的选择以及线程模式的特权级等。

2.Memory工具

Memory Window是Keil调试环境中的一个窗口,它提供了对程序内存的直接访问。通过这个窗口,你可以查看内存中的字节、字、双字等数据,并可以实时修改这些数据以测试程序的行为。这对于调试和验证程序中的内存访问、堆栈使用、变量存储等问题非常有帮助。

3.Disassembly工具

在Disassembly窗口中,你可以看到程序的反汇编代码。这些代码是程序在CPU上实际执行的指令的文本表示。你可以滚动窗口来查看不同的部分,或者使用窗口中的搜索功能来定位特定的代码或地址。

4.Call Stack工具

Call Stack(调用堆栈)界面是一个关键的调试工具,它允许开发者查看程序执行过程中函数调用的顺序和当前的位置。Call Stack窗口将显示当前函数调用的堆栈。这包括每个函数的调用顺序、每个函数的名称(如果可用)以及调用该函数的地址。你可以看到程序是如何从main函数开始,逐步调用其他函数,直到达到当前执行点的。

5.Watch窗口工具

在keil5调试中,Watch窗口是一个非常重要的工具,用于实时监控程序中的变量、寄存器或表达式的值。

三、找到程序跑飞位置(HardFault_Handler)

1.为什么会产生HardFault_Handler

1.由调试事件触发 2.由总线错误,存储器管理错误或使用错误而产生 这个错误的产生是由于HardFault寄存器状态发生改变所导致,这两个原因看上去说的很玄乎,实际上这个问题大家应该都不陌生,最常出现这个问题的原因一般是数据越界,堆栈溢出,等等。

2.出现HardFault_Handler怎么办

遇到之后没有必要手忙脚乱,这个问题其实并不难解决,因为HardFault_Handler的存在意义并不是为了让你的程序卡死,而是为了帮助你解决程序的问题,可以按照以下步骤进行:

1.找到Registers界面

2.然后查看LR寄存器的值,该寄存器只有六种值是正常情况,具体可参考M4权威指南193页。这里给出一个表格:

需要注意的是,进入HardFault_Handler错误处理的原因并不是LR寄存器导致的,而是HardFault寄存器导致的,LR只是在发生错误前进行现场保存而已。LR寄存器只是我们查询问题所在的"指路器"。 当问题是在中断处理导致的时候,LR寄存器的值为0xFFFFFFF9或者0xFFFFFFE9时(也就是上述表格的前两种情况),问题是在由中断事件到中断处理的过程中产生的,而在值为0xFFFFFFF1或者0xFFFFFFE1 的时候,问题是在中断处理被另一中断打断时才发生的,所以才会认为LR的值为0xFFFFFFF1或者0xFFFFFFE1 的时候一定是发生了中断嵌套。我们可以查看一下Cortex M3与M4权威指南。

3.根据LR的寄存器的值判断是主栈还是线程栈导致的问题,,如果是主栈就继续查看MSP寄存器,如果是进程栈,那么就查看PSP寄存器。

4.根据MSP寄存器或者PSP寄存器的记录,将其值在Memory中查看其具体地址,一般是0x80开头的。

5.打开Disassembly,在里面可以找到具体是哪一行代码导致的该问题。

们可以成功找到我们跑飞前代码地方了。

我遇到的HardFault_Handler

我这里的问题很简单,同时也是我故意设置的一个堆栈溢出的问题,这里我带大家分析一下:

首先,我们是走到一条叫menuPoint[selectItem+scrollBar].func1()来去执行我们想执行的函数,我们通过Watch窗口查看一下:

我本意里面的func1是应该指向一个叫amplitudeSub的函数指针的,这里指向不正确,于是我就非常怀疑这个结构体赋值给这个成员有一个环节出了问题,于是我就一步步的单步指向,知道我执行到了一个地方:

在这之前,结构体里面的值都是我理想的值,当我执行完下一条指令的时候,错误出现了。

这里的func1的值居然给我离奇的改变了,因为他的改变,导致我后续程序的跑飞。我们仔细看看执行的那条代码,发现是数组溢出了。

结构体里面的数组大小是15,我这里已经超过了15,所以问题找到了,我们进行修正,继续执行,发现程序没有出现跑死了。

总结

调试我只能说非常的考验自己,入门刚刚好一年STM32,每次打开调试这个界面的时候,都感觉有点力不从心,但是随着一步步的不断调试,我逐渐对调试这个界面越来越熟悉,见到的问题也越来越多,只有不断的去遇到问题,然后逐步的解决问题,才能从菜鸟变成大神吧。

相关推荐
Flyaswing1 小时前
STM32坑分享——擦写单片机内部Flash时影响串口通信
stm32·单片机·嵌入式硬件
跟着杰哥学嵌入式1 小时前
单片机上电后程序不运行怎么排查问题?
单片机·嵌入式硬件
上海易硅智能科技局有限公司1 小时前
mcu+cpld 联合编程(概念及流程)
单片机·嵌入式硬件·mcu·agm芯片
夜间看海1 小时前
52 基于单片机的超声波、温湿度、光照检测分阶段报警
数据库·单片机·mongodb
Flyaswing2 小时前
STM32坑分享——之SPI读写外部Flash引发HardWare Fault
stm32·单片机·嵌入式硬件
lbaihao2 小时前
LVGL学习资料
python·stm32·单片机
云山工作室2 小时前
基于单片机的太阳能数据采集系统(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
aiamia2 小时前
CAN配置---波特率中断引脚等---autochips-AC7811-ARM-M3内核
arm开发·单片机·mcu·车载系统·汽车
腾飞的信仰3 小时前
51单片机-内部扩展RAM的应用
单片机·嵌入式硬件·51单片机
驯龙高手_追风3 小时前
ARM32位MCU开发板调试经验总结
arm开发·单片机·嵌入式硬件