目录
[1. 堆栈溢出](#1. 堆栈溢出)
[2. 指针操作错误](#2. 指针操作错误)
[3. 中断优先级配置错误](#3. 中断优先级配置错误)
[4. 外设错误配置](#4. 外设错误配置)
[5. 存储器管理问题](#5. 存储器管理问题)
[6. 代码逻辑错误](#6. 代码逻辑错误)
[7. 时钟配置错误](#7. 时钟配置错误)
[8. Watchdog 超时](#8. Watchdog 超时)
[1. 电气问题](#1. 电气问题)
[2. 硬件故障](#2. 硬件故障)
[4.Call Stack工具](#4.Call Stack工具)
前言
我们在使用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,每次打开调试这个界面的时候,都感觉有点力不从心,但是随着一步步的不断调试,我逐渐对调试这个界面越来越熟悉,见到的问题也越来越多,只有不断的去遇到问题,然后逐步的解决问题,才能从菜鸟变成大神吧。