4、Flash存储器管理:Flash分区规划与磨损均衡

做呼吸机固件开发,Flash管理是个绕不开的硬骨头。我刚开始做这个领域时,觉得不就是存个程序嘛,分几个区就行了。结果第一次做批量测试,就遇到了参数区写坏的情况——嗯,那次教训挺深刻的。

今天咱们聊聊Flash分区规划和磨损均衡。说白了,就是怎么把你的Flash空间安排得明明白白,让系统跑得稳、活得久。

4.1 Flash分区规划:四个核心区域

我个人习惯把呼吸机的Flash分成四个区。你想想看,Bootloader、App、参数、日志,各司其职,互不干扰。

分区名称 起始地址 大小 功能说明
Bootloader区 0x08000000 64KB 启动引导、固件升级入口
App区 0x08010000 512KB 主应用程序代码
参数区 0x08090000 128KB 患者参数、设备配置
日志区 0x080B0000 320KB 运行日志、错误记录

为什么这么分?我一个个说。

4.1.1 Bootloader区

这个区我建议放在Flash的最开头。为什么?因为芯片上电后默认从0x08000000开始执行。Bootloader要足够小、足够稳定。我在项目中遇到过Bootloader写太大的情况,结果升级时把自己覆盖了——机器直接变砖。所以,64KB足够了,只放启动代码和升级协议栈。

4.1.2 App区

主程序区,呼吸机的核心。这里要注意一点:App区要预留至少20%的余量。我见过一个团队把App区塞得满满当当,结果想加个新功能,发现没地方了。你想想看,呼吸机的算法迭代很快,今天加个波形分析,明天加个报警逻辑,空间不够就尴尬了。

4.1.3 参数区

这个区我特别想多说两句。参数区存的是患者的治疗参数——潮气量、呼吸频率、压力支持等等。这些数据不能丢,丢了机器可能乱吹气。所以参数区我一般用双备份策略:一个主区,一个备份区。写的时候先写备份,校验通过再写主区。万一写一半掉电了,备份区还能救回来。

关键点:参数区的擦写频率最高。每次参数调整都要写一次。所以这里要特别关注擦写寿命。

4.1.4 日志区

日志区存的是运行记录和错误信息。这个区我建议用环形缓冲区的方式管理。为什么?因为日志是循环写的,旧的会被新的覆盖。这样既不会撑爆Flash,又能保留最近一段时间的完整记录。

4.2 擦写寿命管理:别把Flash写死了

Flash的擦写寿命是有限的。一般的NOR Flash,擦写次数在10万次左右。听起来很多对吧?但如果你每秒写一次参数,三天就写完了。我见过一个项目,就是因为没注意擦写频率,机器用了两个月参数区就报废了。

怎么管理?我分享几个经验:

  • 减少不必要的写操作:参数没变化就别写。很多工程师习惯每次循环都写一次参数,其实完全没必要。
  • 合并写操作:把多次小写合并成一次大写。比如调整参数时,等所有参数都改完了再统一写入。
  • 使用写缓存:在RAM里缓存数据,达到一定量或一定时间后再写入Flash。

我的小技巧:在参数区加一个"脏标志位"。只有参数真正变化了,才把脏标志置位,然后在空闲时统一写入。这样能减少至少80%的写操作。

4.3 磨损均衡策略:让Flash活得更久

磨损均衡,说白了就是别总盯着一个地方写。你想想看,如果每次写参数都写同一个地址,那个地址很快就报废了。但其他地址还跟新的一样。

我常用的磨损均衡策略有两种:

4.3.1 地址映射法

把逻辑地址映射到不同的物理地址。每次写操作都换一个物理块。这样所有块的磨损程度就差不多了。

// 磨损均衡示例:简单的地址映射
#define PARAM_BLOCK_COUNT 64
uint32_t current_block = 0;

void write_param(uint8_t *data, uint32_t len) {
    // 先读当前块的使用次数
    uint32_t wear_count = read_wear_count(current_block);
    
    // 如果当前块磨损严重,换下一个
    if (wear_count > MAX_WEAR_COUNT) {
        current_block = (current_block + 1) % PARAM_BLOCK_COUNT;
    }
    
    // 写入数据
    flash_erase(current_block);
    flash_write(current_block, data, len);
    
    // 更新磨损计数
    update_wear_count(current_block);
}

4.3.2 日志结构法

这个我特别喜欢用。不覆盖旧数据,而是追加写新数据。旧数据自然就变成无效的了。等整个区写满了,再统一擦除。这样每个块的擦写次数就均匀了。

注意:日志结构法需要定期做垃圾回收。把有效数据搬走,无效数据擦掉。这个过程要在系统空闲时做,不能影响呼吸机的实时控制。

4.4 避坑指南:我踩过的那些坑

做Flash管理这些年,我踩过不少坑。分享几个典型的:

  • 擦除时间过长:有一次我写参数时直接擦除整个扇区,结果擦除花了100多毫秒。呼吸机在这段时间里没响应,差点出事故。后来我改用双缓冲,先擦备用区,再切换。
  • 掉电保护没做好:写Flash时突然掉电,数据就坏了。我后来加了一个"写前备份"机制,写之前先把旧数据备份到另一个区,写完了再删备份。
  • 磨损均衡算法太复杂:一开始我搞了个很复杂的磨损均衡算法,结果代码量太大,还容易出bug。后来简化了,用最简单的轮询法,效果反而更好。

核心原则:Flash管理要简单可靠。别为了追求极致性能而牺牲稳定性。呼吸机是生命支持设备,稳定压倒一切。

4.5 小结

Flash分区规划和磨损均衡,说白了就是两件事:一是把空间分好,二是把寿命管好。分区要合理,别让参数区和日志区打架;磨损要均衡,别让某个区提前退休。

我个人建议,在做呼吸机项目时,先把Flash分区图画出来,标清楚每个区的起始地址和大小。然后根据每个区的擦写频率,选择合适的磨损均衡策略。参数区用地址映射法,日志区用日志结构法,Bootloader和App区基本不用管磨损问题。

嗯,今天就聊到这儿。下一节咱们讲讲Bootloader的具体实现,包括启动流程和升级协议。到时候我会分享一个我在实际项目中用过的升级方案,挺实用的。