深入解析 MCU 内存架构:Flash、RAM、代码与变量存储详解 .rodata .text heap stack .bss .data code RO-data RW-data ZI-data

1. MCU 内存概述

1.1 MCU 内存计算

STM32F103CBT6 为例,在 Keil 中可以看见,该款型号 MCU 的 Flash, RAM 大小为:
请添加图片描述

​ 可以看出 Flash 的地址范围为 0x08000000 ~ 0x08010000,大小为 0x10000(164 byte = 216 byte,所以为 26 KB = 64 KB),同理 RAM 的地址范围为 0x20000000 ~ 0x20005000,大小为 0x5000(5 * 163 byte = 5 * 212 byte,所以为 5 * 22 KB = 20 KB)

1.2 内存分布结构

  • 64KB Flash(用于存放 .text.rodata
  • 20KB RAM(用于 .data.bss、栈和堆)

程序的典型内存分布:

​ stm32 由于是 32 位地址总线,所以地址位用八位十六进制数表示(168 = 232)。

pgsql复制编辑+------------------+ 0x08010000 (Flash 结束)
|      .rodata    |  只读数据(Flash)
+------------------+
|      .text      |  代码段(Flash)
+------------------+ 0x08000000 (Flash 起始)

+------------------+ 0x20005000 (RAM 结束)
|       Heap      |  动态分配(malloc)
+------------------+
|       Stack     |  栈(局部变量、返回地址)
+------------------+
|      .bss       |  未初始化数据(RAM)
+------------------+
|      .data      |  已初始化全局变量(RAM)
+------------------+ 0x20000000 (RAM 起始)

注意

  1. .text.rodata 只能存放在 Flash(代码和只读常量)。
  2. .data 段在 Flash 里存有初值,但运行时会被复制到 RAM
  3. .bss不会占用 Flash,但会在 RAM 里初始化为 0
  4. 栈和堆共享 RAM,栈 Stack 从 高地址 0x20005000 向低地址 方向增长,堆 Heap 从 低地址 0x20000000 向高地址 方向增长
  5. 当 Stack 和 Heap 彼此增长到一个临界点(即二者相遇),就会导致 Stack Overflow(栈溢出)。

2.ARM 编译器中的存储字段

​ 在 Keil 或 ARM GCC 编译器的 Program Size 统计信息中,列出了四个关键字段:

  • Code(.text)程序代码
  • RO-data(.rodata)只读数据
  • RW-data(.data)已初始化的全局/静态变量
  • ZI-data(.bss)未初始化的全局/静态变量

示例:Keil 编译器的存储统计
请添加图片描述

​ 上图显示了,该程序的内存统计信息如下:

  • Code=13996 byte
  • RO-data=312 byte
  • RW-data=156 byte
  • ZI-data=12156 byte

3. 存储区域的详细解析

​ MCU 代码和变量在存储时会被划分到不同的区域,各个区域用途如下:

存储区域用途存储位置
.text(Code)存放编译后的程序指令(代码)Flash(只读)
.rodata(RO-data)存放只读常量const 变量等)Flash(只读)
.data(RW-data)已初始化的全局/静态变量Flash + RAM
.bss(ZI-data)未初始化的全局/静态变量,启动时清零RAM(自动清零)
Heap(堆)malloc() 动态分配的内存RAM(向上增长)
Stack(栈)局部变量、函数调用返回地址RAM(向下增长)

3.1 .text段(存储 code)

  • 存储内容:存放程序指令,即 CPU 执行的代码。
  • 存储位置:通常存放在 Flash(ROM),因为代码是固定的,不需要在运行时修改。
  • 特点
    • MCU 复位后,程序从 Flash 读取指令并执行。
    • 代码段通常是只读的,防止意外修改。
    • C 语言的函数和中断服务函数都存放在这里。
  • 示例:
void func(void) {  // func 的指令代码存放在 .text 段
    // do something
}

3.2 .rodata 段 (存储 RO-data,Read-Only Data)

  • 存储内容:存放只读数据,如 const 修饰的全局变量、字符串常量等。
  • 存储位置:通常存放在 Flash,因为这些数据不会被修改。
  • 特点
    • 只能被读取,不能修改。
    • 对于资源有限的 MCU,尽量让数据存放在 Flash,而不是占用 RAM(不需要改变的常量可以用 const 修饰)。
  • 示例
const char message[] = "Hello, World!";

3.3 .data 段(存储 RW-data,Read-Write Data )

存储内容:存放已初始化的全局变量/静态变量

存储位置

  • 编译时:数据存放在 Flash。
  • 程序运行时:MCU 启动代码(Startup 文件)会在程序启动时,将 .data 段的数据从 Flash 复制到 RAM,确保变量可读写。

所以已初始化的全局变量会同时占用 Flash 和 RAM 的内存空间

  • 特点

    • 变量必须初始化,否则存放在 .bss 段。
    • 既占用 Flash 又占用 RAM(启动时复制)。
  • 示例:

int a = 10;    // 存放在 .data 段(Flash + RAM)
static int b = 20;  // 也存放在 .data 段(Flash + RAM)

3.4 .bss 段(存储 ZI-data,Zero Initialized Data ,)

  • 存储内容:存放未初始化的全局变量和静态变量
  • 存储位置:直接存放在 RAM。
  • 特点
    • 上电后,系统会自动清零(由 Startup 代码完成)。
    • 变量未初始化时占用 RAM 但不会占用 Flash,因为 Flash 里没有初始值。
  • 示例
int global_var;        // 存放在 .bss 段,自动初始化为 0
static int static_var; // 也是 .bss 段,自动初始化为 0

3.4 堆(Heap)和栈(Stack)

​ 除了上述四个标准段,MCU 运行时还包含 堆(Heap)栈(Stack),它们都位于 RAM,但用法不同。

