单片机 EEPROM 数据管理:掉电保存与磨损均衡实战指南
👁 7 阅读 · 2026-08-18 · 嵌入式 存储
在嵌入式系统中,EEPROM 常用于保存配置参数、校准数据或运行状态,但掉电保存的可靠性与擦写寿命限制常被忽视。本文深入剖析 EEPROM 的物理特性,讲解掉电检测与数据缓冲策略,重点介绍磨损均衡算法(如轮询写入与日志结构)的实现原理,并给出基于 STM32 的完整代码示例,帮助开发者构建高可靠、长寿命的存储方案。
## 一、EEPROM 的物理特性与挑战
EEPROM(Electrically Erasable Programmable Read-Only Memory)支持字节级擦写,但每次写入前需先擦除(通常按页或整片),且擦写次数有限(典型 10 万次)。在频繁保存数据的场景(如设备计数器、用户设置),若不加以管理,很快会耗尽寿命。此外,掉电瞬间可能发生写入中断,导致数据损坏或丢失。因此,设计需兼顾**数据完整性**与**磨损均衡**。
## 二、掉电保存设计:从硬件到软件
### 2.1 硬件掉电检测
- 使用 MCU 内置的 BOD(Brown-Out Detector)或外部电压监控芯片(如 TPS3839),在电源跌落至阈值时触发中断。
- 在中断服务函数中,立即将关键数据写入 EEPROM,并禁止其他中断,确保写入过程不被干扰。
- 注意:EEPROM 写入时间约 3-5ms,需保证掉电后电源能维持足够时间(通常通过大电容储能)。
### 2.2 软件数据缓冲策略
- **双备份(Double Buffer)**:将数据存储在两个固定区域,写入时先写备份区,再写主区,读取时校验主区,若损坏则从备份区恢复。
- **校验和(Checksum)**:在数据末尾附加 CRC 或累加和,读取时校验,防止写入中断导致的数据不完整。
## 三、磨损均衡算法详解
磨损均衡的核心是避免反复擦写同一地址,将写入操作分散到整个存储空间。常用方法有:
### 3.1 轮询写入(Round-Robin)
将存储区划分为 N 个槽位,每次写入使用下一个槽位,并记录当前槽位索引。读取时需扫描所有槽位,找到最新有效数据。
**优点**:实现简单,均衡效果好。
**缺点**:读取需遍历,速度较慢;需额外存储索引。
### 3.2 日志结构(Log-Structured)
类似日志文件系统,每次写入追加到存储区末尾,当写满时进行垃圾回收(擦除旧数据)。
**优点**:写入效率高,适合频繁更新。
**缺点**:需要管理日志头,回收机制复杂。
### 3.3 动态磨损均衡(结合索引表)
在 RAM 中维护一个逻辑地址到物理地址的映射表,每次写入选择擦写次数最少的物理页。适用于较大容量 EEPROM。
## 四、完整代码示例(基于 STM32 HAL 库)
以下示例实现轮询写入 + 双备份 + 校验和,适用于保存 16 字节的用户数据。
```c
// eeprom_manage.h
#ifndef EEPROM_MANAGE_H
#define EEPROM_MANAGE_H
#include "stdint.h"
#include "stdbool.h"
#define DATA_SIZE 16 // 数据长度(字节)
#define SLOT_COUNT 8 // 槽位数量(需为偶数,便于双备份)
#define SLOT_SIZE (DATA_SIZE + 2) // 数据 + 校验和(2字节)
#define EEPROM_START_ADDR 0x08080000 // 假设外部 EEPROM 起始地址,内部需调整
typedef struct {
uint8_t data[DATA_SIZE];
uint16_t checksum;
} Slot_t;
bool EEPROM_Save(uint8_t *data);
bool EEPROM_Load(uint8_t *data);
void EEPROM_Init(void);
#endif
```
```c
// eeprom_manage.c
#include "eeprom_manage.h"
#include "i2c.h" // 假设使用 I2C 接口的外部 EEPROM
static uint8_t current_slot = 0; // 当前写入槽位索引
// 计算校验和(简单累加)
static uint16_t calc_checksum(uint8_t *data) {
uint16_t sum = 0;
for (int i = 0; i < DATA_SIZE; i++) {
sum += data[i];
}
return sum;
}
// 写入一个槽位
static void write_slot(uint8_t slot, uint8_t *data) {
uint16_t addr = EEPROM_START_ADDR + slot * SLOT_SIZE;
uint16_t checksum = calc_checksum(data);
// 先写数据,再写校验和(分两次写,防止中断导致校验和不匹配)
HAL_I2C_Mem_Write(&hi2c1, 0xA0, addr, 2, data, DATA_SIZE, 100);
HAL_I2C_Mem_Write(&hi2c1, 0xA0, addr + DATA_SIZE, 2, (uint8_t*)&checksum, 2, 100);
}
// 读取并校验槽位,返回是否有效
static bool read_slot(uint8_t slot, uint8_t *data) {
uint16_t addr = EEPROM_START_ADDR + slot * SLOT_SIZE;
uint8_t buf[SLOT_SIZE];
HAL_I2C_Mem_Read(&hi2c1, 0xA0, addr, 2, buf, SLOT_SIZE, 100);
uint16_t checksum = (buf[DATA_SIZE] << 8) | buf[DATA_SIZE+1];
if (calc_checksum(buf) == checksum) {
memcpy(data, buf, DATA_SIZE);
return true;
}
return false;
}
// 初始化:扫描所有槽位,找到最新有效数据(槽位索引最大且校验通过)
void EEPROM_Init(void) {
uint8_t temp[DATA_SIZE];
current_slot = 0;
for (int i = SLOT_COUNT-1; i >= 0; i--) {
if (read_slot(i, temp)) {
current_slot = i;
break;
}
}
}
// 保存数据:写入下一个槽位(循环),并更新当前索引
bool EEPROM_Save(uint8_t *data) {
uint8_t next_slot = (current_slot + 1) % SLOT_COUNT;
write_slot(next_slot, data);
// 写入后立即验证
uint8_t verify[DATA_SIZE];
if (read_slot(next_slot, verify)) {
if (memcmp(verify, data, DATA_SIZE) == 0) {
current_slot = next_slot;
return true;
}
}
return false; // 写入失败,保持原槽位
}
// 读取数据:从当前槽位读取,若失败则尝试前一个槽位(双备份)
bool EEPROM_Load(uint8_t *data) {
if (read_slot(current_slot, data)) {
return true;
}
// 尝试前一个槽位(备份)
uint8_t prev_slot = (current_slot == 0) ? (SLOT_COUNT-1) : (current_slot-1);
if (read_slot(prev_slot, data)) {
return true;
}
return false;
}
```
**使用说明**:
- 初始化时调用 `EEPROM_Init()`,上电后调用 `EEPROM_Load()` 读取数据。
- 需要保存时调用 `EEPROM_Save()`,内部自动轮询槽位。
- 掉电中断中调用 `EEPROM_Save()` 时,需确保 I2C 中断优先级最高,或使用轮询方式。
## 五、注意事项与优化建议
- **写入时间**:EEPROM 写入较慢,频繁保存会阻塞主循环,建议采用“脏数据标记 + 定时批量保存”策略。
- **磨损均衡粒度**:槽位数量越多,均衡效果越好,但占用空间增大,需权衡。
- **掉电保护**:在写入过程中掉电,可能导致校验和不匹配,双备份可有效恢复。
- **外部 EEPROM vs 内部**:内部 EEPROM 擦写次数更少(通常 10 万次),外部如 AT24Cxx 可达 100 万次,选型时注意。
- **测试**:使用循环写入测试寿命,并模拟掉电(随机中断写入)验证数据完整性。
## 六、总结
EEPROM 数据管理是嵌入式开发中的关键细节,通过掉电检测、双备份和磨损均衡,可以显著提升系统可靠性。本文提供的轮询写入算法简单高效,适用于大多数场景。对于更高频率的写入,可考虑日志结构或外部 FRAM(铁电存储器)替代。希望本文能帮助你在实际项目中设计出健壮的存储方案。