2. Flash存储器详解:NAND与NOR Flash的结构差异与读写特性
Flash存储器,说白了就是咱们单片机系统里最常用的非易失性存储方案。你想想看,掉电不丢数据、可电擦除、可重复编程——这些特性让Flash几乎成了嵌入式系统的标配。但很多人一上来就踩坑:选NAND还是NOR?时序怎么配?功耗怎么控?
我刚开始做嵌入式那会儿,也在这上面栽过跟头。有一次项目急着赶进度,随手选了颗NOR Flash,结果代码量一上去,发现容量根本不够用。后来换NAND,又发现启动加载时序搞不定……嗯,今天咱们就把这两种Flash彻底讲透。
2.1 NOR Flash:随机访问的“内存型”存储
NOR Flash的结构,说白了就是每个存储单元都并联在位线上。这意味着什么?——随机访问。你可以像读SRAM一样,直接给地址,就能读到数据。这也是为什么NOR Flash常被用来存代码,支持XIP(就地执行)。
核心特点:
- 随机读取快:读时序接近SRAM,适合代码直接运行
- 写入慢:擦除和编程都需要较长时间(毫秒级)
- 容量偏小:常见1Mb~256Mb,再大成本就上去了
- 接口简单:并行或SPI,时序容易理解
我在一个工业控制项目里用过SPI NOR Flash存Bootloader。当时选它的原因很简单——上电就能读,不需要初始化复杂的控制器。你想想看,MCU一复位,直接从NOR Flash取指令执行,多省事。
2.2 NAND Flash:高密度的“块设备”存储
NAND Flash的结构就不一样了。它的存储单元是串联的,像一串链条。这种结构让NAND的存储密度极高,成本也低。但代价是什么?——不能随机访问。你必须按页读取,按块擦除。
我做过一个数据记录仪,每天要存几十MB的传感器数据。NOR Flash根本装不下,只能上NAND。但NAND有个麻烦事——坏块管理。出厂就有坏块,用着用着还会产生新坏块。你得自己维护坏块表,做ECC校验。
注意:NAND Flash的读取是页模式(通常2KB~16KB一页),不能像NOR那样按字节随机读。如果你的MCU不支持NAND控制器,用GPIO模拟时序会非常痛苦——我试过,调试了整整一周。
2.3 结构差异对比:一张表说清楚
| 对比项 | NOR Flash | NAND Flash |
|---|---|---|
| 存储单元连接方式 | 并联 | 串联 |
| 读取方式 | 随机访问(字节/字) | 页读取(2KB~16KB) |
| 擦除方式 | 扇区擦除(通常64KB) | 块擦除(通常128KB~512KB) |
| 写入速度 | 慢(~几百μs/字节) | 快(~几百μs/页) |
| 读取速度 | 快(~100ns随机读) | 较快(~25μs页读取) |
| 密度/成本 | 低密度,高成本 | 高密度,低成本 |
| 可靠性 | 高(无坏块问题) | 需坏块管理+ECC |
| 典型应用 | 代码存储、XIP | 大容量数据、文件系统 |
2.4 读写时序:实战中的关键差异
时序这东西,光看数据手册没用,得动手测。我习惯用逻辑分析仪抓波形,一眼就能看出问题。
NOR Flash SPI读时序
以W25Q系列为例,读操作很简单:发命令0x03,跟3字节地址,然后直接读数据。时钟频率可以跑到104MHz。但要注意——连续读模式下,地址会自动递增,这个特性在做代码搬运时特别好用。
// NOR Flash SPI 读操作示例(伪代码)
void nor_read(uint32_t addr, uint8_t *buf, uint32_t len) {
CS_LOW();
spi_send_byte(0x03); // 读命令
spi_send_byte(addr >> 16); // 地址高8位
spi_send_byte(addr >> 8); // 地址中8位
spi_send_byte(addr); // 地址低8位
for (uint32_t i = 0; i < len; i++) {
buf[i] = spi_recv_byte();
}
CS_HIGH();
}
NAND Flash页读取时序
NAND的读时序就复杂多了。你得先发命令0x00,再发5字节地址(列地址+行地址),然后发命令0x30启动读。之后要等tR时间(通常25μs),再读数据。我踩过的坑是——忘了等tR,结果读出来的全是0xFF。
// NAND Flash 页读取示例(伪代码)
void nand_page_read(uint32_t row_addr, uint8_t *buf) {
CLE_HIGH(); ALE_LOW(); // 命令周期
IOx = 0x00; // 读命令1
CLE_LOW(); ALE_HIGH(); // 地址周期
IOx = 0x00; // 列地址低8位
IOx = 0x00; // 列地址高8位
IOx = row_addr & 0xFF; // 行地址低8位
IOx = (row_addr >> 8) & 0xFF;
IOx = (row_addr >> 16) & 0xFF;
CLE_HIGH(); ALE_LOW(); // 命令周期
IOx = 0x30; // 读命令2
// 等待tR(至少25μs)
while (RB_PIN == 0); // 等待就绪
// 读取页数据(假设页大小2KB)
for (int i = 0; i < 2048; i++) {
buf[i] = IOx;
}
}
经验之谈:NAND的RB引脚一定要用中断或硬件定时器检测,别用软件死等。我曾经在一个RTOS项目里用while轮询RB,结果高优先级任务一直被阻塞,系统直接卡死。
2.5 功耗优化:从选型到代码
功耗优化,说白了就是两件事:少读写、快读写。我总结了几条实战经验:
- 选型阶段:关注待机电流和读写峰值电流。NOR Flash待机通常1~5μA,NAND会高一些(10~50μA)。
- 降低时钟频率:不是所有场景都需要最高速。如果数据量不大,把SPI时钟降到10MHz,功耗能降30%。
- 批量操作:NAND尽量用页写入代替单字节写入。一次写一页(2KB)和写一个字节,功耗差不多,但效率差1000倍。
- 深度睡眠:很多Flash支持Deep Power-down模式,电流可以降到1μA以下。我习惯在系统空闲时主动进入。
功耗对比参考:
| 模式 | NOR Flash (W25Q64) | NAND Flash (MT29F2G) |
|---|---|---|
| 待机 | 1~5 μA | 10~50 μA |
| 读操作 | ~15 mA (104MHz) | ~20 mA (页读取) |
| 写操作 | ~20 mA (页编程) | ~25 mA (页编程) |
| 深度睡眠 | ~1 μA | ~5 μA |
2.6 知识结构图:NAND vs NOR 选型决策
下面这张图是我自己总结的选型逻辑。每次做新项目,我都会先过一遍这个流程。
2.7 避坑指南:我踩过的那些坑
坑1:NOR Flash的“伪”随机写
NOR虽然可以随机读,但写操作必须按页(通常256字节)进行。我曾经以为可以像SRAM一样单字节写,结果发现写之前必须先擦除整个扇区。嗯,那次调试花了我一整天。
坑2:NAND Flash的“坏块”陷阱
新买的NAND芯片,出厂就有坏块。我第一次用NAND时没做坏块管理,结果存到某个地址数据就丢了。后来老老实实加了坏块表和ECC校验,再也没出过问题。
坑3:时序余量不足
数据手册上的时序参数都是理想值。实际布线、温度变化、电压波动都会影响。我习惯在时序参数上加20%的余量。比如tR要求25μs,我就等30μs再读数据。多等几微秒,换来的是可靠性。