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)
- 行为:
- 从内存地址
[r1]
加载 4字节 数据到寄存器r4
- 完成加载后,
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 指令集中的一个带移位操作的加法指令。
-
指令组成
- 助记符:
add.w
.w
表示这是一个 32 位宽的 Thumb-2 指令(Wide format)
- 操作码:
add
(加法) - 操作数:
r2
:目标寄存器(结果存储位置)r1
:第一个加数r2, lsl #2
:第二个加数(r2
左移 2 位后的值)
- 助记符:
-
执行步骤
-
移位操作:
先将寄存器r2
的值 逻辑左移 2 位(相当于乘以 4): -
加法操作:
将r1
和移位后的r2
相加,结果存入r2
:r2 = r1 + (r2 << 2)
-
换句话说,这里是将循环变量每次递增4!
2.6.2 分支跳转
而汇编指令:
cmp r1, r2
+ bne.n 0x8003cd6
是 ARM 汇编中经典的“比较-分支”结构,用于实现条件跳转逻辑。
- 执行流程
cmp r1, r2 ; 比较 r1 和 r2,设置标志位
bne.n 0x8003cd6 ; 如果不相等(Z=0),跳转到目标地址
- 等效 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};
存放的位置在移动,如此一来,数组访问越界带来的风险就暴露出来了!