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校验和擦除后的回读验证,才解决这个问题。

警告:擦除操作不可中断!如果必须中断,请确保硬件有掉电保护电路(如大电容保持供电),或者使用双Bank方案。

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;
}
提示:我建议在Bootloader的初始化代码中,第一时间把自身所在的扇区保护起来。这样即使应用层程序跑飞了,也擦不到Bootloader。

3.4 实战中的注意事项

最后,我总结几个实战中容易踩的坑:

  1. 擦除次数限制:NOR Flash的擦写寿命一般是10万次。虽然听起来很多,但如果你在调试阶段反复擦写同一个扇区,很快就到寿命了。我习惯在调试时用RAM模拟Flash,等逻辑稳定了再烧真Flash。
  2. 读改写陷阱:Flash不能直接修改一个字节,必须先擦除整个扇区,再写回。如果你只想改一个字节,得先把整个扇区读到RAM,修改,擦除扇区,再写回。这个过程如果断电,数据就丢了。
  3. 对齐访问:很多Flash控制器要求写入地址必须对齐到字或半字边界。不对齐的写入会导致硬件错误。

嗯,这一章的内容就这些。Flash是Bootloader的根基,理解它的脾气秉性,后面讲安全升级和防刷写策略时,你才能游刃有余。下一章,我们聊聊Bootloader的启动流程,看看CPU上电后到底干了些什么。