打印
[STM32F1]

stm32-Hardfault

[复制链接]
298|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
macpherson|  楼主 | 2024-7-17 22:57 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式


STM32内存结构

1.要点
1.1 两种存储类型: RAM 和 Flash
RAM可读可写,在STM32的内存结构上,RAM地址段分布[0x2000_0000, 0x2000_0000 + RAM size)
Flash只读,在STM32的内存结构上,Flash地址段[0x0800_0000, 0x2000_0000)
1.2 六类存储数据段: .data/.bss/.text/.constdata/heap/stack
.data数据段: 用来存放初始化了但不是初始化为0的全局变量(global)和静态变量(static)。它是可读可写的
.bss(Block Started by Symbol)数据段: 用于存放没有初始化或初始化为0的全局变量和静态变量,可读可写,如果没有初始化, 系统会将变量初始化为0.
.text代码段: 用来放程序代码(code), 在代码编译完成后, 长久只读存放于此.
.constdata只读常量数据段: const限定的数据类型存放与此,只读.
heap堆区: 通常只我们说的动态内存分配,使用内存分配器(memory allocator)管理, malloc/free进行申请和释放
stack栈区: 在代码执行时用来保存函数的局部变量和参数。其操作方式类似于数据结构中的栈,是一种“后进先出”(Last In First Out,LIFO)的数据结构。这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据,对于哪些暂时存储的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它信息。栈的顶部通常在可读写的RAM区的最后,其地址空间通常“向下减少”,即当栈上保存的数据越多,栈的地址就越小。

1.3 三种存储属性区: RO/RW/ZI
RO (Read Only ): 只读区域, 需要长久保存,烧写到Rom/Flash段,上文数据段的.text段和.constdata段属于此属性区(有时.constdata 段也被叫做 RO-data段, 和这个广义的RO注意区分)
RW (Read Write): 可读可写的初始化了的全局变量和静态变量段,上文中的.data段属于RW区
ZI (Zero Init): 没有进行初始化或者初始化为0,系统上电时会主动把此区域数据进行0初始化,上文的.bss段就是. 另外, 可翻看Keil工具编译的map文件,Heap和Stack区也进行了Zero的属性标注, 因此, Heap和Stack也可认为是ZI区域
RW区比较特别, 可读可写但又进行了初始化,因为RAM中的数据是掉电不可保存的,因此RW区的.data段数据也需要保存在Rom/Flash里面,上电时候再将此类数据复制到RAM区域读写使用。而ZI区域数据不需要掉电保存,直接上电时初始化为0即可使用,因此不需要保存在ROM中。这样,计算RAM/ROM占用空间的公式:

ROM Size = .text + .constdata + .data (RO + RW)
RAM Size = .bss + .data (ZI + RW)

这里RAM size计算时未考虑Stack和Heap区, 实际size是大于此的, 因为这两个区域具备动态变化的复杂性,难于估计。

定义一个全局数组变量举例:

1. static unsigned char test[1024];  //全局、未初始化, ZI区,不影响ROM size
2. static unsigned char test[1024] = {0};  //全局、初始化为0, ZI区,不影响ROM size
3. static unsigned char test[1024] = {1};  //全局、初始化为非0, RW(.data)区,ROM Size 扩大
1.4 扩展说说Heap
在STM32的启动代码startup_*.s文件中,一般这样定义了堆大小:

Heap_Size      EQU     0x200;

在实际使用中, 这个区域可能比1.2节提到的简洁描述更为复杂。

很多小项目没有使用内存分配器: 由于各种原因(RAM不足、程序简单、etc),一些所必须的大块或固定内存直接使用数组的方式定义使用,绕开了内存分配器。那么这个时候, Heap_Size 的存在是没有意义的, Heap_Size 定义越大,越浪费空间,可以直接Heap_Size定义为0。这个时候, 本来该堆区提供的空间可能定义在了.bss段(全局/静态数组没有初始化)、或.data(全局/静态数据初始化为非0)、或Stack上(使用了局部数组变量, Tips: 但大的数组不建议定义在stack, 否则可能栈溢出)
重新实现内存分配器:没有直接将内存分配器直接映射在堆区,而是先定义大的数组内存(可能在.bss或.data, 为避免在ROM存储, 最好在.bss), 再将这块内存给内存分配器支配使用
内存分配器直接使用Heap区: 这个时候就要计算好预留多少空间给Stack区, 留多了,Stack用不上浪费;留少了极可能造成Stack溢出而程序崩溃
除了使用自带RAM外,同时使用外部扩展RAM: 这就需要内存分配器来管理好几块地址不连续的RAM空间了

Stm32的keil编译连接如上图所示。

