2. Flash存储器详解:NOR Flash与NAND Flash的区别,读、写、擦除时序与寿命管理
Flash存储器,说白了就是单片机系统里最常用的非易失性存储方案。你想想看,掉电不丢数据、可电擦除、可重复编程,这些特性让它成了嵌入式工程师的标配。但很多新手容易踩坑——把NOR和NAND混着用,或者压根没搞清楚它们的时序差异。
我个人习惯把Flash分成两类:NOR Flash 和 NAND Flash。它们虽然都叫Flash,但内部架构、接口时序、寿命特性完全是两码事。今天咱们就掰开揉碎讲清楚。
2.1 NOR Flash vs NAND Flash:核心差异
先看一张对比表,这是我在项目选型时必看的参考依据:
| 对比项 | NOR Flash | NAND Flash |
|---|---|---|
| 存储单元结构 | 每个单元并联,可随机访问 | 单元串联成页,只能页/块访问 |
| 读取速度 | 快(~100ns随机读) | 慢(~25μs首次读,但连续读快) |
| 写入速度 | 慢(~10μs/字节) | 快(~200μs/页) |
| 擦除速度 | 慢(~1s/扇区) | 快(~2ms/块) |
| 擦写寿命 | 10万~100万次 | 1万~10万次 |
| 坏块管理 | 无需(出厂无坏块) | 必须(出厂就有坏块) |
| 典型应用 | 代码就地执行(XIP)、参数存储 | 大容量数据存储、文件系统 |
为什么NOR能随机读?因为它的存储单元是并联的,每个位线都能独立寻址。而NAND是串联结构,必须先把一整页数据读到内部缓存才能访问。我在一个工业控制器项目里就吃过这个亏——当时想用NAND存启动代码,结果发现CPU根本没法直接从NAND执行指令,最后只能加NOR做启动引导。
关键结论:NOR适合存代码(XIP),NAND适合存数据(大容量)。选型时别搞反。
2.2 读、写、擦除时序详解
时序这东西,光看数据手册容易晕。我习惯把它拆成三个动作来理解:读、写(编程)、擦除。每个动作的时序都不一样,咱们逐个看。
2.2.1 NOR Flash 时序
读操作:NOR的读时序和SRAM很像。你给地址,它给数据,就这么简单。但要注意——读的时候不需要先擦除,直接读就行。
// NOR Flash 读操作示例(假设地址线A[23:0],数据线D[15:0])
uint16_t nor_read(uint32_t addr) {
*(volatile uint16_t *)addr; // 直接地址访问
return *(volatile uint16_t *)addr;
}
写操作:NOR写之前必须保证目标地址是擦除状态(全0xFF)。写操作需要发一串命令序列,比如写使能、写地址、写数据。我记得第一次调NOR驱动时,忘了发写使能命令,结果数据死活写不进去,折腾了半天。
// NOR Flash 写操作示例(以SST39VF160为例)
void nor_write(uint32_t addr, uint16_t data) {
*(volatile uint16_t *)0x5555 = 0xAA; // 解锁序列
*(volatile uint16_t *)0x2AAA = 0x55;
*(volatile uint16_t *)0x5555 = 0xA0; // 编程命令
*(volatile uint16_t *)addr = data; // 写入数据
while(!(*(volatile uint16_t *)addr & 0x80)); // 等待完成
}
擦除操作:NOR擦除以扇区为单位,最小擦除单位通常是4KB或64KB。擦除时间比较长,一个扇区可能要几百毫秒到1秒。我建议在擦除期间关掉看门狗,否则容易触发复位。
注意:NOR擦除期间不能读同一扇区,但可以读其他扇区。这是硬件限制,不是软件能绕开的。
2.2.2 NAND Flash 时序
NAND的时序比NOR复杂得多。它通过I/O口复用地址和数据,所以需要发命令、地址、数据三个步骤。
读操作:NAND读一页数据,需要先发读命令(0x00),再发5个地址周期,然后等芯片准备好(R/B#引脚变高),最后读数据。连续读页时,可以用缓存读命令(0x30)加速。
// NAND Flash 读一页操作示例(以K9F1G08为例)
void nand_read_page(uint32_t page_addr, uint8_t *buf) {
nand_cmd(0x00); // 读命令
nand_addr(page_addr & 0xFF); // 列地址低8位
nand_addr((page_addr >> 8) & 0xFF); // 列地址高8位
nand_addr((page_addr >> 16) & 0xFF); // 行地址低8位
nand_addr((page_addr >> 24) & 0xFF); // 行地址高8位
nand_addr((page_addr >> 32) & 0xFF); // 行地址扩展
nand_wait_ready(); // 等待R/B#变高
for(int i=0; i<2048; i++) {
buf[i] = nand_read_byte(); // 读一页数据(2KB)
}
}
写操作:NAND写页之前,必须先擦除整个块。写页命令是0x80,然后发地址和数据,最后发0x10确认。写完后要检查状态寄存器,确认是否成功。
经验之谈:NAND写页时如果遇到坏块,状态寄存器会返回失败。我一般在写完后读回校验,而不是只信状态寄存器——因为有些坏块连状态寄存器都不可靠。
擦除操作:NAND擦除以块为单位,一个块通常包含64页(128KB)。擦除命令是0x60,发块地址,再发0xD0确认。擦除时间大约2ms,比NOR快得多。
2.3 寿命管理:别让Flash提前报废
Flash的擦写寿命是有限的。NOR能撑10万~100万次,NAND只有1万~10万次。听起来不少,但如果你每秒写一次,NAND可能几天就挂了。我在一个数据采集项目里就遇到过——没做磨损均衡,结果某个块被反复写,不到一个月就坏了。
寿命管理有三大招:
- 磨损均衡(Wear Leveling):把写操作均匀分布到所有块上。NAND必须做,NOR看情况。
- 坏块管理(Bad Block Management):NAND出厂就有坏块,使用中还会产生新坏块。需要维护坏块表,跳过坏块。
- 写缓冲与合并:小数据先缓存,凑够一页再写。减少擦写次数。
下面这张图是我自己总结的Flash寿命管理流程,每次做存储方案时都会参考:
2.4 实战避坑指南
做Flash驱动这么多年,我踩过不少坑。挑几个典型的说说:
- 擦除后不校验:我曾经以为擦除一定成功,结果有一次电源不稳,擦到一半停了。从那以后,我每次擦除完都会读回校验,确认全是0xFF。
- 写操作不检查状态:NOR和NAND写完后都有状态寄存器或忙信号。别偷懒,一定要等操作完成再干别的。
- NAND坏块表放错位置:坏块表本身也要存到NAND里。我建议放在最开始的几个块,并且做冗余备份——万一坏块表所在的块也坏了,还能从备份恢复。
- 忽略电源波动:Flash写/擦除时对电源要求高。如果电源纹波大,容易写错数据。我在一个电池供电的项目里,就加了超级电容做写操作时的能量缓冲。
小技巧:NOR Flash的擦写寿命比NAND长,但也不是无限的。我习惯在系统里记录每个扇区的擦写次数,快接近寿命上限时主动迁移数据。这叫“主动寿命管理”,比等坏了再修靠谱得多。
嗯,Flash这块内容比较多,但核心就三点:选对类型(NOR还是NAND)、搞准时序(读/写/擦除)、做好寿命管理(磨损均衡+坏块管理)。把这三点吃透了,Flash这块基本不会出大问题。