Flash 存储器深度解析:NAND 与 NOR 的区别
说到单片机里的非易失存储,Flash 绝对是主角。很多初学者搞不清 NAND 和 NOR 到底差在哪,我当年也踩过这个坑——第一次用 NAND Flash 做代码存储,结果发现不能直接寻址执行,项目差点延期。今天咱们就把这事彻底说透。
NAND vs NOR:核心差异
这两种 Flash 的架构完全不同。NOR 像一本书,你可以直接翻到任意一页读内容;NAND 像一卷胶片,你得先找到那一段,再整段读出来。说白了,NOR 支持随机访问,NAND 只支持页访问。
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取方式 | 随机访问,类似 RAM | 页读取,类似硬盘 |
| 执行代码 | 支持 XIP(原地执行) | 不支持,需拷贝到 RAM |
| 写入速度 | 慢(字节编程) | 快(页编程) |
| 擦除速度 | 慢(块擦除) | 相对较快 |
| 擦写寿命 | 约 10 万次 | 约 1~10 万次(视工艺) |
| 容量密度 | 低(1~512Mb) | 高(1Gb~1Tb) |
| 成本 | 高 | 低 |
关键结论:NOR 适合存代码(直接跑),NAND 适合存数据(大容量)。STM32 内部 Flash 就是 NOR 架构,所以才能上电直接执行程序。
STM32 内部 Flash 的组织结构
STM32 的 Flash 不是一整块大内存,它分成了扇区、页、块三个层级。我习惯把扇区比作「抽屉」,页是「文件夹」,块是「文件柜」。不同型号的 STM32,这些层级的大小和数量都不一样。
以 STM32F103 为例
F103 的 Flash 分成了多个扇区,每个扇区 1KB 或 2KB。但更常见的 F4/H7 系列,扇区大小就不一样了。咱们看个典型结构:
// STM32F407 的 Flash 扇区划分(1MB 容量)
// 扇区 0~3:16KB 每个
// 扇区 4:64KB
// 扇区 5~11:128KB 每个
// 擦除时最小单位是扇区
// 写入时最小单位是半字(16位)或字(32位)
FLASH_EraseSector(FLASH_Sector_5, VoltageRange_3);
FLASH_ProgramWord(0x08020000, 0x12345678);
我的经验:做 IAP(在线升级)时,一定要搞清楚扇区大小。我曾经把 128KB 的扇区当 16KB 擦,结果把隔壁的代码也擦掉了——那次调试到凌晨三点。
读写寿命与擦除次数
Flash 不是永生的。每次擦写都会对浮栅晶体管造成损伤,就像反复折叠一根铁丝,总有一天会断。STM32 官方给出的寿命指标是:
- 擦写次数:典型值 10,000 次(数据手册保证)
- 数据保持:在 85°C 环境下可保持 20 年
- 读操作:无限次(不影响寿命)
等等,10,000 次听起来很少?其实大多数应用根本用不完。但如果你做数据记录仪,每秒写一次 Flash,那 10,000 秒就废了——不到 3 小时!
避坑指南:我曾经接手过一个项目,设备运行半年后频繁死机。查到最后发现是 Flash 的一个扇区被写爆了——那个扇区存的是系统配置,每次上电都擦写一次。客户每天断电重启好几次,半年就超了寿命。解决方案:用磨损均衡算法,把配置数据分散到多个扇区。
磨损均衡与寿命管理
既然 Flash 有寿命限制,那怎么延长?核心思路就一条:别总盯着一个扇区擦写。
我常用的方法:
- 扇区轮换:维护一个索引表,每次写数据换一个扇区
- 写次数计数:记录每个扇区的擦写次数,优先用次数少的
- 缓存合并:小数据先攒到 RAM,凑够一页再写
// 简单的磨损均衡示例
#define SECTOR_COUNT 10
uint8_t wear_level[SECTOR_COUNT];
uint32_t get_next_sector(void) {
uint32_t min_wear = wear_level[0];
uint32_t sector = 0;
for (int i = 1; i < SECTOR_COUNT; i++) {
if (wear_level[i] < min_wear) {
min_wear = wear_level[i];
sector = i;
}
}
return sector;
}
Flash 操作的关键时序
写 Flash 不是瞬间完成的。STM32 的 Flash 控制器有固定的时序要求:
- 编程时间:约 20~50μs 每半字(16位)
- 扇区擦除时间:约 20~40ms(视扇区大小)
- 质量擦除时间:约 100~200ms(整片擦除)
嗯,这里要注意:擦除期间 CPU 会被阻塞。如果你有实时性要求,得考虑用 DMA 或者把擦除操作放到空闲时间做。
实战建议:做 Flash 驱动时,一定要加超时处理。我见过一个案例,Flash 擦除时电压不稳导致卡死,系统直接挂在那。加个 500ms 超时重试机制,问题就解决了。
Flash 结构图:从扇区到比特
下面这张图展示了 STM32 Flash 的层级关系,从芯片级到存储单元级:
常见故障与排查
Flash 出问题,症状往往很诡异。我总结了几种典型情况:
| 故障现象 | 可能原因 | 排查方法 |
|---|---|---|
| 程序跑飞 | 代码区被意外擦写 | 检查写保护寄存器,确认擦除地址范围 |
| 数据读出全 0xFF | 扇区未编程或已被擦除 | 检查擦除操作是否误触发 |
| 写入失败 | Flash 控制器忙或电压不足 | 检查 BSY 标志位,测量 VDD 是否稳定 |
| 数据偶尔出错 | 擦写寿命耗尽 | 读取擦写计数,检查磨损均衡 |
调试技巧:我习惯在 Flash 驱动里加一个调试接口,可以读出每个扇区的擦写次数和最后写入时间。这样现场出问题时,远程就能定位是不是 Flash 寿命问题。