#申请原创# @21小跑堂
单片机在断电后,RAM 数据区的数据就会全部丢失
用 MP3 播放器打个比方,如果每次重新开机,都从第一首开始播放,过不了几天,再听到这首歌就会像听到早上的闹钟一样
如果想保存当前播放的曲目,下次开机时继续播放,则需要将参数保存在 EEPROM 或者 FLASH 里
但是,EEPROM 或者 FLASH 可靠吗?
EEPROM 例如 AT24Cxx 系列,寿命大致为10万次擦写,如果每播放一首,擦写一次 EEPROM,每天20首歌,可以用13年,可以用到过时
外置 Flash 例如 W25Qxx 系列,基本也能达到10万次擦写寿命
内置 Flash 则相对比较娇贵了,寿命大致在1万次擦写,使用寿命打折到1年,这。。。
有什么办法能延长使用时间呢?
一、减少擦写次数
每次擦写前先读取,如果数据一致,则没必要再进行擦写
缓存需要擦写的数据,仅擦写最后的数据,变多次为一次
使用电池或大电容蓄电,在断电时统一保存数据,这样断电一次,只擦写一次
二、以空间换时间
如果频繁擦写不可避免,可以分散对单个存储单元擦写的压力,均衡磨损多个存储单元
举个栗子:
一双鞋每天穿,可以穿1年,买10双轮流穿,就可以穿10年了吧
Flash 擦写也是一样,多个区块轮流擦写,这样寿命就可以UpUpUp了
以沁恒 CH32V103 为例,芯片兼容 STM32F103 的 1024 字节 Flash 区块擦写,还支持 128 字节的快速擦写模式
正常存储参数时,先擦除 1 个区块 1024 字节空间,再写入 1 个 unsigned short 或者 unsigned long 参数,即 2~4 字节
为了延长 Flash 使用时间,我们同样使用 1 个区块 1024 字节空间,在写入 1 个 unsigned long 参数时,判断 Flash 空间是否为空(为 0xFFFFFFFF),如果非空,往后 4 字节判断是否为空,直到找到空的 Flash 空间,如果超出 1024 字节空间还没找到空区间,则擦除整个区块,把参数写在第一个字节空间
读取参数类似,从第一个空间开始往后读取,找到最后一个非空单元,该单元存储的参数即为最后存的参数
这样就实现了对该区间的均衡磨损,如果区块大小为 1024 字节,折寿命直接延长为 1024 / 4 字节 = 256倍,够用到退休了
并且这个 1024 字节区间还可以扩展为 2048 甚至更多,寿命呈倍数增长
均衡写入代码如下,Addr 参数为内部 Flash 地址,Size 为区块大小,单个参数在这个区块内均衡磨损读写,Data 即为需要写入的数据:
u8 Flash_Balance_WriteU32(u32 Addr, u32 Size, u32 Data)
{
u8 ret;
u32 i;
for(i=0; i<Size; i+=4)
{
if(Flash_ReadU32(Addr+i) == 0xffffffff) break;
}
if(i >= Size)
{
ret = 0;
for(i=0; i<Size; i+=FLASH_SECTOR_SIZE)
{
ret |= Flash_EraseSector(Addr+i);
}
ret |= Flash_WriteU32(Addr, Data);
}
else
{
ret = Flash_WriteU32(Addr+i, Data);
}
return ret;
}
均衡读取代码如下:
u8 Flash_Balance_ReadU32(u32 Addr, u32 Size, u32 *Data)
{
u32 i;
for(i=0; i<Size; i+=4)
{
if(Flash_ReadU32(Addr+i) == 0xffffffff) break;
}
if(i == 0)
{
*Data = 0xffffffff;
return SR_ERR;
}
else
{
*Data = Flash_ReadU32(Addr+i-4);
return SR_OK;
}
}
问题一:效率高吗?由于均衡磨损基于查找算法,从头到尾查找,区间大小变大时,需要查找整个区间,查找时间会变多,效率会变低,所以 Size 不能取太大
解决办法也有很多,前辈们设计了很多查找算法,例如二分法,就能更好的处理大区间查找
问题二:可靠性高吗?如果在区间满的时候,写入数据,这时候会擦除整个区间,在擦除完成时如果异常断电,则会丢失这个数据
采用通用擦写操作时,这个问题也会出现,由于 Flash 需要擦除,再写入,所以擦除完还没写入时断电,都会导致数据丢失,这个问题解决办法也有很多,可以使用 EEPROM,不需要擦除就没有这个问题,也可以先备份数据设置标志位,再擦除,再写入,这里就不再扩展
问题三:可以存多少个数据?理论上 1 个区块存 1 个数据,也可以多个区块存 1 个数据
如果想 1 个区块存多个数据,甚至存多个可变长度数据例如字符串,可以采用帧头+数据长度+删除标志位+数据的方式进行读写,方法有很多种,这个我也曾经做过,可以一起讨论
优点一:在未进行擦除前,历史参数可还原
由于区块未满前,设置的数据依次往后填充区块,所以很容易找的每次设置的参数,知道每次设置都设置了哪些参数
当然还有上面提到的增加使用时间等优点,由于不用擦除,在一些时候也会减少写入时间
|