嵌入式指南:必须掌握的 Flash 知识(超详细)
✅ 适用平台:STM32、NXP、ESP、瑞萨、瑞芯微、高通、全志、Linux系统嵌入式平台
目录
一、引言:Flash,不只是“存储”
在嵌入式系统中,Flash 是与 MCU、UART、GPIO 一样基础但又极为重要的组件之一。它不仅决定着代码的启动速度,也关系到数据的安全存储与系统的稳定性与可维护性。
不少工程师刚入行时只会调用 flash_write()
、flash_read()
等接口,认为 Flash 就是一个大容量的 EEPROM,但当项目遇到问题时,比如:
- 擦除速度过慢
- 数据丢失
- OTA 升级失败导致设备变砖
才意识到,Flash 的理解深度直接决定了项目的可靠性与维护成本。
本文将全面讲解 Flash 的分类、原理、使用、调试、架构设计等,适用于所有嵌入式工程师,从入门到资深必看的“硬核参考手册”。
二、Flash 的基础知识
2.1 什么是 Flash?
Flash 是一种非易失性存储器(Non-Volatile Memory),即断电后依旧能够保存数据。它的出现替代了早期的 PROM、EPROM、EEPROM,具有更高的擦写次数与更低的功耗。
2.2 Flash 的类型
类型 | 特点 | 典型应用 |
---|---|---|
NOR Flash | 地址可线性映射,支持按字节读取 | 启动代码、固件存储 |
NAND Flash | 存储密度高、价格低,擦写以块为单位 | 文件系统、大容量数据 |
SPI Flash | 通信简单(SPI/QSPI),适合 MCU 连接 | 外部程序存储、参数配置存储 |
eMMC/UFS | 控制器集成,常用于高端 SoC、Linux 平台 | Android 设备、Linux 系统主存储 |
2.3 Flash vs EEPROM vs RAM
类型 | 是否掉电保存 | 擦写单位 | 写入速度 | 寿命 | 适用场景 |
---|---|---|---|---|---|
RAM | 否 | 字节 | 极快 | 无限制 | 运行时临时变量 |
EEPROM | 是 | 字节 | 慢 | 10万次左右 | 少量配置参数存储 |
Flash | 是 | 块/页 | 中等 | 10万~100万次 | 程序存储、文件系统等 |
三、常见 Flash 类型详解
3.1 NOR Flash
- 优点:随机访问速度快、掉电数据保存、直接执行(XIP)
- 缺点:容量小、成本高、写入慢
- 应用:Bootloader、嵌入式系统固件(裸机代码)
3.2 NAND Flash
- 优点:容量大、成本低、擦写速度快
- 缺点:易坏块、不支持 XIP、需要文件系统支持
- 应用:Linux 根文件系统、摄像头录像、日志缓存等
3.3 SPI NOR Flash(含 QSPI)
- 接口:SPI(标准/高速),常见容量 1MB~128MB
- 用法:挂在 MCU SPI 接口,用作代码、数据存储
- 例子:W25Q64,GD25Q系列
3.4 eMMC 与 UFS
- 集成控制器,自动管理坏块、擦除、均衡等
- 广泛应用于安卓、Linux、高端智能设备中
- 操作方式与 SD 卡相似,支持分区、扩容
四、嵌入式系统中的 Flash 应用
4.1 Flash 中存储的内容
- Bootloader(启动加载程序)
- 应用程序(firmware.bin)
- 配置参数(config)
- 文件系统(FAT、LittleFS)
- 校验码、签名信息、出厂校验数据
4.2 Flash 在启动流程中的作用
- 复位后 MCU 从固定地址(如0x08000000)执行
- Bootloader 读取 main FW 地址跳转执行
- OTA 升级通过双备份机制切换 Flash 区域
4.3 外挂 SPI Flash 的使用方式
- 通过 SPI/QSPI 接口访问
- 配置 memory map 支持 XIP(执行 in place)
- 通常用于程序更新、日志保存、摄像头缓存等
4.4 Flash 的地址映射与扇区划分
+----------------------+ 0x00000000
| Bootloader (16KB) |
+----------------------+
| App A (128KB) |
+----------------------+
| App B (128KB) |
+----------------------+
| Param config (4KB) |
+----------------------+
| OTA Backup (128KB) |
+----------------------+
五、Flash 的擦写原理
5.1 擦除机制
- Flash 按“块”或“扇区”擦除,一般为 4KB、64KB
- 擦除后的默认值是全 1(0xFF)
5.2 写入机制
- 写入只能从 1 -> 0,不能从 0 -> 1
- 写前需擦除,或先读、合并、再写入(称为读改写)
5.3 写保护机制
- 写保护位 + 寄存器控制 + 电压使能
- 可用于保护 Bootloader、参数区
5.4 Flash 编程 API 示例(STM32 HAL)
HAL_FLASH_Unlock();
FLASH_Erase_Sector(FLASH_SECTOR_2, VOLTAGE_RANGE_3);
HAL_FLASH_Program(TYPEPROGRAM_WORD, 0x08008000, data);
HAL_FLASH_Lock();
六、Flash 文件系统简介
6.1 为什么需要 Flash 文件系统?
- 原始地址操作容易出错
- 便于组织数据
- 自动磨损均衡 + 错误恢复
6.2 常见 Flash 文件系统对比
文件系统 | 支持Flash | 磨损均衡 | 出错恢复 | 功能复杂度 |
---|---|---|---|---|
FATFS | 部分支持 | 否 | 差 | 中等 |
SPIFFS | 是 | 弱 | 一定程度 | 简单 |
LittleFS | 是 | 强 | 强 | 中等 |
6.3 LittleFS 应用案例
lfs_config cfg = {
.read = user_flash_read,
.prog = user_flash_prog,
.erase = user_flash_erase,
.sync = user_flash_sync,
.block_size = 4096,
.block_count = 128,
};
lfs_mount(&lfs, &cfg);
七、Flash 的稳定性与优化
7.1 写放大(Write Amplification)
- 每次写入都伴随额外擦除
- 建议使用缓存区减少小块频繁写入
7.2 磨损均衡(Wear Leveling)
- 避免某块频繁擦写提前损坏
- 文件系统内部自动实现或使用中间层
7.3 掉电保护
- 写前备份 + CRC + 双缓冲策略
- 避免电压不稳导致写入失败
八、嵌入式项目中的实际案例
8.1 Bootloader + 双备份 OTA 升级
[Slot A] App 固件
[Slot B] OTA 接收区
Boot 时比对 CRC -> 决定运行哪个
8.2 Flash 扇区划分策略
- 常用策略:静态 + 动态分区结合
- 参数配置尽量独立划区,避免随应用一起擦除
8.3 生产出厂校验值存储方案
- 存储 SN/校验码/MAC
- 只写一次区块,可设置永久写保护
九、调试与问题排查经验
9.1 写入失败常见原因
- 未解锁 Flash 写保护
- 寄存器未检查“Busy”位
- 写入地址错误
9.2 擦除卡死调试技巧
- 使用串口 log + step debug
- 检查 flash driver 的 HAL 层是否忙等待死循环
9.3 数据掉失排查思路
- 写入未完成断电
- 电压不稳(需加电容)
- Flash 寿命用尽(查看 ID & 状态寄存器)
十、总结与实践建议
- 选择 Flash:NOR 适合代码,NAND 适合大数据,SPI Flash 性价比高
- 划分策略:Bootloader、主程序、参数、日志分区
- 封装驱动:统一 Flash 读写接口(适配多平台)
- 使用文件系统:推荐 LittleFS,适合嵌入式项目
- 注意电源保护:加稳压电容 + 软件掉电保护
📚 推荐阅读与工具
- ST HAL Flash Programming Manual
- 《Embedded Systems Handbook》第二版
- LittleFS 官方文档:https://github.com/littlefs-project/littlefs
- Flash ID/寿命检测工具:Flashrom、OpenOCD、ST-Link