2. NOR Flash基础:NOR与NAND Flash对比、存储单元结构、Page/Block/Sector概念、读/写/擦除特性
好,咱们正式开始NOR Flash的课程。这一节是基础中的基础,但也是很多新手容易混淆的地方。我个人习惯,在讲任何驱动之前,先把硬件的脾气摸透。NOR Flash和NAND Flash,虽然都叫Flash,但性格完全不同。你想想看,一个像内存一样可以随机访问,另一个像硬盘一样按块操作,这能一样吗?
2.1 NOR与NAND Flash的对比
先来张表,一目了然。我在项目中经常被问到“为什么不用NAND?便宜啊”。嗯,便宜是有代价的。
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取方式 | 随机访问,类似SRAM | 按页读取,类似硬盘 |
| 读取速度 | 快(纳秒级) | 较快(微秒级) |
| 写入速度 | 慢(毫秒级) | 快(微秒级) |
| 擦除速度 | 慢(秒级) | 快(毫秒级) |
| 容量密度 | 低(通常128Mb以下) | 高(可达Gb级) |
| 位翻转 | 极少发生 | 常见,需要ECC |
| 坏块管理 | 无需 | 必须 |
| 接口 | 并行/SPI | 并行/SPI |
| 典型用途 | 代码存储、启动加载 | 大容量数据存储 |
说白了,NOR Flash最大的优势就是“XIP(eXecute In Place)”,也就是代码可以直接在Flash上运行,不用搬到RAM里。这个特性在嵌入式系统里太重要了。我记得有一次做Bootloader,CPU上电后直接就从NOR Flash里取指令执行,省掉了拷贝的步骤,启动速度飞快。
而NAND Flash呢?它更像一个“数据仓库”。容量大、价格低,但读取方式受限。你不能像访问内存一样直接读某个地址,必须按页来。而且NAND天生就有坏块,就像硬盘有坏道一样,需要驱动层做坏块管理和ECC纠错。我刚开始接触NAND时,就被坏块折腾过,后来才明白这是它的“基因缺陷”。
核心区别一句话:NOR是“内存型”存储,适合存代码;NAND是“磁盘型”存储,适合存数据。
2.2 NOR Flash存储单元结构
咱们深入一点,看看NOR Flash里面到底长什么样。它的基本存储单元是一个浮栅晶体管(Floating Gate Transistor)。
你可以把它想象成一个“电子陷阱”。晶体管里有一个浮栅,被绝缘层包裹着。写入数据时,通过高电压把电子注入到浮栅里;擦除时,再把电子拉出来。电子被困在浮栅里,即使断电也不会消失,这就是非易失性的原理。
NOR Flash的存储单元是并联连接的。每个单元的一端接位线(Bit Line),另一端接地。这种结构的好处是:你可以通过地址线直接选中任意一个单元,读取它的状态。这就是“随机访问”的硬件基础。
我的经验:NOR Flash的读取时序非常像SRAM。你给它一个地址,它就能在几十纳秒内把数据吐出来。很多MCU的片内Flash其实就是NOR结构,所以CPU可以直接取指执行。
但并联结构也有代价——每个单元都需要一个独立的接触孔,这占用了大量芯片面积。所以NOR Flash的容量做不大,成本也降不下来。NAND Flash采用串联结构,单元密度高得多,但失去了随机访问能力。这就是工程上的取舍。
2.3 Page、Block、Sector概念
这三个概念,我敢说90%的初学者都搞混过。咱们一个一个理清楚。
- Page(页):这是写入的最小单位。比如你写一个字节,实际上Flash内部会把整个Page都读出来,修改后再写回去。常见的Page大小是256字节或512字节。
- Sector(扇区):这是擦除的最小单位。你不能只擦一个字节,必须擦掉整个Sector。常见大小是4KB或64KB。
- Block(块):通常由多个Sector组成。有些芯片把Block作为擦除单位,有些则直接用Sector。具体要看数据手册。
为什么会这样?因为Flash的物理特性决定了:写入只能把1变成0(编程),而擦除只能把0变成1(恢复)。所以你要修改一个字节,必须先擦除整个Sector,再重新写入。这个“读-改-写”的过程,是Flash驱动设计的核心难点。
避坑指南:我曾经在一个项目里,直接往一个已经写过的地址再次写入数据,结果数据全乱了。后来才意识到,NOR Flash在写入前必须保证该地址处于“擦除态”(全1)。所以驱动里一定要做“写前擦除”检查。
2.4 读/写/擦除特性
好,咱们来聊聊实际操作。NOR Flash的三种操作,各有各的脾气。
读取(Read)
读取是最简单的。你只需要通过SPI接口发送“读命令”(通常是0x03或0x0B),然后跟上24位地址,Flash就会把数据吐出来。读取速度很快,通常能跑到几十MHz的SPI时钟。
// 伪代码示例:读取一个字节
uint8_t nor_read_byte(uint32_t addr) {
spi_cs_low();
spi_send_byte(0x03); // 读命令
spi_send_byte(addr >> 16); // 地址高8位
spi_send_byte(addr >> 8); // 地址中8位
spi_send_byte(addr); // 地址低8位
uint8_t data = spi_recv_byte();
spi_cs_high();
return data;
}
嗯,这里要注意:有些Flash支持“快速读取”模式(0x0B),它会在地址后面加一个“dummy byte”,用来适应更高的时钟频率。我建议你在驱动里同时支持两种模式,根据时钟频率自动切换。
写入(Program)
写入就没那么痛快了。NOR Flash的写入是按Page进行的,而且写入前必须保证该Page已经被擦除。写入命令通常是0x02,后面跟上地址和数据。
写入时间比较长,一个Page通常需要几十到几百微秒。你可以通过读取状态寄存器(命令0x05)来查询写入是否完成。我个人习惯用“轮询”方式,而不是中断,因为写入操作本身就不频繁。
关键点:写入只能把1变成0。如果你想把0变成1,必须擦除。所以驱动里一定要有“写前检查”逻辑,避免在非擦除态下写入。
擦除(Erase)
擦除是最慢的操作,没有之一。一个Sector(4KB)的擦除时间可能长达几百毫秒,甚至几秒。擦除命令通常是0x20(Sector擦除)或0xC7(整片擦除)。
擦除操作会把整个Sector的所有位都变成1。所以擦除后,你读到的数据全是0xFF。这也是Flash的“初始状态”。
避坑指南:我曾经在擦除过程中突然断电,结果那个Sector的数据变得不伦不类,既不是旧数据,也不是全1。后来我学乖了,在擦除前先把重要数据备份到另一个Sector,擦除成功后再删除备份。这叫“掉电安全”设计。
总结一下NOR Flash的三大特性:
- 读:随机访问,速度快,像内存一样用。
- 写:按Page写入,只能1变0,写前必须擦除。
- 擦:按Sector擦除,只能0变1,速度最慢。
理解了这些,你就能明白为什么NOR Flash适合存代码,而不适合做频繁写入的数据存储。代码是“写一次,读多次”的典型场景,正好匹配NOR Flash的特性。而如果你要存日志文件,那还是乖乖用NAND或者SD卡吧。
好,这一节就到这里。下一节咱们开始实战,看看怎么用SPI接口驱动NOR Flash。到时候我会拿出一个实际项目里的代码,咱们一行一行地分析。