ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【嵌入式】STM32内部NOR Flash磨损均衡与掉电保护总结 [打印本页]

作者: 河曲智叟    时间: 3 天前
标题: 【嵌入式】STM32内部NOR Flash磨损均衡与掉电保护总结

1. NOR Flash与NAND Flash

先deepseek看结论:
特性Nor FlashNAND Flash读取速率快(支持随机访问,直接执行代码)较慢(需按页顺序读取)写入/擦除速率慢(擦除需5秒,写入需逐字节操纵)快(擦除4ms,按块操纵)存储密度低(1MB-1GB,得当小容量)高(8GB-1TB+,得当大容量)擦写寿命约10万次约100万次成本高低坏块管理无需坏块管理,可靠性高需ECC纠错和坏块管理代码执行直接运行代码需将代码加载到RAM运行
2. 掉电保护原理

2.1 Flash擦除过程掉电

参考链接nor flash之擦除与写入,擦除的步调有三步:
     通过设置magic num,且存放在扇区头部,可以解决前两种题目,但第三种无法解决,这种题目只能通过扫描是否全是0xFF来检查并重新擦除!
2.2 Flash写入过程掉电

掉电后,前面写入的数据为正常数据,后面未写入,仍未0xFF。
好比写一个结构体,可能会出现写一半的情况,可通过一个状态字段来进行识别,后面进行详细说明。
2.3 磨损均衡

思量到FLASH的寿命,写入数据时是增量写入的,好比修改一个变量或者结构,会重新写入一个新的,同时将旧的标记为已删除,这个标记可以和状态字段整合到一起。
还必要思量扇区写满后数据转移的题目,本次项目中采用主扇区和备份扇区瓜代写入的机制来控制磨损均衡,需添加扇区头结构,对扇区进行标识,要做到既可以在启动时选择到准确的扇区,又可以识别出破坏的扇区。
2.4 断电数据恢复

当写扇区头或者写文件时,假如掉电,那么本次的数据需在启动时识别出来,并可以恢复到增量写入时上个文件完备的数据。本次项目未思量数据恢复的题目,只识别出了是否发生了损坏,损坏会全部擦除,上位机重新下发文件。
3. 扇区头部结构

思量到自己项目中文件较小,且写入频次较低,项目计划并没采用类似easyFlash库那样对所有扇区进行均衡写入。为了加快搜刮,项目中对文件索引单独存储一个扇区,同时联合另一个相同大小的扇区进行写满后换扇区写入。同样,文件数据也采用两个扇区进行瓜代写入。
不管哪个扇区,思量到擦除中断电导致扇区被破坏,每个扇区的头部添加如下结构:
  1. typedef struct tagSectorHeader{
  2.     uint32_t u32Magic; //魔术字,0x12345678
  3.     uint32_t u32SectorStatus; //扇区的状态
  4. }TSectorHeader;
  5. //扇区的状态
  6. #define SECTOR_STATUS_INIT 0xFFFFFFFF   //未使用
  7. #define SECTOR_STATUS_USED 0x00FFFFFF   //已使用,写完数据后设置该装填
  8. #define SECTOR_STATUS_ERASE 0x0000FFFF  //擦除中,先搬运数据,搬运完毕后擦除扇区,变为未使用
复制代码
这里利用了Flash中的一个小技巧,对同一个地点,写4字节数据时,可以多次写入,但必须按照先写0x00FFFFFF再写0x0000FFFF这种方式进行写入,只要之前是FF位置的数据就可以写入。
判定扇区是否损坏:
  1. uint32_t isBadSector(TSectorHeader *pHdr)
  2. {
  3.     // 魔术字无效且不是初始值 或 魔术字是初始值但扇区状态不是初始值
  4.     if (pHdr->u32Magic != SECTOR_MAGIC_NUM && pHdr->u32Magic != 0xFFFFFFFF
  5.         || pHdr->u32Magic == 0xFFFFFFFF && pHdr->u32SectorStatus != SECTOR_STATUS_INIT)
  6.     {
  7.         return 1; // 扇区损坏
  8.     }
  9.     return 0;
  10. }
复制代码
第一次写扇区头:
  1. int32_t writeNewSectorHdr(uint32_t u32SectorAddr)
  2. {
  3.     // 标记新扇区 - 使用状态
  4.     if (writeSectorStatus(u32SectorAddr + offsetof(TSectorHeader, u32SectorStatus), SECTOR_STATUS_USED) != 0)
  5.     {
  6.         return -1;
  7.     }
  8.     // 标记新扇区 - 魔术字
  9.     uint32_t u32Magic = SECTOR_MAGIC_NUM;
  10.     if (AflFlashWriteWords(u32SectorAddr + offsetof(TSectorHeader, u32Magic), &u32Magic, 1) != 0)
  11.     {
  12.         return -1;
  13.     }
  14.     return 0;
  15. }
