一、必备知识点:
在用Keil对STM32的程序进行仿真时程序有时会跑飞,停止仿真程序会停在HardFault_Handler函数里的死循环while(1)中。这说明STM32出现了硬件错误。遇到这种问题时,我们应该怎么调试并定位触发硬件错误的位置呢。不要急,不要慌,如果你对stm32内核错误机制不是很了解的话,请下载并查阅百度云文档:链接:https://pan.baidu.com/s/1SirkeTLXoUGx5gOTwd_TXQ 提取码:56cw
文档里面有先关的知识点介绍,建议先看 :Hard-Fault的诊断.pdf 文档
二、基础知识讲解
2.1、Fault 类异常
有若干个系统异常专用于 fault 处理。 CM3 中的 Faults 可分为以下几类:
- 总线 faults
- 存储器管理 faults
- 用法 faults
- 硬 fault
表 7.8 总线 fault 状态寄存器(BFSR), 地址:0xE000_ED29
表 7.9 存储器管理 fault 状态寄存器(MFSR),地址:0xE000_ED28
表 7.10 用法 fault 状态寄存器(UFSR), 地址:0xE000_ED2A
表 7.11 硬 fault 状态寄存器 地址:0xE000_ED2C
也就是说上述的四个异常,在keil里面中断处理函数都是一样:void HardFault_Handler(void)
2.2、总线 Faults
当 AHB 接口上正在传送数据时,如果回复了一个错误信号(error response),则会产生总线 faults,产生的场合可以是:
- z 取指,通常被称作“预取流产”(prefetch abort)
- z 数据读/写,通常被称作“数据流产”(data abort)
在 CM3 中执行如下动作可以触发总线异常:
- 中断处理起始阶段的堆栈 PUSH 动作。 称为“入栈错误”
- 中断处理收尾阶段的堆栈 POP 动作。 称为“出栈错误”
- 在处理器启动中断处理序列(sequence)后的向量读取时。这是一种罕见的特殊情况,被归类为硬 fault。
2.3、存储器管理 faults
存储器管理faults多与MPU有关,其诱因常常是某次访问触犯了MPU设置的保护策略。另外,某些非法访问,例如,在不可执行的存储器区域试图取指,也会触发一个 MemManagefault,而且即使没有 MPU 也会触发。MemManage faults 的常见诱因如下所示:
- 访问了 MPU 设置区域覆盖范围之外的地址
- 往只读 region 写数据
- 用户级下访问了只允许在特权级下访问的地址
2.4、用法 faults
用法 faults 发生的场合可以是:
- 执行了未定义的指令
- 执行了协处理器指令(Cortex‐M3 不支持协处理器,但是可以通过 fault 异常机制来使用软件模拟协处理器的功能,从而可以方便地在其它 Cortex 处理器间移植)
- 尝试进入 ARM 状态(因为 CM3 不支持 ARM 状态,所以用法 fault 会在切换时产生。软件可以利用此机制来测试某处理器是否支持 ARM 状态)
- 无效的中断返回(LR 中包含了无效/错误的值)
- 使用多重加载/存储指令时,地址没有对齐。另外,通过设置 NVIC 的对应控制位,可以在下列场合下也产生用法 fault:
- 除数为零
- 任何未对齐的访问
2.5、硬 fault
硬 fault 是上文讨论的总线 fault、存储器管理 fault 以及用法 fault 上访的结果。如果这些 fault 的服务例程无法执行,它们就会成为“硬伤”——上访(escalation)成硬 fault。另外,在取向量(异常处理是对异常向量表的读取)时产生的总线 fault 也按硬 fault 处理。
在NVIC 中有一个硬 fault 状态寄存器(HFSR),它指出产生硬 fault 的原因。如果不是由于取向量造成的,则硬 fault 服务例程必须检查其它的 fault 状态寄存器,以最终决定是谁上访的。
三、实操:
3.1、实操一:
3.1.1、在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真,是暂停,不是退出仿真。
3.1.2、查看具体错误原因,stm32在使用keil仿真时有一个错误报告寄存器 Peripherals->Core Peripherals->Fault Reports
3.1.3 在Keil菜单栏点击“View”——“Registers Window”,在寄存器查看窗口查找R14(LR)的值。如果R14(LR) = 0xFFFFFFE9,继续查看MSP(主堆栈指针)的值,如果R14(LR) = 0xFFFFFFFD,继续查看PSP(进程栈指针)的值。我的程序R14(LR) = 0xFFFFFFF9,接下来以此为例。
知识点:有关LR = 0xFFFFFFE9说明,可以看下图,请留意低八位数据数据(E或F)
3.1.4 在Keil菜单栏点击“View”——“Memory Windows”——“Memory1”,在“Address”地址栏中输入MSP的值:0x20001288,然后在对应的行里找到地址。地址一般以0x08开头的32位数。本例中,地址为0x08003CB9。
注意:memory里面一般显示数据由于大小端问题,数据位置可能会前后交换,比如0x12345678数据可能会出现 78 56 45 12;所以需要设置显示格式。这里将鼠标遇到Memory区域,然后右键,然后选择数据格式即可,建议选择long型格式显示。
3.1.5 在Keil菜单栏点击“View”——“Disassembly Window”,在“Disassembly”窗口中右击,在下拉菜单中选择“Show Disassemblyat Address...”。在弹出框“Show Code atAdress”的地址框中输入地址0x08003CB9进行搜索,然后就会找到相对应的代码。这里的代码就是进入循环中断之前的情况。仔细查看附近区域的相关代码来排查错误具体原因。
3.1.6、排查附近代码时,需要知道一般的错误原因,然后根据原因逐个排查,比如数组溢出的问题,我们首先要排查检测的就是数组越位的问题,比如代码上是否有边界判定,如果没有,我们需要在查看检测传入函数参数数据是否过大或者全局变量是否已经被修改后导致数值过大。如果是局部变量的,就只能一步一步看代码了,如果是全局变量,我们可以将该全局变量放入watch窗口,查看该全局变量是否过大或过小。如果数据不正常,这个是否除了排查代码对该变量的赋值或修改,同时,需要查看工程的.map文件,然后找到该全局变量,然后查看该变量前后的变量,有很大可能是前后连续变量越界访问或读写导致全局变量溢出的。
错误原因:
(1)数组越界操作;
(2)内存溢出,访问越界;
(3)堆栈溢出,程序跑飞;
(4)中断处理错误;
工程.map文件打开方式:
project窗口 -> 双击工程目录即可(如果不行,请重新定位link文件夹;请参考下图:)
3.2、实操二:
3.2.1、在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真,不是退出仿真。
3.2.2、在Keil菜单栏点击“View”——“Call Stack Window”弹出“Call Stack + Locals”对话框。然后在对话框中右键选择“Show Caller Code”,就会跳转到出错之前的函数处,仔细查看这部分函数被调用或者数组内存使用情况。