目录
1. 概述
2. 问题描述
3. 问题分析
4. 相关知识
4.1异常和中断
4.2 中断输入与挂起行为
4.3 Cortex-M4处理器的寄存器简介
4.4 C实现的异常处理
4.5 栈帧
4.6 异常返回值
4.7 异常流程
5. 问题定位
5.1 确定栈指针
5.2 确定LR的值
5.3 查询C代码位置
5.4 确定PSP栈
5.5 处理方法
6. 总结
1. 概述
最近做的项目中出现了HardFault故障现象,查阅了网上关于HardFault的排故思路,详尽程度不同,均有所帮助,但深入分析时,又觉得指导的不够到位,本文参考了《ARM Cortex-M3与Cortex-M4权威指南》,借鉴了网友的经验,结合了map文件加以分析,准确定位了故障原因,解决了实际问题,希望能帮助到广大网友。
2. 问题描述
接入以太网运行几个小时后,偶发单片机程序卡死现象。
3. 问题分析
采用一贯的解决嵌入式单片机问题的思路。从软件和硬件两个方面切入分析。(本人长期坚持这一原则,避免硬件问题被忽略)
该工程应用了CAN、串口、网络、AD等外设,在前期测试中,单独应用CAN、串口、网络均未出现问题,尤其是网络,进行过长时间压力测试(间隔1mS PING测试)均未出现问题,可初步排除硬件问题。
分析当时的应用场景,网络接入后引起的单片机程序卡死,复现时间较短。首先想到的方案是进行CAN、网络同时压力测试,并用仿真器在线调试。考虑程序大概率会卡死在HardFault处,因此在HardFault中断服务程序的while ( 1 )设置断点,在故障复现时,程序停在了HardFault_Handler的while循环处,代码如下。
void HardFault_Handler ( void )
{
/* Go to infinite loop when Hard Fault exception occurs */
while ( 1 )
{
}
}
要分析这个问题,我们需对STM32的中断及异常处理有一定的了解,才能更好的分析故障产生的原因,参考《ARM Cortex-M3与Cortex-M4权威指南》,整理了相关知识。
4. 相关知识
4.1异常和中断
要了解HardFault中断,先要了解中断。几乎所有的微控制器,中断是一种常见的特性。中断一般是由硬件(如外设和外部输入引脚)产生的事件,它会引起程序流偏离正常的流程(如给外设提供服务)。当外设或硬件需要处理器的服务时,执行的流程如图 1所示。
图 1外设中断处理流程
所有的Cortex-M处理器都会提供一个用于中断处理的嵌套向量中断控制器(NVIC)。除了中断请求,还有其他需要服务的事件,将其称为“异常”,按照ARM的说法,中断也是一种异常。典型的Cortex-M4微控制器中,NVIC接收多个中断源产生的中断请求,如图 2所示。
图 2 典型微控制器中的各种异常源
Cortex-M4的NVIC支持最多240个IRQ(中断请求)、1个不可屏蔽中断(NMI)、1个SysTick(系统节拍)定时中断及多个系统异常。具体如表 1所示。
表 1 Cortex-M4异常源
异常源 |
异常源说明 |
备注 |
IRQ |
多数IRQ由定时器、I/O端口和通信接口(如UART、SPI、CAN和I2C)等外设产生 |
|
NMI |
由看门狗定时器或掉电检测器等外设产生 |
|
SysTick定时器 |
系统节拍 |
|
系统异常 |
硬件错误、总线错误、存储管理错误、使用错误、PendSV等。 |
微控制器复位后,所有中断都处于禁止状态,且默认的优先级为0,在使用任何一个中断之前,需要:
- 设置所需中断的优先级。
- 使能外设中的可以触发中断的中断产生控制。
- 使能NVIC中的中断。
4.2 中断输入与挂起行为
每个中断源都有多个属性:
- 每个中断都可被禁止(默认)或使能。(这里不包含NMI)
- 每个中断都可被挂起(等待服务的请求)或解除挂起。
- 每个中断都可处于活跃(正在处理)或非活跃状态。
挂起状态的意思是,中断被置于一种等待处理器处理的状态。有些情况下,处理器在中断挂起时就会进行处理。不过,若处理器已经在处理另外一个更高或同等优先级的中断,或者中断被某个中断屏蔽寄存器给屏蔽掉了,那么在其他的中断处理结束前或中断屏蔽被清楚前,挂起请求会一直保持。
中断挂起和激活行为的简单情况如图 3所示。当中断请求产生时,中断进入挂起状态,处理器暂停当前处理的线程并完成压栈和取向量后,处理中断,中断挂起状态清除,进入活跃状态。处理器处理完中断后出栈,继续处理之前的线程,同时中断活跃状态结束。
图 3 中断挂起和激活行为的简单情况
4.3 Cortex-M4处理器的寄存器简介
在介绍异常处理前,需要先了解一下Cortex-M4处理器的寄存器。Cortex-M4处理器在处理器内核中都有多个执行数据处理核控制的寄存器,并以寄存器组的形式进行分组。Cortex-M4处理器的寄存器组中有16个寄存器,其中13个位32位通用寄存器,其他3个为特殊用途,如图 4所示。
图 4 Cortex-M处理器寄存器组中的寄存器
- R0~R12为通用目的寄存器,
- R13为栈指针,物理上存在两个栈指针:主栈指针(MSP)为默认的栈指针,在复位后和处理器处于处理模式时,会被选择使用;另一个栈指针为进程栈指针(PSP),只能用于线程模式(有RTOS时)。
- R14为链接寄存器(LR),用于函数或子程序调用时返回地址的保存。在函数或子程序结束时,程序控制可以通过将LR的数值加载程序计数器(PC)中返回调用程序处并继续执行,在异常处理期间,LR会自动更新为特殊的EXC_RETURN(异常返回)数值。
- R15为程序计数器(PC)。
4.4 C实现的异常处理
对于Cortex-M处理器,可以将异常处理或中断服务程序(ISR)实现为普通的C程序/函数。用于ARM架构的C编译器遵循ARM的一个名为AAPCS(ARM架构过程调用标准)的规范。根据这份标准,C函数可以修改R0~R3、R12、R14(LR)、以及PSR,若C函数需要使用R4~R11,应该将这些寄存器保存在栈空间中,并且在函数结束前将它们恢复,如图 5所示。
图 5 AAPCS规定的函数调用中的寄存器使用
C函数可以用作异常处理,异常机制需要再异常入口处自动保存R0~R3、R12、LR和PSR,并在异常退出时将它们恢复,这些都是由处理器硬件控制。具有浮点单元的处理器,如果用到了浮点单元,则异常机制还需要保存S0~S15和FPSCR。
4.5 栈帧
在异常入口处被压入占空间的数据块为栈帧。对于有浮点单元的处理器,栈帧可能是8或26个字。栈指针的数值在函数入口和出口处应该是双字对齐的。无浮点单元的栈帧格式如图 6所示,有浮点上下文的异常栈帧格式如图 7所示。
图 6 未启用浮点单元的异常栈帧
图 7 具有浮点上下文的异常栈帧
4.6 异常返回值
处理器进入异常处理或中断服务程序(ISR)时,链接寄存器(LR)的数值会被更新为EXC_RETURN数值。其合法值如表 2所示。
表 2 异常返回(EXC_RETURN)合法值
浮点单元在中断前使用(FPCA=1) |
浮点单元未在中断前使用(FPCA=0) |
|
返回处理模式(总是使用主栈) |
0xFFFFFFE1 |
0xFFFFFFF1 |
返回线程模式并在返回后使用主栈 |
0xFFFFFFE9 |
0xFFFFFFF9 |
返回处理模式并在返回后使用进程栈 |
0xFFFFFFED |
0xFFFFFFFD |
4.7 异常流程
当异常产生且被处理器接受时,压栈流程会将寄存器压入栈中并组织栈帧,如图 8所示。
图 8 异常流程
5. 问题定位
工程开发环境为MDK5.25,定位过程如下。
5.1 确定栈指针
在HardFault_Handler的while循环处设置断电,故障复现时R14(LR)寄存器值为0xFFFFFFFD。
查询“异常返回(EXC_RETURN)合法值”表格可知,返回后将使用进程栈,查看进程栈指针PSP的值为0x2000B278。具体如图 9所示。
图 9 故障复现调试示意图
5.2 确定LR的值
打开Memory的监视窗口,输入PSP的值为0x2000B278。则显示进程栈的内容如图 10所示,可知LR的值为0x080290E9。
图 10 查找LR值示意图
5.3 查询C代码位置
在Disassembly窗口单击右键—>选择Show Didassembly at Address—>输入地址0x080290E9—>定位到os_int.c文件的void OS_IntQPost ()函数,如图 11、图 12所示。该函数用于中断中延迟发布消息,分析问题可能和中断有关,和实际故障现象吻合。
图 11 定位C代码位置方法示意图
图 12 定位C代码位置示意图
5.4 确定PSP栈
根据前面的分析,PSP的值为0x2000B278,打开工程的map文件,找到0x2000B278附近的变量为OSCfg_IntQTaskStk(栈地址为0x2000b114~0x2000b2a3)和OSCfg_ISRStk(0x2000b2a4~0x2000b433),均为任务栈(线程栈),如图 13所示。
图 13 MAP文件示意图
5.5 处理方法
用watch窗口分别观察2个栈空间,发现OSCfg_ISRStk栈溢出,经分析可定位问题。接入网线后,由于网络优先级较高,更容易出现中断嵌套,导致OSCfg_ISRStk栈溢出,出现卡死在HardFault现象。
在os_cfg_app.h中定义了OSCfg_ISRStk的大小,如下:
/* --------------------- MISCELLANEOUS ------------------ */
#define OS_CFG_MSG_POOL_SIZE 100u /* Maximum number of messages */
#define OS_CFG_ISR_STK_SIZE 100u /* Stack size of ISR stack (number of CPU_STK elements) */
#define OS_CFG_TASK_STK_LIMIT_PCT_EMPTY 10u /* Stack limit position in percentage to empty */
/* ---------------------- IDLE TASK --------------------- */
#define OS_CFG_IDLE_TASK_STK_SIZE 128u /* Stack size (number of CPU_STK elements) */
/* ------------------ ISR HANDLER TASK ------------------ */
#define OS_CFG_INT_Q_SIZE 10u /* Size of ISR handler task queue */
#define OS_CFG_INT_Q_TASK_STK_SIZE 100u /* Stack size (number of CPU_STK elements) */
/* ------------------- STATISTIC TASK ------------------- */
#define OS_CFG_STAT_TASK_PRIO (OS_CFG_PRIO_MAX - 2u) /* Priority */
#define OS_CFG_STAT_TASK_RATE_HZ 10u /* Rate of execution (1 to 10 Hz) */
#define OS_CFG_STAT_TASK_STK_SIZE 100u /* Stack size (number of CPU_STK elements) */
/* ------------------------ TICKS ----------------------- */
#define OS_CFG_TICK_RATE_HZ 1000u /* Tick rate in Hertz (10 to 1000 Hz) */
#define OS_CFG_TICK_TASK_PRIO 1u /* Priority */
#define OS_CFG_TICK_TASK_STK_SIZE 128u /* Stack size (number of CPU_STK elements) */
/* ----------------------- TIMERS ----------------------- */
#define OS_CFG_TMR_TASK_PRIO 2u /* Priority of 'Timer Task' */
#define OS_CFG_TMR_TASK_RATE_HZ 100u /* Rate for timers (100 Hz Typ.) */
#define OS_CFG_TMR_TASK_STK_SIZE 128u /* Stack size (number of CPU_STK elements) */
将OSCfg_ISRStk大小修改为200,代码如下:
#define OS_CFG_ISR_STK_SIZE 200u
更改后观察OSCfg_ISRStk的空间,在压力测试下使用约60%,系统正常工作。文章来源:https://www.toymoban.com/news/detail-413683.html
6. 总结
HardFault问题涉及的原因比较复杂,有的简单、有的难,均和ARM底层相关,碰到该类问题让人头疼,对于初学者来说更容易发懵。本文通过实际案例分析,提供了解决问题的方法,旨在给读者提供软硬件问题的解决思路,未必能解决所有的问题。但请读者切记,碰到问题时一定要静下心来,认真分析,找到问题的根本原因才是终极目的,排故过程也是知识积累的过程,也是提高个人能力的过程。文章来源地址https://www.toymoban.com/news/detail-413683.html
到了这里,关于【STM32】HardFault问题详细分析及调试笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!