编译信息包含以下几个部分:
    1)Code: 代码段,存放程序的代码部分
    2)RO-data:只读数据段, 存放程序中定义的常量;
    3)RW-data: 读写数据段,存放初始化为非0值的全局变量
    4)ZI-data: 零数据段,存放未初始化的全局变量及初始化为0的变量;

编译完工程会生成一个. map 的文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系:

Total RO  Size (Code + RO Data)                46052 (  44.97kB)
Total RW  Size (RW Data + ZI Data)             36552 (  35.70kB)
Total ROM Size (Code + RO Data + RW Data)      46212 (  45.13kB)
    1)RO Size 包含了 Code 及 RO-data,表示程序占用Flash空间的大小
    2)RW Size 包含了RW-data及ZI-data,表示运行时占用的RAM的大小
    3)ROM Size 包含了Code, RO Data以及RW Data, 表示烧写程序所占用的Flash空间的大小

STM32中程序占用内存容量
Keil MDK下Code, RO-data,RW-data,ZI-data这几个段:

Code存储程序代码。
RO-data存储const常量和指令。
RW-data存储初始化值不为0的全局变量。
ZI-data存储未初始化的全局变量或初始化值为0的全局变量。
占用的Flash=Code + RO Data + RW Data;

运行消耗的最大RAM= RW-data+ZI-data;

这个是MDK编译之后能够得到的每个段的大小,例如下图Program Size 中的Code R0 RW ZI

可以计算出占用的FLASH = 34456+456+172=34.26kB,占用的RAM=172+18908=18.63kB

那么堆栈是如何分配的呢,堆栈的内存占用就是在上面RAM分配给RW-data+ZI-data之后的地址开始分配。

堆:编译器调用动态内存分配的内存区域。

栈:程序运行的时候局部变量的地方,先进后出,这种结构适合程序调用,所以局部变量用数组太大了都有可能造成栈溢出

堆栈溢出容易导致HaltFault。

堆栈大小的设置在启动文件start_stmf103xb.s中(以STM32F103为例):

全局变量被未知原因改变的解决方法

在开发的过程中总会碰到一些奇怪的问题,仿真的时候一看,发现是某个全局变量被莫名其妙改变了,导致整个函数判断都出了问题。
全局变量可能会被改变的原因有以下几点:

1.自己改的(废话~):好好查看这个变量被谁调用了

2.全局变量字节未对齐:
有一次调试的时候发现一个变量定义成局部变量就能正常运行,而定义成全局变量就不能运行了。局部变量能运行说明我程序的逻辑是没问题的,找原因的时候一看是我全局变量经常会莫名其妙被改变。找了一圈发现这个变量根本没被其他函数使用。
后面通过仿真,得到该变量的地址(假设为0x1002)。地址除以4之后发现不是一个整数,这才发现是这个变量字节未4字节对齐导致的。至于为什么不对齐,我也不知道!
解决方法:使用 attribute((aligned(4))) 修饰,使其4字节对齐,就完美解决了。

3.指针未初始化:
假如你定义的变量是指针类型的话,没有给他初始化则会导致该指针是个野指针,里面的值是不确定的。

总之开发的过程中,少用全局变量,要用的话尽量用结构体,做好分层,提高代码的可阅读性和移植性。


使用特权

评论回复
沙发
sfd123| | 2024-7-18 08:25 | 只看该作者
虽然是为了讲hardfault,其实对编译信息讲解的更详细,这个mark一下

使用特权

评论回复
板凳
tpgf| | 2024-7-19 10:32 | 只看该作者
eeprom的操作方式和flash是不是差不多啊

使用特权

评论回复
地板
xiaoqizi| | 2024-7-19 12:23 | 只看该作者
为什么内存结构里边不涉及rom存储呢

使用特权

评论回复
5
木木guainv| | 2024-7-19 16:42 | 只看该作者
所有的寄存器数据是存储在flash里边的是吗

使用特权

评论回复
6
wowu| | 2024-7-19 17:48 | 只看该作者
ZI区域每次上电都会恢复成未初始化状态吗

使用特权

评论回复
7
wakayi| | 2024-7-19 18:54 | 只看该作者
堆栈是位于单片机的哪个部分呢 是ram还是flash呢

使用特权

评论回复
8
4c1l| | 2024-7-27 11:15 | 只看该作者
你的描述涵盖了嵌入式系统中常见的存储区段和属性,特别是在使用STM32微控制器时

使用特权

评论回复
9
4c1l| | 2024-7-27 11:22 | 只看该作者
这里对这些存储区段及属性的详细解释有助于理解系统内存的布局和管理。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

36

主题

1472

帖子

1

粉丝