【STM32】HardFault问题详细分析及调试笔记 | 您所在的位置:网站首页 › STM32程序跑飞到硬件中断 › 【STM32】HardFault问题详细分析及调试笔记 |
目录 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%,系统正常工作。 6. 总结HardFault问题涉及的原因比较复杂,有的简单、有的难,均和ARM底层相关,碰到该类问题让人头疼,对于初学者来说更容易发懵。本文通过实际案例分析,提供了解决问题的方法,旨在给读者提供软硬件问题的解决思路,未必能解决所有的问题。但请读者切记,碰到问题时一定要静下心来,认真分析,找到问题的根本原因才是终极目的,排故过程也是知识积累的过程,也是提高个人能力的过程。 |
CopyRight 2018-2019 实验室设备网 版权所有 |