存储区域作用增长方向
堆(Heap)存放 malloc() 动态分配的数据向上增长
栈(Stack)存放局部变量、函数调用返回地址向下增长

示例:

void foo(void) {
    int local_var = 10;    // local_var 在栈(stack)
    int *ptr = malloc(10); // malloc 分配的内存在堆(heap)
}

4. 代码优化建议

4.1 尽可能使用 const 修饰全局变量:

  • 让编译器将变量放入 Flash(节省 RAM)。
const int lookup_table[] = {1, 2, 3, 4}; // 存在 Flash,而不是 RAM

4.2 避免全局变量初始化为 0

  • 让变量默认存放到 .bss,减少 Flash 和 RAM 的同时占用。
int a = 0;  // 额外占用 Flash
int b;      // 更节省空间,存放在 .bss,自动初始化为 0

4.3 避免 malloc(),使用静态分配

  • 不要频繁使用 malloc(),嵌入式系统中 RAM 资源有限,推荐使用静态数组。
// 避免动态分配,改为静态数组
int buffer[256];

5. 总结

Code(.text):存放程序代码(存储在 Flash)。

RO-data(.rodata):存放只读数据(存储在 Flash)。

RW-data(.data):存放已初始化的全局变量(Flash 复制到 RAM)。

ZI-data(.bss):存放未初始化的全局变量(RAM,上电后自动清零)。

Heap(堆)malloc() 动态分配内存(RAM)。

Stack(栈):存放局部变量、函数调用(RAM)。

ROM (Flash) size = Code + Ro-data + rw-data;

RAM size = Rw-data + zi-data

在现代嵌入式开发领域,通过了解客户需求和电子产品趋势,搜集市面上大量的不同型号的MCU资料,结合市场上刚出现的低成本高性能MCU新产品,是成功进行MCU选型的基础。一般来说,嵌入式系统开发人员在选择MCU时,通常遵循四项主要标准∶功能、可用性、成本和熟悉程度。   微控制器(Microcontroller;MCU)是一种无所不在的嵌入式控制晶片,玩具、家电、医疗、汽车等领域都有其存在,负责各种感测、监控工作,例如我们常见的电饭煲、电磁炉、咖啡壶等内部均由MCU负责感测水温,并接受使用者的指示是否该加温、沸腾,同样的冷气机的温控也是用MCU来实现。此外,如桌上电脑所用的键盘、滑鼠等也各有一颗MCU,负责将敲打的键码、指标的X/Y轴位移偏量等资讯回传给电脑CPU。   对於选择MCU进行设计的系统设计师来说,可获得的大量的不同型号MCU会让选型工作变得复杂,如SiliconLabs工作电压低至0.9V的8位元MCU,德州仪器针对低功耗应用的多款16位元MSP430,飞思卡尔和英飞针对汽车应用的MCU方案,Atmel的AVR系列和Microchip的PIC系列一直在推陈出新……虽然新的32位ARM核Cortex-m3处理器已经发布许久,古老的8位8051核还是在不同MCU中占领主流地位……面对缤纷多彩的MCU世界,正确把握MCU发展趋势,熟悉MCU架构,甚至於借助选择工具进行分析比较就显得极其必要。   MCU的主要分类:   按用途分类:   通用型:将可开发的资源(ROM、RAM、I/O、EPROM)等全部提供给用户。 专用型:其硬件及指令是按照某种特定用途而设计,例如录音机机芯控制器、打印机控制器、电机控制器等。 按其基本操作处理的数据位数分类:   根据总线或数据暂存器的宽度,单片机又分为1位、4位、8位、16位、32位甚至64位单片机。   4位MCU大部份应用在计算器、车用仪表、车用防盗装置、呼叫器、无线电话、CD播放器、LCD驱动控制器、LCD游戏机、儿童玩具、磅秤、充电器、胎压计、温湿度计、遥控器及傻瓜相机等;8位MCU大部份应用在电表、马达控制器、电动玩具机、变频式冷气机、呼叫器、传真机、来电辨识器(CallerID)、电话录音机、CRT显示器、键盘及USB等;8位、16位单片机主要用于一般的控制领域,一般不使用操作系统,16位MCU大部份应用在行动电话、数字相机及摄录放影机等;32位MCU大部份应用在Modem、GPS、PDA、HPC、STB、Hub、Bridge、Router、工作站、ISDN电话、激光打印机彩色传真机;32位用于网络操作、多媒体处理等复杂处理的场合,一般要使用嵌入式操作系统。64位MCU大部份应用在高阶工作站、多媒体互动系统、高级电视游乐器(如SEGA的Dreamcast及Nintendo的GameBoy)及高级终端机等。   8位MCU工作频率在16~50MHz之间,强调简单效能、低成本应用,在目前MCU市场总值仍有一定地位,而不少MCU业者也持续为8bitMCU开发频率调节的节能设计,以因应绿色时代的产品开发需求。   16位MCU,则以16位运算、16/24位寻址能力及频率在24~100MHz为主流规格,部分16bitMCU额外提供32位加/减/乘/除的特殊指令。由于32bitMCU出现并持续降价及8bitMCU简单耐用又便宜的低价优势下,夹在中间的16bitMCU市场不断被挤压,成为出货比例中最低的产品。   32位MCU可说是MCU市场主流,单颗报价在1.5~4美元之间,工作频率大多在100~350MHz之间,执行效能更佳,应用类型也相当多元。但32位MCU会因为操作数内存长度的增加,相同功能的程序代码长度较8/16bitMCU增加30~40%,这导致内嵌OTP/FlashROM内存容量不能太小,而芯片对外脚位数量暴增,进一步局限32bitMCU的成本缩减能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值