【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-M3Cortex-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-M3Cortex-M4权威指南》,整理了相关知识。

4. 相关知识

4.1异常和中断

        要了解HardFault中断,先要了解中断。几乎所有的微控制器,中断是一种常见的特性。中断一般是由硬件(如外设和外部输入引脚)产生的事件,它会引起程序流偏离正常的流程(如给外设提供服务)。当外设或硬件需要处理器的服务时,执行的流程如图 1所示。

【STM32】HardFault问题详细分析及调试笔记_第1张图片

 图 1外设中断处理流程

        所有的Cortex-M处理器都会提供一个用于中断处理的嵌套向量中断控制器(NVIC)。除了中断请求,还有其他需要服务的事件,将其称为“异常”,按照ARM的说法,中断也是一种异常。典型的Cortex-M4微控制器中,NVIC接收多个中断源产生的中断请求,如图 2所示。

【STM32】HardFault问题详细分析及调试笔记_第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所示。当中断请求产生时,中断进入挂起状态,处理器暂停当前处理的线程并完成压栈和取向量后,处理中断,中断挂起状态清除,进入活跃状态。处理器处理完中断后出栈,继续处理之前的线程,同时中断活跃状态结束。

【STM32】HardFault问题详细分析及调试笔记_第3张图片

图 3 中断挂起和激活行为的简单情况

4.3 Cortex-M4处理器的寄存器简介

        在介绍异常处理前,需要先了解一下Cortex-M4处理器的寄存器。Cortex-M4处理器在处理器内核中都有多个执行数据处理核控制的寄存器,并以寄存器组的形式进行分组。Cortex-M4处理器的寄存器组中有16个寄存器,其中13个位32位通用寄存器,其他3个为特殊用途,如图 4所示。

【STM32】HardFault问题详细分析及调试笔记_第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所示。

【STM32】HardFault问题详细分析及调试笔记_第5张图片

图 5 AAPCS规定的函数调用中的寄存器使用

        C函数可以用作异常处理,异常机制需要再异常入口处自动保存R0~R3、R12、LR和PSR,并在异常退出时将它们恢复,这些都是由处理器硬件控制。具有浮点单元的处理器,如果用到了浮点单元,则异常机制还需要保存S0~S15和FPSCR。

4.5 栈帧

        在异常入口处被压入占空间的数据块为栈帧。对于有浮点单元的处理器,栈帧可能是8或26个字。栈指针的数值在函数入口和出口处应该是双字对齐的。无浮点单元的栈帧格式如图 6所示,有浮点上下文的异常栈帧格式如图 7所示。

【STM32】HardFault问题详细分析及调试笔记_第6张图片

 图 6 未启用浮点单元的异常栈帧

【STM32】HardFault问题详细分析及调试笔记_第7张图片

图 7 具有浮点上下文的异常栈帧

4.6 异常返回值

        处理器进入异常处理或中断服务程序(ISR)时,链接寄存器(LR)的数值会被更新为EXC_RETURN数值。其合法值如表 2所示。

表 2 异常返回(EXC_RETURN)合法值

浮点单元在中断前使用(FPCA=1)

浮点单元未在中断前使用(FPCA=0)

返回处理模式(总是使用主栈)

0xFFFFFFE1

0xFFFFFFF1

返回线程模式并在返回后使用主栈

0xFFFFFFE9

0xFFFFFFF9

返回处理模式并在返回后使用进程栈

0xFFFFFFED

0xFFFFFFFD

4.7 异常流程

        当异常产生且被处理器接受时,压栈流程会将寄存器压入栈中并组织栈帧,如图 8所示。

【STM32】HardFault问题详细分析及调试笔记_第8张图片

图 8 异常流程

5. 问题定位

工程开发环境为MDK5.25,定位过程如下。

5.1 确定栈指针

     在HardFault_Handler的while循环处设置断电,故障复现时R14(LR)寄存器值为0xFFFFFFFD

        查询“异常返回(EXC_RETURN)合法值”表格可知,返回后将使用进程栈,查看进程栈指针PSP的值为0x2000B278。具体如图 9所示。

【STM32】HardFault问题详细分析及调试笔记_第9张图片

图 9 故障复现调试示意图

5.2 确定LR的值

        打开Memory的监视窗口,输入PSP的值为0x2000B278则显示进程栈的内容如图 10所示,可知LR的值为0x080290E9

【STM32】HardFault问题详细分析及调试笔记_第10张图片

图 10 查找LR值示意图

5.3 查询C代码位置

     在Disassembly窗口单击右键—>选择Show Didassembly at Address>输入地址0x080290E9—>定位到os_int.c文件的void  OS_IntQPost ()函数,如图 11、图 12所示。该函数用于中断中延迟发布消息,分析问题可能和中断有关,和实际故障现象吻合。

【STM32】HardFault问题详细分析及调试笔记_第11张图片

图 11 定位C代码位置方法示意图

 【STM32】HardFault问题详细分析及调试笔记_第12张图片

图 12 定位C代码位置示意图

5.4 确定PSP栈

        根据前面的分析,PSP的值为0x2000B278打开工程的map文件,找到0x2000B278附近的变量为OSCfg_IntQTaskStk(栈地址为0x2000b114~0x2000b2a3)和OSCfg_ISRStk(0x2000b2a4~0x2000b433),均为任务栈(线程栈),如图 13所示。

【STM32】HardFault问题详细分析及调试笔记_第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底层相关,碰到该类问题让人头疼,对于初学者来说更容易发懵。本文通过实际案例分析,提供了解决问题的方法,旨在给读者提供软硬件问题的解决思路,未必能解决所有的问题。但请读者切记,碰到问题时一定要静下心来,认真分析,找到问题的根本原因才是终极目的,排故过程也是知识积累的过程,也是提高个人能力的过程。

你可能感兴趣的:(STM32进阶,stm32,单片机,嵌入式硬件)