嵌入式内存架构:Flash、SRAM 与程序启动流程
视频版:嵌入式内存架构详解
嵌入式系统里真正难的不是记住“Flash 存程序、SRAM 存变量”,而是理解从断电保存、地址映射到程序运行之间的完整关系。只有把这条链串起来,才能解释变量为什么消失、程序为什么保留,以及启动代码到底在做什么。
1. 断电后,谁还记得?
程序烧写到 Flash 后,即使断电仍然存在。运行时变量通常放在 SRAM,断电后内容丢失。这是 Flash 与 SRAM 最直观的区别。
- 非易失性:Flash、ROM、EEPROM,断电后保持;
- 易失性:SRAM,断电后丢失。
2. 存储层级:速度、容量与成本的权衡
越靠近 CPU 的存储越快、越小、单位成本越高。典型层级包括寄存器、Cache、SRAM、Flash 和外部存储。
并不是所有 MCU 都有 Cache 或外部存储。层级只是理解访问速度和容量取舍的模型。
3. Flash 单元如何保存电荷
经典 Flash 单元利用浮栅保存电荷,通过电荷改变晶体管阈值,从而表示不同状态。
许多 MCU 可以直接从内部 Flash 取指,不必把全部代码复制到 SRAM。
4. Flash 写入规则:擦除、编程、验证
Flash 的一个关键限制是:擦除后通常读为全 1,编程可以把 1 变为 0,但要把 0 恢复为 1,必须先擦除整个擦除单元。
工程上要注意:
- 擦除单位与编程单位各不相同;
- 写入后最好读回验证;
- 擦写寿命有限,频繁更新要分散到不同位置;
- 必要时使用磨损均衡或外部 EEPROM。
5. SRAM 是运行时工作台
SRAM 用双稳态锁存器存储数据,常见单元由多个晶体管构成。持续供电时不需要像 DRAM 那样周期刷新,普通写入也不需要先擦除。
变量、缓冲区、调用栈和堆通常都放在 SRAM。断电后这些状态全部丢失。
6. ROM 与 EEPROM
- Mask ROM:内容在制造阶段固定,适合内置启动代码等场景;
- EEPROM:支持小量、字节级或页级电擦写,适合配置和校准参数;
- 部分 MCU 使用 Flash 模拟 EEPROM,需要软件管理擦除与磨损。
7. 地址映射:把存储器与外设放进统一空间
内存映射给存储器、外设寄存器和系统组件分配地址。例如 STM32 中 Flash 可以从某个固定基址开始,SRAM 有独立地址段,外设寄存器也占用地址空间。
32 位地址空间可以覆盖 4 GiB,但这只是地址空间,并不代表芯片实际拥有 4 GiB 内存。
8. 链接脚本与程序分段
链接器把程序的不同段安排到不同区域:
.text:代码;.rodata:只读常量;.data:已初始化的可写全局变量;.bss:零初始化全局变量;- 堆和栈:运行时的动态与调用空间。
典型情况下,.data的初值保存在 Flash,启动时复制到 SRAM;.bss只需清零,不必在固件镜像中保存整块零值。
9. 启动初始化:main 之前发生了什么
启动代码通常负责:
- 设置初始栈指针;
- 建立向量表;
- 把
.data初值从 Flash 复制到 SRAM; - 清零
.bss; - 调用运行时初始化;
- 进入
main()。
这解释了为什么“初值为 42 的全局变量”在main中是 42,“未初始化全局变量”在main中是 0。
10. 堆栈与内存预算
函数调用通常建立栈帧,保存局部变量、返回地址和部分寄存器。堆用于动态分配。典型 Cortex-M 布局中,栈向低地址增长,堆向高地址增长,它们共享有限的 SRAM 空间。
优化内存时,要记住:
- 把大数组改成
static只减轻栈压力,不减少 SRAM 总占用; - 只读表通常可以留在 Flash;
- 要测量栈峰值并保留安全余量;
- 不要频繁写非易失性存储器;
- 最终位置要结合链接映射文件确认。
知识链
存储类型 → 地址映射 → 程序分段 → 启动初始化 → 程序运行 → 内存预算与优化总结
内存架构不是存储器属性表,而是程序从固件到运行的完整生命周期。理解 Flash、SRAM、地址、链接器和启动代码之间的关系,就能更清楚地分析内存溢出、数据丢失、启动失败和性能问题。