4. 数据存储规划:按摩仪参数分类与地址映射表设计
好,咱们接着聊。上一节我们把 EEPROM 的硬件接口和驱动搞定了,但说实话,那只是万里长征第一步。你想想看,驱动写好了,数据往哪儿存?怎么存?存错了怎么办?
我刚开始做嵌入式那会儿,就吃过这个亏。有一次项目赶得急,我直接把参数按顺序往 EEPROM 里一塞,觉得“反正能读写就行”。结果产品上市后,客户反馈说“按摩仪用着用着,力度突然变成最大了”。查了半天,原来是地址重叠了,数据被覆盖。嗯,从那以后,我再也不敢轻视数据存储规划了。
4.1 按摩仪参数分类:先分清楚再动手
说白了,数据规划的第一步就是分类。你得知道你的按摩仪里到底要存哪些东西。我个人习惯,先把参数分成四大类:
- 用户模式:比如“轻柔模式”、“强力模式”、“助眠模式”等。每个模式对应一套预设参数。
- 力度参数:用户自定义的力度值,通常是 1-10 档。这个值用户会频繁调整。
- 时长参数:单次按摩的持续时间,比如 15 分钟、30 分钟。有些用户喜欢短时间,有些喜欢长一点。
- 历史记录:最近几次的使用记录,比如“2025-03-15 使用了强力模式 20 分钟”。这个功能虽然不起眼,但用户反馈很好。
为什么要分类?因为不同类型的参数,写入频率和重要性完全不同。力度参数用户天天调,写入次数多;用户模式可能一个月才改一次。如果你把高频写入的数据和低频写入的数据混在一起,很容易把 EEPROM 的寿命耗尽。EEPROM 的擦写次数一般是 10 万次左右,看着挺多,但如果你每秒写一次,几天就挂了。
4.2 地址映射表设计:一张表管好所有数据
分类完了,接下来就是画地址映射表。这张表就是你的“数据地图”,每个参数在 EEPROM 里住哪个房间,门牌号是多少,都得清清楚楚。
我建议用表格来规划,一目了然。下面是我给按摩仪设计的一个典型地址映射表:
| 参数名称 | 起始地址 | 占用字节 | 数据类型 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 魔数(Magic Number) | 0x0000 | 2 | uint16_t | 0xA5A5 | 用于判断 EEPROM 是否已初始化 |
| 版本号 | 0x0002 | 1 | uint8_t | 0x01 | 数据结构版本,方便后续升级 |
| 当前用户模式 | 0x0003 | 1 | uint8_t | 0x00 | 0=轻柔, 1=强力, 2=助眠 |
| 力度等级 | 0x0004 | 1 | uint8_t | 5 | 1-10 档,用户频繁修改 |
| 按摩时长 | 0x0005 | 1 | uint8_t | 15 | 单位:分钟,范围 5-60 |
| 历史记录索引 | 0x0006 | 1 | uint8_t | 0 | 循环写入的当前索引 |
| 历史记录区 | 0x0010 | 240 | 结构体数组 | 全0 | 存储最近 10 条记录,每条 24 字节 |
| 预留区 | 0x0100 | 256 | — | 0xFF | 为未来功能预留 |
看到这个表,你可能会问:“为什么要在最前面放一个魔数?”
这个问题问得好。魔数的作用,说白了就是判断 EEPROM 是不是“处女地”。新出厂的芯片,EEPROM 里全是 0xFF。如果程序第一次运行时读到魔数不是 0xA5A5,就说明 EEPROM 还没初始化过,需要写入默认值。否则,直接读取已有数据。这个技巧我在多个项目里都用过,非常实用。
4.3 历史记录区的设计:循环缓冲区是个好办法
历史记录这块,我单独拿出来说说。因为它的存储方式和普通参数不太一样。
普通参数是“原地更新”,比如用户把力度从 5 改成 6,直接往地址 0x0004 写 6 就行。但历史记录是“追加写入”,每次使用完都要新增一条。如果每次都往同一个地址写,那只能存一条记录,显然不行。
我的做法是:用循环缓冲区。在地址 0x0010 到 0x00FF 之间,划分出 240 个字节,每条记录固定 24 字节,总共可以存 10 条。再配合一个索引变量(地址 0x0006),记录当前写到哪一条了。
伪代码大概是这样:
// 定义历史记录结构体
typedef struct {
uint8_t year; // 年份偏移,比如 25 表示 2025 年
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t mode; // 使用的模式
uint8_t intensity; // 使用的力度
uint8_t duration; // 使用的时长
uint16_t crc; // 校验和,防止数据错误
} HistoryRecord_t;
// 写入历史记录
void History_Write(HistoryRecord_t *record) {
uint8_t index;
uint16_t addr;
// 读取当前索引
EEPROM_Read(0x0006, &index, 1);
// 计算写入地址:0x0010 + index * 24
addr = 0x0010 + (index * sizeof(HistoryRecord_t));
// 写入记录
EEPROM_Write(addr, (uint8_t *)record, sizeof(HistoryRecord_t));
// 更新索引(循环)
index = (index + 1) % 10;
EEPROM_Write(0x0006, &index, 1);
}
你看,这样每次写入都会覆盖最旧的那条记录,永远保留最新的 10 条。而且因为索引是单独存的,即使写入过程中断电,最多丢一条记录,不会把整个历史区搞乱。
4.4 避坑指南:我踩过的几个坑
最后,分享几个我实际项目中踩过的坑,希望能帮你省点时间:
- 坑一:地址对齐问题。 有些 MCU 的 EEPROM 要求 2 字节或 4 字节对齐,否则读写会出错。我建议所有结构体都按 4 字节对齐,省心。
- 坑二:默认值写错。 有一次我把力度默认值写成了 0,结果用户开机发现没力度,以为机器坏了。后来改成 5,问题解决。
- 坑三:忘记预留空间。 产品上市后,市场部突然说要加一个“加热功能”,需要存温度参数。结果地址全占满了,只能硬着头皮改地址映射表,导致老设备不兼容。
嗯,关于数据存储规划,今天就聊到这儿。下一节我们讲讲如何实现参数的读写接口,以及怎么保证数据的一致性。到时候见。