第3章:Flash驱动深度优化

Flash操作慢,这是做Bootloader绕不开的痛。我早期做OTA升级时,光擦除就占了总时间的70%以上。你想想看,用户等半天进度条不动,体验能好吗?

这一章,咱们就专门啃这块硬骨头。我会把擦除和写入的优化手段,掰开揉碎了讲清楚。

3.1 擦除算法优化:从串行到并行

传统做法是一个扇区一个扇区擦。比如擦一个64KB的扇区要100ms,擦8个就是800ms。这太慢了。

核心思路:利用Flash支持多扇区同时操作的特性,发起并行擦除命令。

关键点:不是所有Flash都支持并行擦除。你得先看数据手册,确认是否支持“Multi-Block Erase”或“Simultaneous Erase”。

我举个例子。某款主流Flash芯片,支持同时擦除4个扇区。优化前后的对比:

操作 传统串行擦除 并行擦除(4扇区)
擦除4个扇区(256KB) 400ms 120ms
擦除8个扇区(512KB) 800ms 240ms
擦除16个扇区(1MB) 1600ms 480ms

看到没?时间直接砍掉70%。

实现要点:

  • 发送擦除命令时,一次性指定起始和结束扇区地址
  • 轮询状态寄存器时,要等所有扇区都完成
  • 注意:并行擦除期间,不能插入其他Flash操作

我的习惯:我会在擦除前先检查目标区域是否已经是擦除状态(全0xFF)。如果是,直接跳过。这个检查成本极低,但能省下大量无效擦除时间。

3.2 写入算法优化:Cache Line对齐

写入慢,很多时候不是Flash本身慢,而是CPU和Flash之间的数据搬运效率低。

问题根源:大多数MCU的Flash控制器,内部有Cache Line(缓存行)。如果你写入的数据没有对齐到Cache Line边界,就会触发额外的读-修改-写操作。

举个例子。假设Cache Line是16字节。你写一个8字节的数据,起始地址是0x1002。这就不对齐。Flash控制器会先读0x1000-0x100F这16字节,修改其中8个,再写回去。多了一次读操作,时间翻倍。

优化方案:

  • 确保每次写入的起始地址是Cache Line对齐的
  • 写入长度最好是Cache Line的整数倍
  • 如果数据不对齐,先填充到对齐边界
// 优化前:不对齐写入
void flash_write_raw(uint32_t addr, uint8_t *data, uint32_t len) {
    for (uint32_t i = 0; i < len; i++) {
        flash_program_byte(addr + i, data[i]);  // 逐字节写入,效率极低
    }
}

// 优化后:Cache Line对齐写入
void flash_write_aligned(uint32_t addr, uint8_t *data, uint32_t len) {
    // 确保地址对齐到16字节
    if (addr & 0x0F) {
        // 处理不对齐的前缀部分
        uint32_t prefix_len = 16 - (addr & 0x0F);
        flash_write_raw(addr, data, prefix_len);
        addr += prefix_len;
        data += prefix_len;
        len -= prefix_len;
    }
    
    // 对齐部分,按16字节块写入
    while (len >= 16) {
        flash_program_block(addr, data, 16);  // 一次写入16字节
        addr += 16;
        data += 16;
        len -= 16;
    }
    
    // 处理剩余的不对齐尾部
    if (len > 0) {
        flash_write_raw(addr, data, len);
    }
}

我曾经踩过的坑:有一次我优化写入速度,发现怎么调都提不上去。查了半天,原来是DMA配置里,源地址和目的地址的宽度不匹配。源是8位,目的是32位,导致每次传输都要做位宽转换。嗯,这个细节很容易被忽略。

3.3 组合优化策略

擦除和写入优化,不是孤立的。实际项目中,我会把它们组合起来用。

我的推荐流程:

  1. 预检查:先读目标区域,如果全是0xFF,跳过擦除
  2. 并行擦除:一次性擦除多个连续扇区
  3. 对齐写入:按Cache Line对齐,批量写入数据
  4. 状态轮询优化:用中断或DMA完成标志,代替忙等

这套组合拳打下来,我见过的最优效果是:刷写1MB固件,从原来的12秒,压缩到3.5秒。用户几乎感觉不到等待。

一个小技巧:如果你的Flash支持“双缓冲”或“乒乓操作”,可以一边擦除下一个扇区,一边写入当前扇区。这能进一步隐藏擦除时间。不过实现起来稍微复杂点,需要处理好状态机。

3.4 实测数据对比

最后,我放一组实测数据。这是在某款Cortex-M4芯片上,用1MB固件做的测试:

优化策略 擦除时间 写入时间 总时间
原始(串行擦除+逐字节写) 8.2s 4.5s 12.7s
并行擦除(4扇区) 2.1s 4.5s 6.6s
并行擦除+Cache Line对齐写 2.1s 1.8s 3.9s
全部优化(含预检查+状态轮询优化) 1.5s 1.2s 2.7s

从12.7秒到2.7秒,提升了将近5倍。这就是优化的价值。

好了,这一章的内容就到这里。下一章,咱们聊聊如何用DMA和中断来进一步榨干Flash控制器的性能。到时候见。