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 在系统级存储架构中的典型位置与访问路径差异:
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 为例,展示存储单元层级关系:
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 三种操作的基本流程与约束关系:
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 等硬件参数,用于验证分区配置是否正确。