本帖最后由 DKENNY 于 2024-9-13 17:52 编辑
#申请原创# @21小跑堂
1. 前言
在嵌入式 MCU 开发中,由于硬件资源有限,RAM 和 ROM 空间常常比较紧张。即使是一些较大的 MCU,其 RAM 空间也通常只有 512KB,而一些小型 MCU 的 RAM 则更显微薄。本文将以 APM32 控制器为例,通过实验介绍RAM的相关数据段以及资源分配情况。
2. 数据段含义
2.1 Program Size解析
在使用 Keil 编译程序后,编译成功时会在输出栏看到一行信息,其中包含了 Program Size。那么,这个 Program Size 实际上意味着什么呢?其中的 Code、RO-data、RW-data 和 ZI-data 各代表什么呢?
例如,输出信息为:
- Program Size: Code=1676 RO-data=512 RW-data=40 ZI-data=1024
- Code 段:这个部分表示我们编写的代码占用了多少 Flash 存储空间。需要注意的是,Code 段的大小是经过编译器优化和汇编处理后的结果,添加注释或无效代码不会影响 Code 段的大小。
- RO-data 段:即只读数据段(Read-Only Data)。在代码中定义的 const 常量以及某些固定的字符(如 printf 打印的内容)都会存储在这个数据段中。
- RW-data 段:可读可写数据段(Read-Write Data)。代码中的变量会被存放在这个数据段内。
- ZI-data 段:即零初始化数据(Zero-Initialized Data)。用于存放那些初始化为 0 的可读写变量。
那么,局部变量、全局变量和常量是如何占用 RAM 空间的呢?如何计算它们实际占用的 RAM 和 ROM 空间呢?
关于 RAM 和 ROM 空间的占用计算方式如下:
- ROM 空间大小计算方式:Total ROM = Code + RO-data + RW-data
- RAM 空间大小计算方式:Total RAM = RW-data + ZI-data
通过实验,我们可以进一步验证和推理局部变量、全局变量以及常量是如何占用 RAM 的。
3. 局部变量、全局变量、常量如何占用 RAM?
3.1 栈大小Stack_Size 与 堆大小Heap_Size
但在此之前,我们需要先了解栈大小(Stack_Size)和堆大小(Heap_Size)这两个重要概念。MCU 的 RAM 空间被划分为多个区域,其中包括堆和栈(还有其他区域)。这两个区域的大小可以在对应的启动文件中查看和设置。
当我们编写程序时,栈(Stack)和堆(Heap)是如何使用的呢?
- 栈的使用:在程序编码过程中,当我们在函数内部定义一个局部变量时,实际上是在栈空间上申请了一块内存来存储数据。在程序运行过程中,当进入某个函数时,需要保存当前函数内的变量状态,也就是进行压栈操作。这意味着将当前的寄存器(如 r0、r1 等)压入栈空间,以便在函数退出时能够恢复现场。压栈操作实际上就是在栈空间上占用一块内存来存储数据。当函数退出时,需要进行出栈操作,即从栈空间中读取之前压栈操作保存的数据,恢复寄存器的值,然后继续执行后续的代码。
- 堆的使用:在程序执行过程中,当我们调用 malloc 函数时,实际上是从堆上申请一块内存来供我们使用。一旦使用完成,我们需要调用 free 函数来释放这块内存区域,以便其他部分可以继续使用。
此外,对于裸机程序(没有操作系统的程序),整个程序使用的栈都是从启动文件(如 startup_xxxx.s)中设置的栈空间中分配的。而对于使用实时操作系统(RTOS)的程序,每个线程都具有独立的栈空间,这一点需要特别注意。
接着我们进行如下测试验证。
3.2 验证栈大小设置,对程序编译的影响
1. 修改栈大小,观察程序编译大小变化,设置 Stack_Size EQU 0x800,程序编译结果如下。
- Program Size: Code=1676 RO-data=512 RW-data=40 ZI-data=2048
2. 修改栈大小为 Stack_Size EQU 0x400 ,程序编译结果如下。
|