2. Flash 硬件基础:存储原理与操作特性

在嵌入式系统中,Flash 是存储固件、文件系统和日志的主要非易失性存储器。Zephyr RTOS 的存储驱动层直接与 Flash 硬件交互,理解 NOR 与 NAND 的差异、页/扇区/块的分层结构以及擦写寿命限制,是正确配置分区表和调试读写异常的前提。

学习目标:掌握 Flash 的物理存储单元层级、两种主流技术的读写特性差异,以及擦除操作对寿命的影响,为后续 Zephyr Flash API 调用和分区表设计打下硬件认知基础。

2.1 NOR 与 NAND:两种主流 Flash 技术

Flash 存储器根据存储单元连接方式分为 NOR 和 NAND 两大架构,它们在读取速度、写入方式、密度和可靠性上存在本质区别。

对比维度 NOR Flash NAND Flash
存储单元连接 并联,每个单元独立连接位线 串联,多个单元共享位线
读取方式 随机访问,类似 SRAM,支持 XIP(片上执行) 页读取,需先加载到 SRAM 缓冲区
写入(编程) 按字节/字编程,速度较慢 按页编程,批量写入速度快
擦除粒度 扇区(通常 4KB~64KB) 块(通常 128KB~1MB)
密度 低(1MB~256MB) 高(512MB~1TB)
位反转率 极低,可靠性高 较高,需要 ECC 纠错
典型用途 固件存储、Bootloader、代码执行 大容量数据存储、文件系统、日志
Zephyr 场景提示:NOR Flash 常用于存放 Zephyr 固件镜像(支持 XIP 直接运行),而 NAND Flash 多用于存储文件系统(如 LittleFS)或 OTA 升级包暂存区。驱动层通过 struct flash_driver_api 统一抽象两种硬件。

下图展示了 NOR 与 NAND 在系统级存储架构中的典型位置与访问路径差异:

NOR vs NAND Flash 系统访问路径对比 NOR Flash 架构 CPU / MCU NOR Flash XIP 总线 特点:随机读取、字节编程、扇区擦除 典型:固件镜像、Bootloader、参数存储 NAND Flash 架构 CPU / MCU NAND Flash 页读取 特点:页读取、页编程、块擦除、需 ECC 典型:文件系统、OTA 包、日志数据

2.2 页、扇区、块:Flash 存储单元层级

Flash 不是按字节直接覆盖写入的,其物理结构决定了必须遵循“擦除-写入”的层级规则。理解页(Page)、扇区(Sector)和块(Block)的关系是配置分区表的基础。

  • 页(Page):最小的读取和编程(写入)单位。NOR 页通常为 256B~1KB,NAND 页通常为 2KB~16KB。写入前对应区域必须处于擦除状态(全 1)。
  • 扇区(Sector):NOR Flash 的最小擦除单位,通常为 4KB、32KB 或 64KB。擦除操作将整个扇区恢复为全 1。
  • 块(Block):NAND Flash 的最小擦除单位,由多个页组成(通常 64~256 页),大小 128KB~1MB。擦除操作以块为单位执行。
常见误区:不要将“扇区”和“块”混用。在 Zephyr 的 DTS 分区描述中,NOR 设备使用 sectors 属性定义擦除粒度,而 NAND 设备使用 blocks 属性。错误配置会导致擦除操作越界或遗漏。

下图以 4KB 扇区 NOR 和 128KB 块 NAND 为例,展示存储单元层级关系:

Flash 存储单元层级对比 NOR Flash 层级 芯片(Chip) 扇区(Sector)— 最小擦除单位(4KB~64KB) 页(Page)— 最小编程单位(256B~1KB) 字节(Byte)— 最小读取单位 NAND Flash 层级 芯片(Chip) 块(Block)— 最小擦除单位(128KB~1MB) 页(Page)— 最小编程/读取单位(2KB~16KB) 字节(Byte)— 通过页缓冲区访问

2.3 擦写寿命与磨损均衡

Flash 存储单元的擦写次数是有限的,超过寿命后单元将无法可靠存储数据。这是分区设计和文件系统选型时必须考虑的关键约束。

