当前位置: 首页 > news >正文

STM32F4踩坑小记——使用HAL库函数进入HardFault

STM32F4踩坑小记——使用HAL库函数进入HardFault

  • 前言
  • 一、问题浮现
  • 二、问题的解答不重要,重要的是排查和分析过程
    • 2.1 摆开调试的架子
    • 2.2 分析汇编代码
      • 2.2.1 单条指令解析
        • 2.2.1.1 第一条指令:`ldr.w r4, [r1], #4`
        • 2.2.1.2 第二条指令:`str r4, [r0, #0]`
      • 2.2.2 组合功能
    • 2.3 寄存器r0指向哪里?
    • 2.4 如何根据参考手册确定寄存器的绝对地址?
    • 2.5 寄存器r1存放的是谁的地址?
    • 2.6 寄存器r2存放了什么?
      • 2.6.1 循环变量累加
      • 2.6.2 分支跳转
    • 2.7 在触发硬错误的时候发生了什么?
    • 2.8 问题解决
  • 三、总结

前言

这次的踩坑让我出乎意外,因为我的CRC外设的代码一直是正常运行的,后来不知道怎么的,一运行HAL_CRC_Calculate()函数,就进入了hardfault,为此懊恼不已。问题出在哪里呢?

本文洋洋洒洒写了很多,如果您希望直奔最后的解决方案,请直接跳转到2.8 问题解决一章节即可。前面写了很多分析过程,弯弯绕绕,不见得读者喜欢。然而这是我的思考过程,里面有讲调试的方法,汇编语句的分析,参考手册的查询和理解,数据手册的阅读和与代码的对照。信息量很多,希望能给大家带来帮助!

如果你喜欢本文,欢迎点赞、关注、转发、收藏!大家的支持是我继续更新的动力。

一、问题浮现

先将我的代码贴出来,大家可以直接拿去跑一跑,多半是正常运行的!但它确实有问题。(注意:运行之前要调用CRC的初始化函数!)

	char printf_string_g[200];uint32_t crc[6] = {0xFFFF,0xFFFF};uint32_t ret = HAL_CRC_Calculate(&hcrc, crc, sizeof(crc));sprintf(printf_string_g,"CRC calculate: %#lx\r\n", ret);HAL_UART_Transmit(&huart1, (uint8_t *)printf_string_g, strlen(printf_string_g), 1000);

在函数HAL_CRC_Calculate()内部,有这样的一段代码:

  /* Enter 32-bit input data to the CRC calculator */for (index = 0U; index < BufferLength; index++){hcrc->Instance->DR = pBuffer[index];}

而程序运行到这里的时候,就奔溃了……

在这里插入图片描述

二、问题的解答不重要,重要的是排查和分析过程

既然在这里进入了hardfault,那么调试一下!

2.1 摆开调试的架子

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.2 分析汇编代码

