第三章 磨损均衡核心算法(一):基本思想与分类

好,咱们进入正题了。磨损均衡,说白了就是让EEPROM里每个存储单元被擦写的次数尽量平均。你想想看,如果某个地址老是被写,其他地址闲着,那这个倒霉的地址很快就挂了。整个芯片也就废了。

我最早接触这个问题,是在做车身控制模块的时候。当时有个客户反馈,车子开了三年,门锁控制偶尔失灵。查到最后,发现是存储门锁状态的EEPROM单元提前报废了。嗯,从那以后,我对磨损均衡就特别上心。

3.1 基本思想:别让一个兵累死

磨损均衡的核心思想,其实特别简单——把写操作分散到不同的物理地址上。逻辑地址不变,但每次写入时,实际存储位置在变。

举个例子:

  • 逻辑地址 0x100 存的是“车门状态”
  • 第一次写入,实际写到物理地址 0x000
  • 第二次写入,实际写到物理地址 0x010
  • 第三次写入,实际写到物理地址 0x020

这样,每个物理地址的擦写次数就均匀了。

关键指标:磨损均衡度

磨损均衡度 = 最大擦写次数 / 平均擦写次数

理想值是 1.0,越接近越好。一般工程上做到 1.5 以内就算不错了。

我在一个项目里见过,没做均衡的芯片,有的单元擦了 10 万次,有的才 1000 次。均衡度高达 100!这种板子,你说能撑多久?

3.2 算法分类:两大流派

磨损均衡算法,大致分两类。我习惯这么记:

  • 静态均衡:所有数据一视同仁,不管你是常变的还是不变的,都参与轮换
  • 动态均衡:只对频繁变化的数据做均衡,不变的数据就让它待着

你可能会问,那静态均衡不是更好吗?嗯,理论上是的。但代价也大——你得把那些“死数据”也搬来搬去,每次搬都要擦写一次,反而加速了磨损。

特性 静态均衡 动态均衡
覆盖范围 全部存储空间 仅活跃数据区域
实现复杂度
额外擦写开销 大(搬移死数据)
适用场景 高可靠性要求 资源受限的MCU

我的建议: 如果EEPROM容量不大(比如 2KB 以下),直接用动态均衡就够了。容量大的,或者对寿命要求特别苛刻的,再上静态均衡。

3.3 静态均衡 vs 动态均衡:实战对比

咱们拿一个实际场景来说。假设有个ECU,要存三个数据:

  • 车辆VIN码(写一次,再也不变)
  • 累计里程(每次熄火更新)
  • 故障码(偶尔写入)

动态均衡的做法:

只对“累计里程”和“故障码”做均衡。VIN码就固定在某个地址,不动它。这样实现简单,开销小。但问题是,VIN码占着的那几个单元,永远不被均衡,其他单元在频繁擦写。

静态均衡的做法:

连VIN码也要定期搬移。比如每写 1000 次里程,就把VIN码挪个地方。这样所有单元都雨露均沾。但代价是,每次搬VIN码,都要多两次擦写(一次读旧位置,一次写新位置)。

我记得有个项目,客户要求 20 年寿命。我们算了算,用动态均衡,EEPROM能撑 15 年。后来改成静态均衡,把搬移周期调大,才勉强达标。嗯,有时候就是得在成本和寿命之间找平衡。

注意: 静态均衡如果搬移太频繁,反而会缩短寿命。因为搬移本身也是擦写操作。我曾经见过一个设计,搬移周期设得太短,结果均衡度是好了,但总擦写次数翻了一倍,寿命反而降了。

3.4 日志结构(Log-Structured)方法

这个方法,说白了就是把EEPROM当成一个日志本。每次写入,不是覆盖旧数据,而是在新位置追加一条新记录。旧数据就变成“无效”的了。

具体流程:

  1. 划分存储区为多个块(Block),每个块包含多条记录
  2. 每次写入,在当前块末尾追加新记录
  3. 当前块写满了,切换到下一个块
  4. 所有块都写满了,触发一次“垃圾回收”——把有效记录合并到新块,擦除旧块

代码示意(伪代码):

// 日志结构写入
uint16_t log_write(uint16_t logical_addr, uint8_t *data, uint8_t len) {
    // 1. 找到当前写入位置(块内偏移)
    // 2. 追加记录头(逻辑地址、时间戳、长度)
    // 3. 追加数据
    // 4. 更新当前写入指针
    // 5. 如果块满了,切换到下一块
    // 6. 如果所有块都满了,触发垃圾回收
}

这个方法的好处很明显:

  • 天然均衡:每次写入都在新位置,不会盯着一个地址猛写
  • 掉电安全:即使写一半掉电,最多丢一条记录,不会破坏旧数据
  • 写入速度快:不需要先擦除再写,直接追加就行

但缺点也有:

  • 读性能差:要读一个逻辑地址,得从最新记录往前找,最坏情况要遍历整个日志
  • 垃圾回收开销大:合并有效记录时,要搬移数据,会触发额外擦写
  • 存储利用率低:无效记录占着空间,直到垃圾回收才释放

实战技巧: 我一般会在记录头里加一个“版本号”或“时间戳”。这样读数据时,只需要找版本号最大的那条记录就行,不用遍历全部。另外,垃圾回收的触发阈值要调好——太早触发,无效记录太少,浪费;太晚触发,可用空间不够,写入会卡住。

日志结构方法,在NAND Flash里用得特别多。EEPROM里也有,但不多见。为啥?因为EEPROM的擦写次数本来就比NAND高(100万次 vs 10万次),而且容量小,日志结构的开销相对就大了。

我个人觉得,日志结构更适合那些写入频繁、但数据量不大的场景。比如存故障码,每次写一条,读的时候按时间倒序查。嗯,我在一个OBD诊断模块里就这么用的,效果还不错。

小结

这一章咱们聊了磨损均衡的基本思想,还有三种主流方法:

  • 动态均衡:简单实用,适合大多数场景
  • 静态均衡:更彻底,但代价大
  • 日志结构:天然均衡,但读性能差

下一章,我会讲具体的实现细节——怎么设计映射表、怎么处理掉电、怎么调优参数。到时候咱们再细聊。