4、Flash存储器原理:NOR Flash与NAND Flash区别、Flash驱动开发要点、磨损均衡
好,咱们今天聊聊Flash。做ADAS系统,Flash是绕不开的。你想想看,Bootloader要存,应用程序要存,关键配置参数也要存。选错Flash,或者驱动写得不好,轻则升级失败,重则系统变砖。我这些年踩过的坑,有一半都跟Flash有关。
Flash存储器,说白了就是一块能电擦除、电写入的非易失性存储器。但同样是Flash,NOR和NAND完全是两码事。咱们先把这个最基础的区别搞清楚。
4.1 NOR Flash与NAND Flash:核心区别
我个人习惯把NOR Flash想象成“内存的远房亲戚”。为什么?因为它支持随机访问。你给它一个地址,它就能直接返回那个地址的数据。读操作跟读SRAM差不多,速度快,延迟低。这在ADAS里有什么用?比如代码直接在NOR Flash上执行(XIP,Execute In Place),省掉拷贝到RAM的步骤,上电就能跑。
NAND Flash呢?它更像“硬盘的微型版”。它按页(Page)读写,按块(Block)擦除。你不能直接读某个字节,得先把整页数据读到RAM里,再从中取你要的字节。写操作也一样,得先擦除整个块,再写页。这操作模式,跟NOR Flash完全不同。
我整理了一个表格,方便你对比:
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读操作 | 随机访问,类似RAM,快 | 按页读取,需先读入缓冲区,较慢 |
| 写操作 | 按字节/字写入,但擦除前需先擦除整个扇区 | 按页写入,但擦除前需先擦除整个块 |
| 擦除操作 | 按扇区擦除(通常64KB~128KB) | 按块擦除(通常128KB~1MB) |
| 容量 | 较小(通常1MB~64MB) | 较大(通常128MB~8GB以上) |
| 成本 | 较高(每MB价格) | 较低(每MB价格) |
| 可靠性 | 坏块极少,位翻转概率低 | 出厂就有坏块,使用中还会产生新坏块,需ECC |
| XIP支持 | 支持(代码可直接运行) | 不支持(需拷贝到RAM执行) |
| 典型应用 | Bootloader、关键参数存储 | 大容量固件存储、日志记录 |
看到这个表,你应该明白了。在ADAS系统里,我通常的做法是:NOR Flash放Bootloader和关键配置,因为要保证上电就能跑,而且不能出错。NAND Flash放应用程序和升级包,因为容量大、成本低。当然,有些高端芯片直接把NOR和NAND封装在一起,叫“多芯片封装”(MCP),省空间。
4.2 Flash驱动开发要点
驱动开发,嗯,这里要注意。很多人觉得Flash驱动就是调几个API,读、写、擦除。其实没那么简单。我见过太多因为驱动写得糙,导致升级到一半系统死掉的案例。
我个人总结,Flash驱动开发有四个关键点:
- 时序控制要精准:Flash操作有严格的时序要求。比如写一个字节,需要先发命令、再发地址、再发数据,最后等芯片内部完成(通过查询状态寄存器或等待忙信号)。时序不对,数据就写歪了。
- 擦除操作要谨慎:擦除是Flash最耗时的操作,一个扇区擦除可能要几百毫秒。而且擦除期间,整个Flash芯片都处于忙状态,不能做其他操作。所以,擦除操作必须在关中断的环境下执行,否则中断一来,时序就乱了。
- 写操作前必须擦除:Flash的特性是,只能从1写成0,不能从0写成1。所以写之前,必须先把目标区域擦除成全1。这个很多人会忘,尤其是从RAM开发转过来的。
- 错误处理要完善:Flash操作可能失败。比如电压不稳、芯片老化、坏块等。驱动必须能检测到错误(比如超时、状态寄存器异常),并返回错误码。不能死等,更不能直接崩溃。
这里给一个简单的NOR Flash写字节示例,注意看时序:
// 伪代码:写一个字节到NOR Flash
int nor_flash_write_byte(uint32_t addr, uint8_t data) {
// 1. 发送写使能命令
flash_send_command(CMD_WRITE_ENABLE);
// 2. 发送页编程命令
flash_send_command(CMD_PAGE_PROGRAM);
// 3. 发送24位地址(高字节在前)
flash_send_address(addr, 3);
// 4. 发送数据
flash_send_data(data);
// 5. 等待操作完成(轮询状态寄存器)
uint32_t timeout = 100000;
while (timeout--) {
uint8_t status = flash_read_status();
if (status & STATUS_BUSY) {
// 还在忙,继续等
continue;
}
if (status & STATUS_WRITE_ENABLE_LATCH) {
// 写使能锁存已清除,说明操作完成
return 0; // 成功
}
}
// 6. 超时处理
return -1; // 失败
}
4.3 磨损均衡:为什么需要?怎么做?
磨损均衡,这个词听起来高大上,其实道理很简单。Flash的擦写次数是有限的。NOR Flash一般10万次,NAND Flash一般1万~10万次(取决于工艺)。如果你总是往同一个地址写数据,那个地址很快就报废了。而其他地址还“年轻力壮”。
为什么会这样?因为Flash的擦除操作会对氧化层造成物理损伤。每擦除一次,氧化层就薄一点。擦除次数多了,氧化层就击穿了,那个块就变成坏块了。
在ADAS系统里,哪些数据会频繁写入?最常见的就是系统日志和配置参数。比如车辆每次上电,都要记录一个启动时间戳。如果不做磨损均衡,那个存时间戳的地址可能几个月就挂了。
磨损均衡的核心思想就一句话:把写操作均匀分布到所有可用的存储块上。具体怎么做?有两种常见策略:
- 动态磨损均衡:每次写数据时,找一个擦除次数最少的块来写。这需要维护一个“擦除次数表”,记录每个块的擦除次数。写之前查表,选最年轻的块。
- 静态磨损均衡:除了动态均衡,还会把长期不动的“冷数据”(比如Bootloader代码)搬移到擦除次数较多的块上,把“热数据”搬移到擦除次数少的块上。这样能更均匀地磨损所有块。
对于ADAS系统,我个人建议:Bootloader和应用程序所在的区域,不需要做磨损均衡,因为写一次就不动了。但参数存储区和日志区,必须做磨损均衡。而且,磨损均衡算法不能太复杂,否则会影响启动速度。我一般用“日志型”磨损均衡:把参数存储区划分成多个槽位,每次写参数时,写到下一个槽位,写满一圈后再从头开始。这样每个槽位的擦写次数就均匀了。
最后,总结一下。Flash选型,NOR还是NAND,看你的应用场景。驱动开发,时序、擦除、错误处理,一个都不能少。磨损均衡,别等到Flash报废了才想起来做。嗯,这些就是我这些年跟Flash打交道的一些心得。下一节,咱们聊聊具体的Bootloader分区设计,那又是另一个有意思的话题。