[CW32F003系列] cw32系列mcu简单实用的flash模拟eeprom程序

[复制链接]
4407|51
dreamCar 发表于 2026-2-8 10:16 | 显示全部楼层
存储数据变得容易了,后续操作更便捷。
朝生 发表于 2026-2-10 16:37 | 显示全部楼层
如果需要删除单页数据,确保电源稳定,使用正确的擦除命令或操作,以避免数据丢失或损坏。
星闪动力 发表于 2026-2-10 19:48 | 显示全部楼层
非易失性数据保存在EEPROM中,适合长时间保存信息,适用于需要数据持久性的场合。
破晓战神 发表于 2026-2-11 15:39 | 显示全部楼层
楼主的代码写得非常清晰,注释也很到位
软核硬核 发表于 2026-2-12 10:23 | 显示全部楼层
单片机的EEPROM是存重要配置数据的,不怕停电。
单芯多芯 发表于 2026-2-12 16:15 | 显示全部楼层
删除数据时,得稳稳当当的,用对方法,别让电源不稳或操作错误,把数据给弄丢了。
明日视界 发表于 2026-2-17 10:01 | 显示全部楼层
存储数据变得更方便快捷了。
线稿xg 发表于 2026-2-24 11:22 | 显示全部楼层
EEPROM用于长期存储数据,像存身份*信息一样,适合那些信息需要长时间保留的情况。
Pretext 发表于 2026-2-25 11:08 | 显示全部楼层
写入flash通常是通过特定的程序或指令完成的,具体取决于单片机型号和编程语言。
物联万物互联 发表于 2026-2-26 11:17 | 显示全部楼层
单片机的EEPROM用于保存关键配置,即便断电也不会丢失数据。
 楼主| zhanan 发表于 2026-8-13 15:45 | 显示全部楼层
继续改进,又有新思路
在有些模拟eeprom变量保存比较频繁的场合,两页轮换会很快,或者FLASH富余很多,只用两页太浪费了。有扇区擦除的mcu可用扇区轮换,擦除次数可明显减少,但CW系列mcu只有页擦除,一页固定512字节,拼页(多页合并当一页用)是一个办法,但拼页有两个问题:1是每次擦除都要连着擦好几页,耗费时间加倍,这在随时可能断电的情况下风险变大。2是页面解锁可能跨页,连续写入的时候要判断是否进入下一个未解锁的页面。

新思路是多页轮换,写满一页换下一页,可以任意多页轮换。
多页里面应该只有一页在用,其余页皆为备用,备用页是擦好的。
但可能各种原因备用页未必擦好备用,这就需要在上电后模拟eeprom初始化时整理一下,找到最近的可用页,其余页统统擦除备用。
从页面结构上看,偏移0(页首)记录擦除次数,从偏移4开始记录数据。写入循序也有讲究,先写数据,后写页首。

a有头有数据,表示这是一个正常页面,如果在转存时,旧的页面没来得及擦除,会有两个正常页面,区别在页首擦除次数不同,显然擦除次数大者数据近一些。
b无头有数据,不是正常页面,新页面未写完就断电了。
c无头无数据,空白页面。
其他情况,理论上应该不出现,比如数据区不是全空,数据或擦除次数出错,暂且忽略,有心的可以再想想办法。

所以,初始化只留一个正常页面的思路就来了:
1.轮询页面,c是空白页跳过,b是不正常页面直接擦除,a是正常页保留,并且记下最大擦除次数
2.再次轮询页面,只保留最大擦除次数的页面,其余擦除

整个程序反而简单清新了一些。
很多时候,变量的ID及数值都不大,这样写入FLASH的内容0很多,如果取反写入,写0会很少,对降低FLASH的磨耗是否有利,请大佬们指点一下。

