2、Flash存储器详解:NOR Flash vs NAND Flash、内部结构(浮栅晶体管)、编程/擦除原理、读/写/擦除时序、磨损均衡与寿命管理
好,咱们今天来啃一块硬骨头——Flash存储器。说实话,Flash是嵌入式系统里最让人又爱又恨的器件。爱它,是因为它掉电不丢数据;恨它,是因为它的写入和擦除机制实在太“拧巴”了。我刚开始做产品时,就因为没搞懂NOR和NAND的区别,导致一个批量项目差点翻车。嗯,今天咱们就把这事彻底捋清楚。
2.1 NOR Flash vs NAND Flash:核心差异
先问一个问题:你选Flash时,第一反应是什么?很多人直接看容量和价格。但我觉得,你得先搞清楚你的CPU是怎么“接”这个Flash的。
NOR Flash和NAND Flash,名字听起来像兄弟,但脾气完全不同。我习惯用一个比喻:NOR像你书桌上的内存条,可以随机访问任意地址;NAND像仓库里的货架,你得先找到货架号,再一箱一箱翻。
| 对比项 | NOR Flash | NAND Flash |
|---|---|---|
| 读取方式 | 随机访问,类似SRAM | 页读取,先发命令再读 |
| 写入单位 | 字节/字 | 页(通常2KB/4KB) |
| 擦除单位 | 扇区(64KB~128KB) | 块(128KB~512KB) |
| 典型容量 | 1MB~64MB | 128MB~8GB+ |
| 读速度 | 快(~100ns随机访问) | 较快(页读取~25μs) |
| 写速度 | 慢(~10μs/字节) | 快(~200μs/页) |
| 擦除速度 | 慢(~1s/扇区) | 快(~2ms/块) |
| 坏块管理 | 无(出厂无坏块) | 必须有(出厂可能有坏块) |
| 典型用途 | 代码就地执行(XIP) | 大容量数据存储 |
你看,NOR最大的优势是“随机读快”,所以很多MCU直接把程序放在NOR里跑,这叫XIP(eXecute In Place)。而NAND呢,它天生就是为“大容量顺序读写”设计的,比如存音频、视频、文件系统。
关键选型原则:如果你的代码需要直接在Flash里运行,选NOR;如果你要存几百兆的数据,选NAND。别想着用NAND跑代码,那延迟会让你崩溃的。
2.2 内部结构:浮栅晶体管
Flash能存数据,靠的是一个叫“浮栅晶体管”的东西。说白了,就是在普通MOS管的栅极下面,又加了一层“浮空的栅极”。这层浮栅被二氧化硅绝缘层包裹着,电荷一旦进去,就很难跑出来。
为什么会这样?你想想看,普通MOS管断电后,栅极上的电荷很快就泄放了。但浮栅不一样,它被绝缘层“关”在里面,电荷可以保持10年以上。这就是Flash“非易失性”的物理基础。
我画了一张图,帮你理解这个结构:
这张图里,控制栅接字线,源极和漏极接位线。当你在控制栅上加高压,电子会穿过隧穿氧化层,注入到浮栅里。这个过程叫“编程”。反过来,把浮栅里的电子拉出来,叫“擦除”。
我的经验:浮栅里的电子数量决定了晶体管的阈值电压。有电子时,阈值电压升高,读出来是“0”;没电子时,阈值电压低,读出来是“1”。所以Flash的初始状态(擦除后)是“1”,写入是往“0”写。这个逻辑跟很多人的直觉相反,我第一次用的时候也搞反了。
2.3 编程/擦除原理
编程和擦除,说白了就是“注入电子”和“拉出电子”两个动作。但这两个动作都需要高压,通常是8V~20V。所以Flash芯片内部都有电荷泵,把VCC升上去。
编程(Program):Fowler-Nordheim隧穿
在控制栅上加高压(比如+12V),源极和漏极接地。电子从沟道穿过隧穿氧化层,注入浮栅。这个过程很慢,但很精确。NOR可以按字节编程,NAND只能按页编程。
擦除(Erase):也是Fowler-Nordheim隧穿,但方向相反
在控制栅上加负高压(比如-12V),或者把源极接正高压。电子从浮栅被拉回沟道。擦除必须按扇区(NOR)或块(NAND)进行,不能只擦一个字节。
注意:每次编程和擦除都会对隧穿氧化层造成损伤。氧化层一旦击穿,这个存储单元就废了。NOR的擦写寿命通常是10万次,NAND是1万~10万次(取决于工艺)。
2.4 读/写/擦除时序
搞嵌入式,光懂原理不够,还得会看时序图。我挑几个关键点说说:
NOR Flash读取时序
NOR的读操作跟SRAM很像:给地址,等一段时间(比如70ns),数据就出来了。所以很多MCU可以直接把NOR映射到地址空间里,用普通的读指令就能访问。
- CE#(片选):拉低,选中芯片
- OE#(输出使能):拉低,打开数据输出缓冲
- WE#(写使能):读操作时保持高电平
- 地址建立时间:地址必须在CE#和OE#之前稳定
- 数据输出时间:从OE#拉低到数据有效,通常几十纳秒
NAND Flash读取时序
NAND的读操作就复杂多了。你得先发命令(0x00),再发地址(5个字节),再发命令(0x30),然后等芯片准备好(RB#引脚变高),最后读数据。
// NAND Flash页读取伪代码
void nand_read_page(uint32_t page_addr, uint8_t *buffer) {
// 1. 发送读命令
nand_send_cmd(0x00);
// 2. 发送5字节地址(列地址+行地址)
nand_send_addr(page_addr);
// 3. 发送确认命令
nand_send_cmd(0x30);
// 4. 等待芯片就绪(RB#引脚)
while (!nand_is_ready());
// 5. 读取一页数据(通常2KB)
for (int i = 0; i < PAGE_SIZE; i++) {
buffer[i] = nand_read_byte();
}
}
你看,NAND的读操作需要“命令-地址-等待-数据”四个步骤,比NOR麻烦多了。但NAND的页读取速度很快,一页2KB的数据,大约25μs就能读完。
2.5 磨损均衡与寿命管理
这是Flash开发里最容易被忽视,但也是最致命的问题。Flash的擦写次数是有限的,如果你总是往同一个地址写数据,那个块很快就报废了。
我遇到过最惨的一次:一个数据采集设备,每秒钟往Flash里写一次日志。用了三个月,Flash就坏了。后来一查,发现日志文件一直写在同一个块上,那个块被擦了100多万次,远超NAND的寿命极限。
磨损均衡(Wear Leveling)
说白了,就是把写操作均匀地分布到所有块上。有两种策略:
- 动态磨损均衡:每次写数据时,选一个擦除次数最少的块来写。适合小数据量的频繁写入。
- 静态磨损均衡:连那些长期不动的“冷数据”块,也定期搬走数据,把擦除机会让给它们。适合大容量存储。
坏块管理(Bad Block Management)
NAND出厂时就可能有坏块,而且使用过程中还会产生新的坏块。所以必须用坏块表来记录哪些块不能用。我习惯在系统初始化时扫描所有块,把坏块标记出来,以后读写都跳过它们。
寿命估算公式:
总写入量 = 容量 × 擦写次数 × 磨损均衡效率
举个例子:一块1GB的NAND,擦写次数1万次,磨损均衡效率90%。那么总写入量 = 1GB × 10000 × 0.9 = 9TB。如果你的系统每天写100MB,那这块Flash能用9TB / (100MB/天) ≈ 90,000天 ≈ 246年。嗯,实际上因为坏块和损耗,实际寿命会短一些,但足够用了。
我的建议:如果你的系统需要频繁写小数据(比如日志、配置参数),别直接操作Flash。用一个小型文件系统,比如LittleFS或SPIFFS,它们内置了磨损均衡和坏块管理。我自己在IoT项目里一直用LittleFS,省心很多。