Flash 硬件基础:NOR vs NAND、SPI 接口与 XIP 模式
做 Bootloader 这些年,我最大的体会就是:不懂 Flash 硬件,就别谈分区策略。你想想看,Bootloader 本身就是跑在 Flash 上的第一段代码,如果连存储介质的脾气都没摸透,后面所有设计都是空中楼阁。
这一章,咱们就把 Flash 的底牌翻出来看看。我会结合自己踩过的坑,把 NOR、NAND、SPI、QSPI、XIP 这些概念一次性讲透。
3.1 NOR Flash vs NAND Flash:两种截然不同的存储哲学
先问个问题:为什么嵌入式系统里同时存在 NOR 和 NAND 两种 Flash?
答案很简单——它们是为不同场景设计的。NOR 追求随机读取速度和代码执行能力,NAND 追求存储密度和成本。我当年第一次接触 NAND 时,差点被它的坏块管理搞崩溃,后来才明白这就是它的天性。
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取方式 | 随机读取,类似 SRAM | 页读取,类似硬盘 |
| 执行代码 | 支持 XIP(片内执行) | 不支持 XIP,需拷贝到 RAM |
| 擦除粒度 | 扇区(通常 4KB~64KB) | 块(通常 128KB~512KB) |
| 写入速度 | 慢(~1MB/s) | 快(~10MB/s) |
| 寿命 | 10万~100万次擦写 | 1万~10万次擦写 |
| 坏块管理 | 无需管理 | 必须管理 |
| 典型容量 | 1MB~256MB | 128MB~8GB+ |
| 成本 | 高 | 低 |
核心结论:Bootloader 通常放在 NOR Flash 里,因为上电就能直接跑。NAND Flash 更适合存文件系统、固件镜像这类大块数据。
3.2 SPI Flash 接口:嵌入式系统的标配
现在市面上绝大多数 MCU 都外挂 SPI Flash。为什么?因为便宜、引脚少、布线简单。我做过一个项目,PCB 上就 6 根线连 Flash,省下来的 GPIO 全给了传感器。
SPI Flash 的典型引脚:
- CS(片选)—— 拉低选中设备
- SCLK(时钟)—— 最高可达 133MHz
- MOSI(主出从入)—— 发送命令和地址
- MISO(主入从出)—— 接收数据
操作 SPI Flash 的基本流程,说白了就是发命令、等结果、读数据。我习惯用下面这个伪代码来理解:
// SPI Flash 读数据流程
spi_cs_low(); // 选中 Flash
spi_send_byte(CMD_READ); // 发送读命令 0x03
spi_send_byte(addr_high); // 发送 24 位地址
spi_send_byte(addr_mid);
spi_send_byte(addr_low);
for (i = 0; i < length; i++) {
data[i] = spi_receive_byte(); // 读取数据
}
spi_cs_high(); // 释放 Flash
经验之谈:SPI Flash 的读命令 0x03 是标准命令,所有厂商都支持。但写操作前必须先擦除,这个坑我见过太多人踩了。
3.3 QSPI 与 XIP 模式:让 Flash 跑得更快
标准 SPI 一次只传 1 位数据,太慢了。QSPI(Quad SPI)用 4 条数据线同时传,速度直接翻 4 倍。我做过一个项目,用 QSPI 把固件加载时间从 3 秒降到了 0.8 秒,用户体验提升很明显。
QSPI 有两种常用模式:
- 单线模式:兼容标准 SPI,用于初始化
- 四线模式:4 条数据线同时传输,适合批量读写
说到 XIP(Execute In Place),这可是 NOR Flash 的杀手锏。代码直接在 Flash 上执行,不用拷贝到 RAM。我刚开始做 Bootloader 时,觉得 XIP 很神奇,后来发现原理其实很简单——NOR Flash 的读取延迟足够低,CPU 可以直接取指。
注意:XIP 模式下不能同时写 Flash。如果你在 XIP 区域执行代码,又想擦写同一片 Flash,系统会直接崩溃。我当年调试时就被这个问题折磨了两天。
3.4 Flash 的读写擦除特性:你必须知道的三个事实
Flash 的操作有三个基本动作:读、写、擦除。但它们的特性完全不同。
3.4.1 读操作
读是最简单的。NOR Flash 可以随机读任意地址,NAND Flash 必须按页读。SPI Flash 的读速度取决于时钟频率和模式。
3.4.2 写操作
写操作有个关键限制:只能把 1 写成 0,不能把 0 写成 1。要把 0 变回 1,必须先擦除。这就是为什么写之前必须擦除。
3.4.3 擦除操作
擦除是最慢的操作,以扇区或块为单位。一个 4KB 扇区的擦除时间通常在 50ms~500ms 之间。我做过一个产品,OTA 升级时擦除整个 Flash 花了 3 秒,用户差点以为死机了。
// Flash 擦除流程(以 SPI NOR Flash 为例)
spi_cs_low();
spi_send_byte(CMD_WRITE_ENABLE); // 写使能
spi_cs_high();
spi_cs_low();
spi_send_byte(CMD_SECTOR_ERASE); // 扇区擦除命令 0x20
spi_send_byte(addr_high);
spi_send_byte(addr_mid);
spi_send_byte(addr_low);
spi_cs_high();
// 等待擦除完成
while (spi_read_status() & BUSY_BIT) {
// 轮询忙标志
}
避坑指南:我曾经在擦除过程中掉电,导致 Flash 状态机卡死。后来我加了一个超时机制,超过 1 秒还没完成就复位 Flash。这个经验让我养成了所有 Flash 操作都加超时的习惯。
3.5 本章知识结构图
下面这张图把 Flash 硬件体系的核心关系画出来了,建议你保存下来对照着看:
3.6 小结
这一章我们聊了 Flash 硬件的几个核心概念。NOR 和 NAND 各有各的脾气,SPI 和 QSPI 决定了数据传输的带宽,XIP 模式让代码直接在 Flash 上跑。这些知识是后面做分区策略的基础。