下面贴出完整程序,在CW32F003X4及IAR环境下运行通过。
 楼主| zhanan 发表于 2026-8-13 15:49 | 显示全部楼层
  1. /*
  2. 项目组织
  3. 1.将eeprom.c添加到项目中
  4. 2.在设置菜单所在c文件中定义数组u16 FEE_data[],并赋予初始值
  5. 3.修改eeprom.h中FEE_data数组大小及变量id枚举名
  6. 运用
  7. 4.上电初始化 FEE_init();
  8. 5.FEE_rd(en_feedata_t); // 读,数组标号用枚举名
  9.    FEE_wr(u16,en_feedata_t); // 写
  10. */
  11. #include "eeprom.h"

  12. #define FEEP0_ADDR 0x00003C00 /* 首页地址 */
  13. #define FEEP_PCS   10         /* 页个数 */
  14. #define FEEP_BYTES 512        /* 页字节数 */
  15. #define FEE_LOCKS  2048 /* 锁定区字节数 */

  16. static u32 FEEcounts; // 累计擦除次数(用作启用页页首标记)
  17. static u32 FEEaddr; // 当前页首地址,指向当前启用页
  18. static u16 FEEoffset; // 页指针,指向当前页内可写地址

  19. static void FEEeof(void); // 存满处理
  20. static void FEEput(u32 addr); // 转存处理
  21. static void FEEextr(u32 addr); // 提取数据
  22. static void FEEerase(u32 addr); // 擦除
  23. /***/
  24. u32 FEE_rd(en_feedata_t id) // eeprom读
  25. {
  26.   return FEE_data[id]; // 直接读取数组成员
  27. }
  28. /***/
  29. void FEE_wr(u32 data, en_feedata_t id) // eeprom写
  30. {
  31.   if(data!=FEE_data[id])
  32.   {
  33.     u32 addr;
  34.     FEE_data[id]=data; // 更新数组成员

  35.     addr=FEEaddr+FEEoffset; // 写FLASH地址
  36.     FLASH->PAGELOCK = 0x5A5A0000|(1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区
  37.     while (FLASH->CR1 & 0x20); // 等待空闲
  38.     FLASH->CR1 = 0x5A5A0001; // 编程模式
  39.     *((u32 *)addr) = ((u32)id)<<24|(data&0xFFFFFF); //id在高8位,值在低24位
  40.     while (FLASH->CR1 & 0x20); // 等待完成
  41.     FLASH->CR1 = 0x5A5A0000; // 改为只读
  42.     FLASH->PAGELOCK = 0x5A5A0000; // 锁定页面
  43.     FEEoffset+=4; // 指向下一个
  44.     if(FEEoffset>=FEEP_BYTES) FEEeof(); // 存满处理
  45.   }
  46. }
  47. /***/
  48. static void FEEput(u32 addr) // 转存
  49. {
  50.   u8 nxh;
  51.   FEEaddr=addr; // 作为当前页
  52.   FLASH->PAGELOCK = 0x5A5A0000|(1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区
  53.   while (FLASH->CR1 & 0x20); // 等待空闲
  54.   FLASH->CR1 = 0x5A5A0001; // 编程模式
  55.   FEEoffset=4; // 偏移4是数据开始
  56.   for(nxh=0; nxh<(sizeof(FEE_data)/sizeof(FEE_data[0])); nxh++) // 数组各成员
  57.   {
  58.     *((u32 *)(addr+FEEoffset)) = (u32)(nxh<<24)|(FEE_data[nxh]&0xFFFFFF); // id在高8位,值在低24位
  59.     while (FLASH->CR1 & 0x20); // 等待完成
  60.     FEEoffset+=4;
  61.   }
  62.   *((u32 *)addr) = ++FEEcounts; // 页首写累计擦除次数作为启用标记
  63.   while (FLASH->CR1 & 0x20); // 等待完成
  64.   FLASH->CR1 = 0x5A5A0000; // 改为只读
  65.   FLASH->PAGELOCK = 0x5A5A0000; // 重新锁定
  66. }
  67. /***/
  68. static void FEEerase(u32 addr) // 擦除指定页面
  69. {
  70.   FLASH->PAGELOCK = 0x5A5A0000|(1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区
  71.   while (FLASH->CR1 & 0x20); // 等待空闲
  72.   FLASH->CR1 = 0x5A5A0002; // 页擦除模式
  73.   *((u32 *)addr) = 0; // 页面上写启动擦除
  74.   while (FLASH->CR1 & 0x20); // 等待完成
  75.   FLASH->CR1 = 0x5A5A0000; // 改为只读
  76.   FLASH->PAGELOCK = 0x5A5A0000; // 重新锁定
  77. }
  78. /***/
  79. static void FEEextr(u32 addr) // 提取数据
  80. {
  81.   u32 data;
  82.   FEEaddr=addr; //作为当前页
  83.   FEEcounts=*(u32 *)(addr); // 偏移0是累计擦除次数
  84.   for(FEEoffset=4; FEEoffset<FEEP_BYTES; FEEoffset+=4) // 数据从偏移4开始
  85.   {
  86.     data=*(u32 *)(addr+FEEoffset);
  87.     if(~data) // 非空,有数据
  88.     {
  89.       FEE_data[data>>24] = data & 0xFFFFFF;// 替换数组成员值
  90.     }
  91.     else // 空,数据区结束
  92.     {
  93.       break;
  94.     }
  95.   }               
  96. }
  97. /***/
  98. static void FEEeof(void) // 存满处理
  99. {
  100.   u32 temp0, temp1;
  101.   temp0= FEEaddr; // 当前页
  102.   temp1=temp0+FEEP_BYTES; // 下一页
  103.   if(temp1<(FEEP0_ADDR + FEEP_BYTES * FEEP_PCS))
  104.   {
  105.     FEEput(temp1); // 在指定范围内,切换到下一页
  106.   }
  107.   else
  108.   {
  109.     FEEput(FEEP0_ADDR); // 超出,用首页
  110.   }
  111.   FEEerase(temp0); //擦除旧页
  112. }
  113. /***/
  114. void FEE_init(void) // 上电初始化
  115. {
  116.   u8 temp;
  117.   u32 tempaddr,tempmax;

  118.   SYSCTRL->AHBEN |= 0x02; //开FLASH时钟
  119.   tempmax=0;
  120.   for(temp=0;temp<FEEP_PCS;temp++) // 轮询各页
  121.   {
  122.     tempaddr=FEEP0_ADDR + FEEP_BYTES * temp; // 页首地址
  123.     if(~(*(u32 *)(tempaddr+4))) // 数据区非空
  124.     {
  125.       if(~(*(u32 *)tempaddr)) // 首地址非空,有擦除次数
  126.       {
  127.         if((*(u32 *)tempaddr)>tempmax) tempmax=*(u32 *)tempaddr; // 记下最大的
  128.       }
  129.       else
  130.       {
  131.         FEEerase(tempaddr); // 空表示转存失败,直接擦除
  132.       }
  133.     }
  134.   }
  135.   if(tempmax) // 至少启用了一页
  136.   {
  137.     for(temp=0;temp<FEEP_PCS;temp++) // 轮询各页
  138.     {
  139.       tempaddr=FEEP0_ADDR + FEEP_BYTES * temp; // 页首地址
  140.       if(~(*(u32 *)tempaddr)) // 首地址非空,有擦除次数
  141.       {
  142.         if((*(u32 *)tempaddr)==tempmax) // 擦除次数=最大次数的
  143.         {
  144.           FEEextr(tempaddr); // 作为当前页,提取数据
  145.         }
  146.         else
  147.         {
  148.           FEEerase(tempaddr); // 非当前页则擦除
  149.         }         
  150.       }
  151.     }
  152.     if(FEEoffset>=FEEP_BYTES) FEEeof(); // 满页处理
  153.   }
  154.   else // 全空未用,初始化原始数据
  155.   {
  156.     FEEcounts=0;
  157.     FEEput(FEEP0_ADDR); // 转存
  158.   }   
  159. }
  160. /***/
  161. /*******************/

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部
0