第三章 磨损均衡核心算法(一):基本思想与分类
好,咱们进入正题了。磨损均衡,说白了就是让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当成一个日志本。每次写入,不是覆盖旧数据,而是在新位置追加一条新记录。旧数据就变成“无效”的了。
具体流程:
- 划分存储区为多个块(Block),每个块包含多条记录
- 每次写入,在当前块末尾追加新记录
- 当前块写满了,切换到下一个块
- 所有块都写满了,触发一次“垃圾回收”——把有效记录合并到新块,擦除旧块
代码示意(伪代码):
// 日志结构写入
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诊断模块里就这么用的,效果还不错。
小结
这一章咱们聊了磨损均衡的基本思想,还有三种主流方法:
- 动态均衡:简单实用,适合大多数场景
- 静态均衡:更彻底,但代价大
- 日志结构:天然均衡,但读性能差
下一章,我会讲具体的实现细节——怎么设计映射表、怎么处理掉电、怎么调优参数。到时候咱们再细聊。