复制代码
更改扇区状态:
  1. int32_t writeSectorStatus(uint32_t u32StatusStartAddr, SECTOR_STATUS_DEFINE statusNow)
  2. {
  3.     int32_t iRet = 0;
  4.     FILEINDEX_STATUS_DEFINE statusLast; // 注意状态写入必须按状态定义的顺序进行写入!
  5.     AflFlashReadWords(u32StatusStartAddr, &statusLast, 1);
  6.     if (statusNow == SECTOR_STATUS_USED)
  7.     {
  8.         if (statusLast != SECTOR_STATUS_INIT)
  9.         {
  10.             ASSERT(1, "last sector status err!");  
  11.             return -1;
  12.         }
  13.     }
  14.     else if (statusNow == SECTOR_STATUS_ERASE)
  15.     {
  16.          if (statusLast != SECTOR_STATUS_USED)
  17.         {
  18.             ASSERT(1, "last sector status err!");
  19.             return -1;
  20.         }
  21.     }
  22.     else
  23.     {
  24.     }
  25.     iRet = AflFlashWriteWords(u32StatusStartAddr, &statusNow, 1);
  26.     return iRet;
  27. }
复制代码
4. 文件索引结构

  1. //文件索引的操作状态
  2. #define FILEINDEX_STATUS_INIT 0xFFFFFFFF         //未使用
  3. #define FILEINDEX_STATUS_PRE_WRITE 0x00FFFFFF    //准备写入
  4. #define FILEINDEX_STATUS_FIN_WRITE 0x0000FFFF    //已写入 写入索引结构数据即更新,文件数据靠CRC校验
  5. #define FILEINDEX_STATUS_FIN_DEL 0x000000FF      //已删除
  6. //#define FILEINDEX_STATUS_PRE_DEL 0x000000FF    //准备删除 该操作可以用来恢复上次的数据,本次设计无需恢复数据
  7. //文件索引信息
  8. typedef struct tagFileIndex
  9. {
  10.     uint32_t u32DataStatus;      // 操作状态
  11.     uint32_t u32IndexAddr;       // 该索引在扇区中的起始地址
  12.     uint32_t u32DataAddr;        // 该文件在Flash中的起始地址
  13.     uint32_t u32DataLen;         // 数据块的长度
  14.     uint16_t u16FileCRC;         // 数据校验值
  15.     uint16_t u16AlignReserved;   // 对齐保留
  16.     // 其他数据
  17. }TFileIndex;
复制代码
写索引时,先写状态为预备写入,然后写结构体字段,写完后再修改状态为写入完成。
好比写一个文件,会先写索引结构,再写文件内容,索引结构中添加状态字段来进行控制,文件内容没写完掉电,可通过检查索引结构中的文件长度、CRC来对文件内容进行校验。
写索引数据:
  1. // 写索引数据,状态无需传入,内部控制
  2. int32_t writeFileIndexToFlash(uint32_t u32CopyToAddr, TFileIndex *ptfileIndex)
  3. {
  4.     int32_t iRet = 0;
  5.     do
  6.     {
  7.         // 先写状态为【准备写入】
  8.         iRet = writeFileIndexStatus(u32CopyToAddr + offsetof(TFileIndex, u32DataStatus), FILEINDEX_STATUS_PRE_WRITE);
  9.         if (iRet != 0)
  10.             break;
  11.         //写索引地址
  12.         iRet = AflFlashWriteWords(u32CopyToAddr + offsetof(TFileIndex, u32IndexAddr), &u32CopyToAddr, 1);
  13.         if (iRet != 0)
  14.             break;
  15.         // 写剩余的其他所有数据
  16.         iRet = AflFlashWriteWords(u32CopyToAddr + offsetof(TFileIndex, u32DataAddr),
  17.                 (uint32_t *)&ptfileIndex->u32DataAddr, (sizeof(TFileIndex) - offsetof(TFileIndex, u32DataAddr))/4);
  18.         if (iRet != 0)
  19.             break;
  20.         // 最后,写状态为【已写入】
  21.         iRet = writeFileIndexStatus(u32CopyToAddr + offsetof(TFileIndex, u32DataStatus), FILEINDEX_STATUS_FIN_WRITE);
  22.         if (iRet != 0)
  23.             break;
  24.         ptfileIndex->u32DataStatus = FILEINDEX_STATUS_FIN_WRITE;
  25.         ptfileIndex->u32IndexAddr = u32CopyToAddr;
  26.     } while (0);
  27.     if (iRet != 0)
  28.     {
  29.         DEBUG(DEBUG_ERROR, ("writeFileIndexToFlash Error!!"));
  30.     }
  31.     return iRet;
  32. }
复制代码
写文件数据(略),过程就是先写索引,再写数据,最后检查写入的文件CRC是否准确。
5. 总结

以上是状态控制的核心代码,其他和写文件数据内容相关、扇区切换相关略去了,以后有时机可以写个通用的、小型的模块。
数据恢复的思路如下,本次检查到非法数据,直接擦了:
   当写入新的结构时(留意这个结构必须具备唯一标识符),需将旧的结构标记为预备删除,然后写新的结构,当新结构数据写完后,会将旧的结构数据再标记为已删除。这样初始化时扫描所有数据,假如同一个标识符的结构,一个状态为预备删除,一个为预备写入,此时认为数据未拷贝完,可以将预备写入改为已删除,废掉这个数据,重新拷贝一个新的,此时即使再次掉电也没题目。假如同一个标识符的结构,一个状态为预备删除,一个为写入完成,此时认为数据已经拷贝完,将预备删除改为已删除即可。
  6. 调试中题目



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4