ldr.w   r4, [r1], #4
str     r4, [r0, #0]

这两条 ARM Thumb-2 汇编指令共同实现了一个 内存数据搬运 操作,常见于数据拷贝或初始化场景。以下是逐条解析和整体作用说明:

2.2.1 单条指令解析

2.2.1.1 第一条指令:ldr.w r4, [r1], #4
  • 操作:带后递增的加载(Load with Post-increment)
  • 行为
    1. 从内存地址 [r1] 加载 4字节 数据到寄存器 r4
    2. 完成加载后,r1 的值自动 +4(字节地址步进)
  • 等效C代码
    r4 = *(uint32_t*)r1;  // 从r1指向的地址读取32位数据
    r1 += 4;              // 指针后移4字节
    
2.2.1.2 第二条指令:str r4, [r0, #0]
  • 操作:存储(Store)
  • 行为
    • r4 的值存储到内存地址 [r0 + 0](即 r0 指向的地址)
    • r0 的值 不变
  • 等效C代码
    *(uint32_t*)r0 = r4;  // 将r4的值写入r0指向的地址
    

2.2.2 组合功能

ldr.w   r4, [r1], #4
str     r4, [r0, #0]

这两条指令联合实现的功能:

// C语言等效操作
*(uint32_t*)r0 = *(uint32_t*)r1;  // 从r1指向处拷贝4字节到r0指向处
r1 += 4;                          // 源指针后移

总结:
从寄存器r1指向处拷贝4字节到寄存器r0指向处,之后寄存器r1 = r1 + 4;

2.3 寄存器r0指向哪里?

查看寄存器值:

在这里插入图片描述
因为我在调用HAL_CRC_Calculate()函数,而且在操作寄存器DR,那么就自然需要查看参考手册。

2.4 如何根据参考手册确定寄存器的绝对地址?

打开《STM32F4 中文参考手册》,找到寄存器的定义:

在这里插入图片描述
纳尼?偏移地址是0x00?从哪里开始偏移?

带着疑惑,打开第2章 存储器和总线架构,找到2.3 存储器和总线架构章节,可以看到下面的表格:

在这里插入图片描述

看到CRC的寄存器从地址0x40023000开始的。对这个地址有没有感到熟悉?
回看2.3 寄存器r0指向哪里?章节,可以看到寄存器r0的值就是这个地址。那么就知道,原来,是将数据拷贝到寄存器DR。

这一个结论在C语言的代码上一眼就看到了,何必费心思在汇编上,还要查手册呢?

这是一种分析过程,遇到其他的问题也可以从这个角度分析。

2.5 寄存器r1存放的是谁的地址?

函数执行到断点处,就可以看到寄存器的值:
在这里插入图片描述

查看C语言代码,可以知道,一定是入参:指针pBuffer的值。

在这里插入图片描述
验证一下:

在这里插入图片描述
果然是这个地址,那么继续查一下:

在这里插入图片描述
第2个入参就是数组crc的地址。

总结:

数组crc的地址是寄存器r1的初始值,而在循环里面,寄存器r1的值是不断累加的,每次累加4,直到达到寄存器r2的值,就退出循环。

2.6 寄存器r2存放了什么?

单步执行汇编指令,发现寄存器r1的值在递增,增加幅度是4字节。而执行循环的条件是r1的值小于r2的值。r2存放了循环条件。
请看下图:
在这里插入图片描述

2.6.1 循环变量累加

汇编指令 add.w r2, r1, r2, lsl #2 是 Thumb-2 指令集中的一个带移位操作的加法指令。

  1. 指令组成

    • 助记符add.w
      • .w 表示这是一个 32 位宽的 Thumb-2 指令(Wide format)
    • 操作码add(加法)
    • 操作数
      • r2:目标寄存器(结果存储位置)
      • r1:第一个加数
      • r2, lsl #2:第二个加数(r2 左移 2 位后的值)
  2. 执行步骤

    1. 移位操作
      先将寄存器 r2 的值 逻辑左移 2 位(相当于乘以 4):

    2. 加法操作
      r1 和移位后的 r2 相加,结果存入 r2

      r2 = r1 + (r2 << 2)
      

换句话说,这里是将循环变量每次递增4!

2.6.2 分支跳转

而汇编指令:

cmp r1, r2 + bne.n 0x8003cd6ARM 汇编中经典的“比较-分支”结构,用于实现条件跳转逻辑。

  1. 执行流程
cmp     r1, r2          ; 比较 r1 和 r2,设置标志位
bne.n   0x8003cd6       ; 如果不相等(Z=0),跳转到目标地址
  1. 等效 C 代码
if (r1 != r2) {goto 0x8003cd6;  // 跳转到标签或函数
}
// 否则继续执行下一条指令

现在看下寄存器r2

在这里插入图片描述

计算一下,0x20023000 - 0x2001ffd0 = 0x30,也就是十进制48。

在这里插入图片描述
我们拷贝了48个字节?

在这里插入图片描述
这里的sizeof(crc),确实是48。

到这里,理解了这段汇编是说,结束循环的条件是r1 == r2,而r2存放的内容是拷贝结束的地址

2.7 在触发硬错误的时候发生了什么?

单步执行,最后进入硬错误,看下寄存器:

在这里插入图片描述
当循环执行到了地址0x20020000就执行不下去了,无法到达预期的0x20020030(寄存器r2的值)。

那么这个地址有什么魔力吗?

查看《STM32F407数据手册》第5章 Memory mapping看到如下细节:

在这里插入图片描述
对应我们的数组crc的地址(uint32_t crc[6] = {0xFFFF,0xFFFF};),确定了一点,它存放在图中的16KB的SRAM区域。然而大家一定注意到了从地址0x20020000再往上,就是保留区域了!

这就是进入硬错误的原因!

难道说数组uint32_t crc[6] = {0xFFFF,0xFFFF};存储的越界了,有一半跨过了保留区域?

不可能的!各个内存段的区域划分在ld文件中早已确定了,不可能将变量存放在外面。比如下面的代码:

MEMORY
{CCMRAM    (xrw) : ORIGIN = 0x10000000,   LENGTH = 64KRAM       (xwr) : ORIGIN = 0x20000000,   LENGTH = 128KFLASH     (xwr) : ORIGIN = 0x8000000,   LENGTH = 1024K
}

RAM段的起始地址是0x20000000,大小是128K。算一下结束地址:128K对应128*1024 = 131072字节,换算成十六进制,就是0x20000,再加上起始地址,再减一,得到结束地址是:0x2002 0000 - 1 = 0x201 FFFF

在这里插入图片描述

2.8 问题解决

问题出在了函数的调用上,函数的第3个参数是一共有多少个需要参加CRC计算,而不是字节!入参是字节数,导致循环中,数组访问越界了!

所以应当有如下修改:

	uint32_t crc[6] = {0xFFFF,0xFFFF};uint32_t ret = HAL_CRC_Calculate(&hcrc, crc, sizeof(crc)/sizeof(crc[0]));

三、总结

问题终于解决了。分析了一大堆,其实就是数组越界导致的。那么问题来了,为什么之前就没问题呢?当代码量增加,SRAM的使用量也在增加,以至于数组uint32_t crc[6] = {0xFFFF,0xFFFF};存放的位置在移动,如此一来,数组访问越界带来的风险就暴露出来了!

http://www.lryc.cn/news/590395.html

相关文章:

  • 蓝光三维扫描技术:手机闪光灯模块全尺寸3D检测的高效解决方案
  • HTML基础知识 二(创建容器和表格)
  • 在虚拟环境中复现论文(环境配置)
  • Class<T> 类传递及泛型数组
  • SSH连接复用技术在海外云服务器环境下的稳定性验证与优化方案
  • 动态规划的核心性质——最优化原理 (Principle of Optimality)
  • git的diff命令、Config和.gitignore文件
  • Python编程基础(六)| 用户输入和while循环
  • slurm设置用户节点和分区权限
  • Telink的GPIO
  • 系统思考场景应用
  • Node.js基础用法
  • 3DGS之COLMAP
  • iOS 抓包工具选择与配置指南 从零基础到高效调试的完整流程
  • VR 污水厂初体验:颠覆传统认知​
  • CSS全面系统教程:从入门到精通网页样式设计
  • 安全初级作业2
  • 基于SpringBoot+Uniapp球场预约小程序(腾讯地图API、Echarts图形化分析、二维码识别)
  • Vue在线预览Excel和Docx格式文件
  • 【IDEA】格式化代码工具配置
  • STM32硬件I2C的注意事项
  • c语言-数据结构-二叉树的遍历
  • 2025华为ODB卷-宜居星球改造计划200分-三语言题解
  • Jenkins credentials 增加了github credential 但是在Git SCM 凭证中不显示
  • Redis持久化RDB和AOF实现原理详细介绍
  • 将Android Studio创建的一个apk工程放到Android15源码中构建
  • mysql- 存储结构、存储函数,批量生成测试数据
  • ssl相关命令生成证书
  • 代码随想录算法训练营第五十天|图论part1
  • Python 日志轮换处理器的参数详解