Flash 类型 典型擦写寿命(P/E Cycles) 失效模式 Zephyr 应对策略
NOR(浮栅) 10,000 ~ 100,000 编程失败、读取位反转 分区静态分配,避免频繁擦写同一扇区
NAND(SLC) 50,000 ~ 100,000 块坏块、ECC 纠错失败 坏块管理(BBT)+ 磨损均衡算法
NAND(MLC) 3,000 ~ 10,000 块坏块、数据保持能力下降 文件系统层磨损均衡(如 LittleFS)
NAND(TLC) 1,000 ~ 3,000 块坏块、读取干扰 必须配合硬件 ECC 和软件磨损均衡
Zephyr 实践要点:对于频繁写入的分区(如日志分区),建议使用支持磨损均衡的文件系统(如 LittleFS 或 NVS)。在 DTS 中为不同分区设置不同的 erase-size 和 write-size,可以优化擦写寿命管理。

2.4 读、写、擦除操作特性

Flash 的三种基本操作在电气特性和时序上差异显著,驱动层必须严格遵循硬件手册的约束。

2.4.1 读取操作

  • NOR:支持随机地址读取,延迟低(~100ns),可直接映射到 CPU 地址空间执行代码。
  • NAND:以页为单位读取,先将整页数据加载到内部 SRAM 缓冲区,再通过 I/O 接口输出,延迟较高(~25μs 首次访问)。
  • Zephyr API:flash_read() 函数封装了底层读取流程,对于 NOR 设备直接内存映射,对于 NAND 设备自动处理页加载。

2.4.2 写入(编程)操作

  • 前提条件:目标区域必须处于擦除状态(所有位为 1)。写入操作只能将 1 变为 0,不能将 0 变回 1。
  • NOR:支持按字节或字编程,编程时间约 10μs~1ms 每字节。
  • NAND:以页为单位编程,编程时间约 200μs~1ms 每页。
  • Zephyr API:flash_write() 在写入前不会自动擦除,开发者需先调用 flash_erase()。

2.4.3 擦除操作

  • 作用:将指定区域的所有位恢复为 1,是 Flash 唯一能“复位”位状态的操作。
  • NOR:按扇区擦除,时间约 100ms~1s 每扇区。
  • NAND:按块擦除,时间约 1ms~10ms 每块。
  • Zephyr API:flash_erase() 需要传入起始偏移和大小,大小必须是擦除单位的整数倍。
关键约束:擦除操作是 Flash 寿命消耗的主要来源。每次擦除都会减少 P/E 周期计数。在调试阶段,避免在循环中反复擦除同一扇区/块,否则可能快速耗尽寿命导致硬件损坏。

下图总结了 Flash 三种操作的基本流程与约束关系:

Flash 读/写/擦除操作流程 读取(Read) 发送读取地址 数据输出到总线 无需擦除,不耗寿命 写入(Program) 检查区域是否已擦除 按页/字节编程 只能 1→0,不能 0→1 擦除(Erase) 按扇区/块擦除 所有位恢复为 1 消耗 P/E 寿命

2.5 Zephyr 中的 Flash 硬件抽象

Zephyr 通过 struct flash_driver_api 将上述硬件特性抽象为统一的驱动接口。开发者无需关心底层是 NOR 还是 NAND,只需调用标准 API 即可。

// Zephyr Flash 驱动核心 API 结构体(简化)
struct flash_driver_api {
    int (*read)(const struct device *dev, off_t offset, void *data, size_t len);
    int (*write)(const struct device *dev, off_t offset, const void *data, size_t len);
    int (*erase)(const struct device *dev, off_t offset, size_t size);
    const struct flash_parameters *(*get_parameters)(const struct device *dev);
    int (*page_layout)(const struct device *dev, const struct flash_pages_layout **layout, size_t *layout_size);
};
调试建议:在 Zephyr 中通过 flash_get_parameters() 获取 flash_parameters 结构体,可以读取 write_block_size、erase_value(擦除后默认值,通常为 0xFF)和 capacity 等硬件参数,用于验证分区配置是否正确。