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 硬件体系的核心关系画出来了,建议你保存下来对照着看:

Flash 硬件知识体系 Flash 硬件知识体系 Flash 存储器 NOR Flash NAND Flash 支持 XIP · 随机读取 · 高寿命 页读取 · 高密度 · 需坏块管理 接口模式 SPI(1 位数据) QSPI(4 位数据) XIP(片内执行) 核心设计原则 Bootloader 放在 NOR Flash 中,利用 XIP 直接执行 固件镜像和文件系统放在 NAND Flash 中,利用高密度存储 QSPI 接口用于加速固件加载,减少启动时间

3.6 小结

这一章我们聊了 Flash 硬件的几个核心概念。NOR 和 NAND 各有各的脾气,SPI 和 QSPI 决定了数据传输的带宽,XIP 模式让代码直接在 Flash 上跑。这些知识是后面做分区策略的基础。