赞
踩
flash为嵌入式设备中常见的存储器,优点:便宜,容量大,但缺点也比较明显,最大的缺点是寿命问题,flash编程只能将bit由1位置0,不能将0位置1,将0置1只能擦除扇区,而扇区往往比编程单位要大很多,哪怕我们只对对一个地址写两个字节的数据,也需要擦除整个扇区来完成数据更新,频繁擦写导致flash坏块。
本人这边做的一个小玩意里面需要存储一些掉电保存的数据,但修改频次又有点多,硬件上没有掉电维持电路,只能尽可能减少flash扇区擦写次数了。
说人话就是,将一个大数据扇区差分城多个数据帧,轮流写操作数据帧,当所有的数据帧都写过了再擦除重新来过。这样本来我们每次修改都需要擦出扇区,优化为现在写满一个扇区再擦除,寿命得到了成倍的提升。这里是对flash单一扇区里的数据帧磨损均衡,同样可以对多个扇区进行扇区磨损均衡,同理。
本人需要存储的数据一个定长浮点型数组,只针对本人项目的需要,实现起来也相对来说简单些。
例如一个flash 扇区 2k,我们存储一个short[14]的数组。
我们将数据帧格式定义为 |帧头|data[28]||unused[2]|帧尾|
帧头 0xA5 为 数据帧有效,0XFF代表未使用,其他则代表数据帧废弃。扇区中只存在一个有效的数据帧。
读取:从扇区中遍历数据块,找到帧头0XA5的数据帧并检验数据返回
写入:从扇区中遍历数据块,找到帧头0XA5的数据帧置0(本人亲测STM32F103内部flash是可写入,可能有的平台不可以,未使用数据可拿一位做擦除标记位,),找到0XFF的帧头,若未找到则擦除扇区写入。
源码如下
#define SECTOR_SIZE 2048 #define FRAME_SIZE 32 //枕头帧尾 14*U16 + 2*U8 typedef struct { const u32 addr; //起始地址 u32 currAddr; //起始地址 u16 count; //当前读写块 u8 buff[FRAME_SIZE]; //数据 } FLASH_LEVELINGType; FLASH_LEVELINGType flashLeveling = { .addr = 0x0803F800, .currAddr= 0x0803F800, .count= 0, .buff = {0}, }; void FlashLeveingWrite(u8 * data) { FLASH_Unlock(); //解锁 FlashNewFrame( ); flashLeveling.buff[0] = 0xa5; flashLeveling.buff[FRAME_SIZE-1] = 0x5a; memcpy(flashLeveling.buff+1,data,FRAME_SIZE-2); FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成 STMFLASH_Write_NoCheck(flashLeveling.currAddr,(u16 *)flashLeveling.buff,FRAME_SIZE/2); DBG_DEBUG("0x%x %d写完%x\r\n",flashLeveling.currAddr,flashLeveling.count,flashLeveling.buff[0]); FLASH_Lock(); //上锁 } u8* FlashLeveingRead() { FlashActiveFrame(); return flashLeveling.buff; } void FlashNewFrame() { u16 i = 0; while(i <= SECTOR_SIZE/FRAME_SIZE) { flashLeveling.currAddr = flashLeveling.addr +i* FRAME_SIZE; FLASH_Read(flashLeveling.buff,flashLeveling.currAddr, FRAME_SIZE); if( flashLeveling.buff[0] == 0xFF && flashLeveling.buff[FRAME_SIZE-1] == 0xFF) { DBG_DEBUG("%d写\r\n",i); flashLeveling.count = i; return; }else if( flashLeveling.buff[0] == 0xa5 && flashLeveling.buff[FRAME_SIZE-1] == 0x5a) { memset(flashLeveling.buff,0,FRAME_SIZE); FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成 STMFLASH_Write_NoCheck(flashLeveling.currAddr,(u16 *)flashLeveling.buff,FRAME_SIZE/2);//清0 } i++; } if(i >SECTOR_SIZE/FRAME_SIZE ) { FLASH_WaitForLastOperation(FLASH_WAITETIME); //等待上次操作完成 DBG_DEBUG("擦%d\r\n",FLASH_ErasePage(flashLeveling.addr)); CLEAR_BIT(FLASH->CR, FLASH_CR_PER); / flashLeveling.count = 0; flashLeveling.currAddr = flashLeveling.addr; } } void FlashActiveFrame() { u16 i = 0; while(i <= SECTOR_SIZE/FRAME_SIZE) { flashLeveling.currAddr = flashLeveling.addr +i* FRAME_SIZE; FLASH_Read(flashLeveling.buff,flashLeveling.currAddr, FRAME_SIZE); if( flashLeveling.buff[0] == 0xa5 && flashLeveling.buff[FRAME_SIZE-1] == 0x5a) { flashLeveling.count = i; return; } i++; } if(i >SECTOR_SIZE/FRAME_SIZE ) //没存数据 { flashLeveling.count = 0xffFF;//标示未使用一个数据块 flashLeveling.buff[0] = 0xff; } }
只需要修改一下数据帧格式就好,数据帧可做类链表串联,留一个字节值向下一个数据帧序号。
|帧头|data[28]||nextFrame|帧尾|
代码有空再补。
上面的其实都是对单一数据存储,一个时间点中只有一个有效数据在读写,而多个不定长数据存储,则某种程度上已经类似文件管理。
这样扇区应当分成两个区,一个作为 Frame Allocation Table(数据帧分配表),另一个作为数据区
Frame Allocation Table(数据帧分配表) 子项结构
以2048字节一个扇区为例 ,前64个字节作为FAT表,后面的为数据区,32字节为一数据帧。
FAT表子项结构定义:u16 :0x00 擦除帧,0xf7 数据帧被使用且是首帧, 0xf3 数据帧被使用非头帧
FRAME 结构:
|帧头|data[28]||nextFrame|帧尾|
这样可以根据FAT表获取扇区中总共有多少个数据被存储,至于区分这些数据哪一个数自己想要的,可以在frame结构着手。
例如 数据首帧,最开始存储的字符串为数据名,根据数据名获取对应的数据。
例如 saveDate = data1+ data2+ data3;
根据以上思路,应该是可以实现的。
代码有空再补。
只做抛砖引玉。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。