程序存储器(Flash/ROM)详解:程序烧录过程、代码存储原理、读取速度与功耗
程序存储器,说白了就是单片机里存代码的地方。你写的 C 语言、汇编指令,最终都变成二进制机器码,老老实实躺在这里。断电不丢,上电就能跑——这是它和 RAM 最大的区别。
我个人习惯把 Flash 和 ROM 放在一起讲,因为现在绝大多数单片机用的都是 Flash 工艺。传统掩膜 ROM 基本只在超大批量的玩具、遥控器里还能见到,咱们日常开发碰到的,99% 是 Flash。
程序烧录过程:从 .hex 到芯片内部
你点一下 IDE 里的“下载”按钮,背后发生了什么?我拆成几步说:
- 编译链接:IDE 把你的源代码变成 .hex 或 .bin 文件。这里面已经是纯粹的机器码和地址信息了。
- 通信握手:烧录器(比如 ST-Link、J-Link)通过 SWD 或 JTAG 接口,和芯片内部的 Bootloader 或调试模块建立连接。
- 擦除扇区:Flash 写入前必须先擦除。注意,擦除是按扇区(Sector)来的,不是按字节。一个扇区通常 1KB 到 4KB 不等。
- 逐页写入:擦干净后,烧录器把数据一页一页写进去。页(Page)比扇区小,一般是 256 字节或 512 字节。
- 校验回读:写完后,芯片会把刚写的内容读出来和原始数据比对。不一致就报错。
这里有个关键点:擦除和写入是两回事。擦除是把 Flash 单元全部置为 1(0xFF),写入是把某些位从 1 改成 0。所以你不能直接往一个非空地址写数据——必须先擦再写。
代码存储原理:Flash 内部到底长什么样?
Flash 的核心存储单元是浮栅晶体管。每个单元存 1 位数据,通过控制栅和浮栅之间的电荷来区分 0 和 1。
- 写入(编程):在控制栅上加高电压,热电子穿过氧化层注入浮栅。浮栅带负电,阈值电压升高,管子关断——读出时为 0。
- 擦除:在衬底上加高电压,把浮栅里的电子拉出来。阈值电压降低,管子导通——读出时为 1。
- 读取:在控制栅上加一个中间电压,看管子是否导通。导通就是 1,不导通就是 0。
你想想看,为什么 Flash 有擦写寿命?因为每次注入和拉出电子,都会对氧化层造成不可逆的损伤。一般 NOR Flash 的擦写次数在 1 万到 10 万次之间。NAND Flash 更少,几千次就差不多了。
读取速度与功耗:为什么程序不能跑在 Flash 上?
很多初学者会问:既然程序存在 Flash 里,CPU 直接从 Flash 取指令不行吗?行,但慢。
我列个对比表,你一看就明白:
| 存储器类型 | 读取速度 | 典型功耗(读) | 特点 |
|---|---|---|---|
| NOR Flash | 20-50 ns | 5-15 mA | 可随机访问,指令可直接执行 |
| NAND Flash | 50-100 μs(首次) | 10-20 mA | 按页读取,不能直接执行 |
| SRAM | 5-15 ns | 1-5 mA | 速度快,但掉电丢失 |
| Cache(SRAM) | 2-5 ns | 0.5-2 mA | CPU 内部,极快 |
看到了吧?Flash 的读取速度比 SRAM 慢 3-5 倍。如果 CPU 主频很高(比如 100MHz 以上),直接从 Flash 取指令就会产生等待周期,拖慢整体性能。
那怎么办?现代单片机的做法是:
- 预取缓冲(Prefetch Buffer):CPU 一次从 Flash 读 4 条或 8 条指令,存到内部缓冲里。这样大部分时候 CPU 直接从缓冲取指令,不用等 Flash。
- 指令 Cache:把常用的指令段缓存到 SRAM 里。循环代码尤其受益。
- ART 加速器:ST 在 STM32F4/F7 上用的技术,说白了就是 Flash 接口带了一个 64 位宽的读缓冲,配合自适应预取,能把 Flash 的等效读取速度拉到接近 0 等待。
Flash 读取时序:一个简单的例子
假设你用的单片机主频 72MHz,Flash 读取速度 30ns。一个时钟周期大约是 13.9ns。也就是说,读一次 Flash 需要 2-3 个时钟周期。
如果 CPU 没有预取缓冲,执行一条单周期指令(比如 NOP)需要:
取指令:3 个周期(等待 Flash)
译码:1 个周期
执行:1 个周期
总耗时:5 个周期
有了预取缓冲后,大部分取指令操作从缓冲里拿,只需要 1 个周期:
取指令:1 个周期(从缓冲)
译码:1 个周期
执行:1 个周期
总耗时:3 个周期
性能提升了 40%。这就是为什么现在的单片机都标配预取和 Cache。
SVG 图:Flash 读取与预取缓冲流程
功耗优化:Flash 读取的隐藏成本
很多人以为 Flash 读取功耗很低,其实不然。Flash 内部有电荷泵,需要把电压升到 4.5V 左右才能读取。电荷泵的效率通常在 50%-70% 之间,剩下的能量都变成热量散掉了。
我总结几个降低 Flash 读取功耗的方法:
- 降低主频:频率越低,Flash 读取的等待周期越少,电荷泵的开关损耗也越小。
- 使用睡眠模式:不执行代码时,把 Flash 切换到低功耗待机状态。很多单片机支持 Flash 掉电模式,功耗可以降到 μA 级。
- 代码紧凑:把频繁调用的函数放在同一个 Flash 页里,减少跨页读取的额外开销。
- 使用 RAM 执行:对时间敏感的部分代码,复制到 SRAM 里执行。Flash 可以进入低功耗模式。
嗯,关于程序存储器的内容就这些。Flash 的烧录、存储原理、读取速度和功耗,是嵌入式开发的基础。你理解了这些,后面遇到代码跑飞、烧录失败、功耗异常的问题,就知道从哪里入手了。