3、Flash存储器基础:Flash类型与特性、读写擦除操作、扇区保护机制
好,咱们进入第三章。这一章我打算聊聊Flash存储器。你想想看,Bootloader的核心工作就是跟Flash打交道——把新固件写进去,把旧固件擦掉,还得保证中途不掉链子。说白了,不懂Flash就别谈Bootloader安全升级。
我个人习惯,在讲任何存储器之前,先搞清楚它的物理特性。因为很多坑,都是底层特性挖的。
3.1 Flash存储器的类型与特性
车载ECU里用的Flash,主要分两大类:NOR Flash和NAND Flash。嗯,这里要注意,咱们Bootloader场景下,绝大多数都是NOR Flash。
为什么是NOR? 因为NOR支持随机读取,也就是CPU可以直接在Flash上执行代码(XIP,Execute In Place)。你想想看,ECU上电后,CPU第一件事就是去Flash的起始地址取指令,如果是NAND,还得先拷贝到RAM里,太慢了。
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取速度 | 快(随机读取) | 慢(需要页读取) |
| 写入速度 | 慢 | 快 |
| 擦除速度 | 慢(秒级) | 快(毫秒级) |
| 密度 | 低(1MB~64MB常见) | 高(GB级别) |
| 可靠性 | 高(坏块少) | 低(需要坏块管理) |
| 典型应用 | 代码存储、Bootloader | 大容量数据存储 |
我在项目中遇到过,有人把NAND Flash用在Bootloader区域,结果上电启动不稳定。后来查原因,就是NAND的初始读取延迟太大,CPU等不及。所以,记住:Bootloader必须用NOR Flash,这是铁律。
3.2 读写擦除操作
Flash的操作,说白了就三个:读、写、擦。但这里有个关键点——写之前必须先擦。你想想看,RAM可以随便覆盖,但Flash不行。Flash的写操作只能把1变成0,不能把0变成1。要把0变回1,只能擦除。
我给大家拆解一下这三个操作:
3.2.1 读取操作
最简单。直接给地址,读数据。NOR Flash的读时序跟SRAM差不多,所以CPU可以直接寻址。代码里就是一条指针解引用:
uint32_t data = *(volatile uint32_t *)0x08000000;
嗯,这里要注意,一定要加 volatile,防止编译器优化掉你的读取操作。
3.2.2 写入操作
写入也叫编程(Program)。NOR Flash的写入,通常以字(16位)或半字(8位)为单位。写入前,目标地址必须是擦除过的(全0xFF)。
我给大家看一个典型的Flash写入流程:
// 伪代码:Flash写入一个字
void Flash_WriteWord(uint32_t addr, uint16_t data) {
// 1. 检查地址是否对齐
if (addr & 0x1) return ERROR;
// 2. 解锁Flash控制器
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
// 3. 等待上次操作完成
while (FLASH->SR & FLASH_SR_BSY);
// 4. 设置编程模式
FLASH->CR |= FLASH_CR_PG;
// 5. 写入数据(硬件自动完成)
*(volatile uint16_t*)addr = data;
// 6. 等待完成并检查错误
while (FLASH->SR & FLASH_SR_BSY);
if (FLASH->SR & FLASH_SR_PGERR) {
// 处理错误
}
// 7. 锁定Flash
FLASH->CR &= ~FLASH_CR_PG;
FLASH->CR |= FLASH_CR_LOCK;
}
我曾经在调试时,忘了检查BSY位,结果连续写入导致数据错乱。嗯,从那以后,我每次写Flash都老老实实等BSY清零。
3.2.3 擦除操作
擦除是Flash最耗时的操作。NOR Flash支持两种擦除:扇区擦除和整片擦除。扇区擦除一般需要几百毫秒到几秒,整片擦除可能十几秒。
这里有个避坑指南:擦除期间绝对不能断电。我曾经遇到过,客户在产线上刷写ECU时突然断电,结果Flash处于半擦除状态,整个扇区数据全乱,ECU直接变砖。后来我们加了擦除前的CRC校验和擦除后的回读验证,才解决这个问题。
3.3 扇区保护机制
扇区保护,说白了就是给Flash上锁。防止Bootloader区域被意外擦写。你想想看,如果升级过程中,程序跑飞了,不小心把Bootloader代码给擦了,那ECU就彻底废了。
常见的保护机制有几种:
- 硬件写保护引脚:Flash芯片有个WP#引脚,拉低后整个芯片禁止写入。我习惯在Bootloader启动时,先检查这个引脚的电平。
- 扇区写保护寄存器:通过配置Flash控制器的寄存器,锁定特定扇区。比如STM32的Flash有WRP(Write Protection)寄存器。
- 选项字节保护:有些MCU支持通过选项字节(Option Bytes)设置保护级别,掉电不丢失。
我给大家看一个实际项目中用的保护策略:
// 伪代码:保护Bootloader扇区
void Protect_Bootloader_Sector(void) {
// 1. 解锁选项字节
FLASH->OPTKEYR = 0x08192A3B;
FLASH->OPTKEYR = 0x4C5D6E7F;
// 2. 设置扇区0~1为写保护(假设Bootloader占前两个扇区)
FLASH->WRPR = 0x0003; // 位0和位1置1
// 3. 启动选项字节加载
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);
// 4. 锁定
FLASH->CR |= FLASH_CR_OPTLOCK;
}
3.4 实战中的注意事项
最后,我总结几个实战中容易踩的坑:
- 擦除次数限制:NOR Flash的擦写寿命一般是10万次。虽然听起来很多,但如果你在调试阶段反复擦写同一个扇区,很快就到寿命了。我习惯在调试时用RAM模拟Flash,等逻辑稳定了再烧真Flash。
- 读改写陷阱:Flash不能直接修改一个字节,必须先擦除整个扇区,再写回。如果你只想改一个字节,得先把整个扇区读到RAM,修改,擦除扇区,再写回。这个过程如果断电,数据就丢了。
- 对齐访问:很多Flash控制器要求写入地址必须对齐到字或半字边界。不对齐的写入会导致硬件错误。
嗯,这一章的内容就这些。Flash是Bootloader的根基,理解它的脾气秉性,后面讲安全升级和防刷写策略时,你才能游刃有余。下一章,我们聊聊Bootloader的启动流程,看看CPU上电后到底干了些什么。