3、Flash存储器详解:NOR Flash与NAND Flash的区别、SPI Flash接口协议、Flash的读写特性
好,咱们今天聊聊Flash。做嵌入式系统,尤其是消防报警这类需要记录历史数据的设备,Flash是绕不开的。说白了,它就是咱们设备的“黑匣子”。
我个人习惯把Flash分成两大类:NOR Flash和NAND Flash。名字听着像,脾气可差远了。你想想看,一个像内存一样可以随机访问,另一个像硬盘一样按块操作,能一样吗?
3.1 NOR Flash与NAND Flash的区别
先说说NOR Flash。我最早接触它是在做一款工业控制器的时候。它的最大特点就是支持随机读取。什么意思呢?就是你可以像读内存一样,直接给个地址,它就能把数据吐出来。这对于代码执行来说太方便了——很多单片机直接把程序放在NOR Flash里跑,省掉了一颗RAM。
但NOR Flash有个硬伤:写入和擦除速度慢。而且擦除操作通常是以“扇区”为单位的,一个扇区64KB或128KB。你想想看,如果我只想改一个字节,也得先把整个扇区读出来,擦掉,再写回去。这效率,嗯...
再来看NAND Flash。它的存储密度高,成本低,容量可以做得很大。我们消防报警主机里的历史记录,动辄几十万条,用NAND Flash存再合适不过了。
但NAND Flash有个让人头疼的问题:坏块。我在项目中遇到过,一块新的NAND Flash,出厂时就可能有几个坏块。你得在软件里做坏块管理,不然数据写到坏块上就丢了。
我把它们的核心区别整理成了表格,方便你对比:
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取方式 | 随机读取,类似RAM | 页读取,类似硬盘 |
| 写入速度 | 慢(微秒级) | 快(微秒级) |
| 擦除速度 | 慢(秒级) | 快(毫秒级) |
| 存储密度 | 低 | 高 |
| 坏块管理 | 无需 | 必须 |
| 典型应用 | 代码存储、小容量数据 | 大容量数据、文件系统 |
核心结论:如果你的应用需要频繁随机读取,比如直接执行代码,选NOR。如果需要大容量存储,比如历史记录、日志文件,选NAND。
3.2 SPI Flash接口协议
好,选定了Flash类型,接下来怎么跟它通信?在嵌入式领域,最常用的就是SPI接口。SPI Flash,说白了就是通过SPI总线来操作的Flash存储器。它通常是NOR Flash,但也有少数NAND Flash支持SPI接口。
SPI接口有4根线:
- CS(片选):选中设备,低电平有效
- SCLK(时钟):提供同步时钟
- MOSI(主出从入):主机发送数据
- MISO(主入从出):从机发送数据
操作SPI Flash,本质上就是通过这4根线发送命令和数据。每个命令都有一个固定的操作码,比如:
0x03:读取数据0x02:写入数据(页编程)0x20:擦除扇区0xD8:擦除块0x06:写使能
我举个例子,读取数据的流程是这样的:
// 伪代码:读取SPI Flash数据
void spi_flash_read(uint32_t addr, uint8_t *buf, uint32_t len) {
// 1. 拉低CS,选中设备
CS_LOW();
// 2. 发送读取命令 0x03
spi_send_byte(0x03);
// 3. 发送24位地址(高位在前)
spi_send_byte((addr >> 16) & 0xFF);
spi_send_byte((addr >> 8) & 0xFF);
spi_send_byte(addr & 0xFF);
// 4. 连续读取数据
for (uint32_t i = 0; i < len; i++) {
buf[i] = spi_recv_byte();
}
// 5. 拉高CS,释放设备
CS_HIGH();
}
小技巧:我习惯在发送完地址后,先发一个哑字节(0xFF)来触发SPI时钟,然后再读取数据。有些Flash芯片需要这个“空操作”来准备数据。
3.3 Flash的读写特性
这部分是重点,也是很多新手容易踩坑的地方。Flash的读写特性,说白了就是三个字:写前必擦。
为什么?因为Flash的存储单元在出厂时是“1”状态。写入操作只能把“1”变成“0”,但不能把“0”变成“1”。要想把“0”变回“1”,必须执行擦除操作。擦除会把整个扇区或块全部置为“1”。
所以,标准的写入流程是:
- 读取目标扇区的内容到缓冲区
- 在缓冲区中修改需要更改的字节
- 擦除目标扇区
- 将缓冲区内容写回扇区
我曾经在项目中犯过一个低级错误:直接往一个已经写过数据的地址再次写入,结果数据全乱了。后来排查了半天才发现,原来是没有先擦除。嗯,从那以后,我每次写Flash前都会先检查一下状态寄存器,确保写使能已经打开。
另外,Flash的寿命也是个大问题。NOR Flash的擦写次数一般在10万次左右,NAND Flash更少,只有几千到几万次。对于消防报警主机这种需要频繁记录历史数据的设备,如果不做磨损均衡,很快某个扇区就会报废。
注意:千万不要在中断服务函数里直接操作Flash!Flash的写入和擦除操作耗时较长(毫秒级),会严重阻塞中断响应。我建议把Flash操作放到任务循环或后台线程中处理。
最后,说说写保护。很多SPI Flash都有写保护引脚(WP)和状态寄存器中的保护位。我在调试时经常遇到“写不进去”的情况,一查,原来是状态寄存器里的BP位被设置了,把整个芯片保护起来了。解决办法很简单:先发送写使能命令(0x06),然后修改状态寄存器,清除保护位。
好了,关于Flash的这三个核心知识点,我就讲到这里。记住:选型看需求,通信靠SPI,读写先擦除。下一节,咱们聊聊如何在Flash上实现一个简单的文件系统,让历史记录管理更高效。