硬件知识
基于成本与需求的考虑,铁电已经很少使用,最常用的是eeprom与flash
eeprom 相对于 flash的优势是 寿命长(100万次 1万次),且可以按字节操作
所以一般嵌入式系统中 eeprom存储运行时参数(掉电需保存的参数或者频繁需要修改的数据),flash存储写次数较少但数据量较大的数据(日志或者记录)
软件方面
分析
- linux读写的系统调用
size_t write(int fildes,const void *buf,size_t nbytes);
size_t read(int fildes,void *buf,size_t nbytes);
- mcu驱动层提供的函数形式大致如下
xxx_read(uint8_t* const buf, size_t addr, size_t len);
xxx_write(const uint8_t* buf, size_t addr, size_t len);
对比可以看出,linux操作系统里的文件系统通过文件句柄对接了应用层和底层,应用层并不关心实际的某个"文件"的实际存储地址和存储方式,这部分工作由内核文件系统处理
可以按照这样的思路设计简单的存储系统(不考虑运行中增加参数的需求,也不考虑多进程多线程的继承关系和锁的设计)
简单设计
- 分配参数的地址
#define LEN_param1 2
#define LEN_param2 5
#define ADDR_param1 0
#define ADDR_param2 (ADDR_param1 + LEN_param1)
- 利用enum自增的机制与数组索引配合
enum
{
param1,
param2,
param_num,
} E_PARAM
//限制作用域,存储在rom中
static const struct
{
size_t addr;
size_t len;
} Param[param_num] =
{
{ADDR_param1, LEN_param1},
{ADDR_param2, LEN_param2},
}
- 封装
//这是比较简单的表现形式
read_param(E_PARAM num,uint8_t* const buf)
{
//......
xxx_read(buf,Param[num].addr, Param[num].len);
//......
}
write_param(E_PARAM num,const uint8_t* buf)
{
//......
xxx_write(buf,Param[num].addr, Param[num].len);
//......
}
优化
- 对于结构体成员的读写
一组相关的数据最好用结构体组织
//用来计算结构体成员相较于结构体起始地址的偏移量
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef struct __attribute__((packed))
{
uint8_t hight;
uint8_t weight;
uint32_t birthday;
} person_t;
person_t person1 = {170,70,20001119};
xxx_write(buf,Param[num].addr+offsetof(person_t,birthday),sizeof(person1.birthday));
- 对于一些特殊的数据
- 重要的数据:双备份,crc校验,可以用查表法减少计算量
- 读写次数巨多的数据还要考虑磨损均衡,防止被写坏
- 频繁写偶尔读的数据,可以模拟cache,写n次写内存,读的时候再固化到存储器中
- 性能上的优化
- 模拟pc删除文件的过程,建立索引,删除只清索引,提高系统响应表现
- 写长数据时可以按扇区或者块写(数据手册提供)
- 考虑存储器的特性,划分数据地址的的时候注意跨页
- 一些掉电需要存储的数据注意电容给的电压与时间够不够某些极限场景下的要求