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

[复制链接]
4163|49
 楼主| zhanan 发表于 2023-3-11 20:37 | 显示全部楼层
phoenixwhite 发表于 2023-3-11 20:06
还不如外界EEPROM了。

eeprom在淘汰的边缘,外接的很多都是FLASH了。
 楼主| zhanan 发表于 2023-3-11 20:44 | 显示全部楼层
mollylawrence 发表于 2023-3-11 20:13
把Flash部分扇区当作EEPROM使用??

是的,项目中经常有保存设置参数,8位机会额外提供少量的eeprom,但32位mcu不提供了,flash倒是很富余,不用白不用。
claretttt 发表于 2023-3-11 20:51 | 显示全部楼层
这个以后存储数据简单很多了。              
 楼主| zhanan 发表于 2023-3-11 20:54 | 显示全部楼层
本帖最后由 zhanan 于 2023-3-11 21:00 编辑
kkzz 发表于 2023-3-11 20:27
这个写入的速度快吗?

怎么比? 和eeprom比,换页的时候差不多时间,不换页则快100倍。换页是在页写满了才进行,如果eeprom变量又多又频繁设置,可以把页设置的大一些,降低发生换页的概率。如果因为换页,影响高速设备运行,可以在高速设备运行期间,不进行eeprom的写操作,改到高速设备暂停时进行。
cashrwood 发表于 2023-3-11 21:00 | 显示全部楼层
许多应用场合下需要用eeprom保存非易失性的数据
 楼主| zhanan 发表于 2023-3-11 21:07 | 显示全部楼层
claretttt 发表于 2023-3-11 20:51
这个以后存储数据简单很多了。

是的,简单项目,连库都嫌麻烦的小应用,尤其是从8位机转过来的,设置参数虽然不多,但没有eeprom就是不行,必须的。
qgbgzp 发表于 2025-7-15 15:06 | 显示全部楼层
刚接触芯源,有这么好的实例,感谢
 楼主| zhanan 发表于 2025-12-31 22:47 | 显示全部楼层
有幸入选应用好贴,https://bbs.21ic.com/icview-3327088-1-1.html
重新整理了一下,方便初学者一看就能应用。
把eeprom变量数组的定义及初始化直接放在用户程序中了。
 楼主| zhanan 发表于 2025-12-31 22:48 | 显示全部楼层
/************ eeprom.h 文件内容 **************/
#include "J_CW32L010.h"

typedef enum {MBid,JDyd} en_feedata_t; // 变量ID
extern u16 FEE_data[2]; // 变量表初始值,数组标号对应变量ID。变量表和菜单参数放在一起

extern void FEE_init(void); // eeprom初始化
extern u16 FEE_rd(en_feedata_t); // 读
extern void FEE_wr(u16,en_feedata_t ); // 写
 楼主| zhanan 发表于 2025-12-31 22:49 | 显示全部楼层
/************ eeprom.c 文件内容 *************/
/*
项目组织
1.将eeprom.c添加到项目中
2.在设置菜单所在c文件中定义数组u16 FEE_data[],并赋予初始值
3.修改eeprom.h中FEE_data数组大小及变量id枚举名
运用
4.上电初始化 FEE_init();
5.FEE_rd(en_feedata_t); // 读,数组标号用枚举名
   FEE_wr(u16,en_feedata_t); // 写
*/
#include "eeprom.h"

#define FEE_LOCKS  (512*8) /* 锁定区字节数,一个锁定位管8页4096字节,以下地址须按此对齐*/
#define FEE_P0ADDR  0x0000F800  /* P0首地址 */
#define FEE_PAGES   1  /* 每P页数只能1页 */
#define FEE_WORDS  (512/4 * FEE_PAGES)  /* 字数 = 页字节数/4 *页数 */
#define FEE_P1ADDR (FEE_P0ADDR + FEE_WORDS*4) /* P1首地址 */

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

static void FEEeof(void); // 存满处理
static void FEEput(u32 addr); // 转存处理
static void FEEextr(u32 addr); // 提取数据
static void FEEerase(u32 addr); // 擦除
static void FEEerase_put(u32 addr); // 擦存处理
/***/
u16 FEE_rd(en_feedata_t id) // eeprom读
{
  return FEE_data[id]; // 直接读取数组成员
}
/***/
void FEE_wr(u16 data, en_feedata_t id) // eeprom写
{
  if(data!=FEE_data[id])
  {
    u32 addr;
    FEE_data[id]=data; // 更新数组成员

    addr=FEEaddr+FEEoffset*4; // 写FLASH地址
    FLASH->PAGELOCK = 0x5A5A0000|(1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区
    while (FLASH->ISR & 0x20); // 等待空闲
    FLASH->CR1 = 0x5A5A0001; // 页编程模式
    *((u32 *)addr) = ((u32)id)<<16|data; //id在高半字,值在低半字
    while (FLASH->ISR & 0x20); // 等待完成
    FLASH->CR1 = 0x5A5A0000; // 改为只读
    FLASH->PAGELOCK = 0x5A5A0000; // 锁定页面
    FEEoffset++; // 指向下一个字
    FEEeof(); // 存满处理
  }
}
/***/
static void FEEput(u32 addr) // 转存
{
  FLASH->PAGELOCK = 0x5A5A0000|(1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区,转存用到的地址必须都在同一个锁定区
  while (FLASH->ISR & 0x20); // 等待空闲
  FLASH->CR1 = 0x5A5A0001; // 页编程模式
  for(FEEoffset=1; FEEoffset<=(sizeof(FEE_data)/sizeof(FEE_data[0])); FEEoffset++) // 数组各成员
  {
    *((u32 *)(addr+FEEoffset*4)) = ((FEEoffset-1)<<16)|FEE_data[FEEoffset-1]; // id在高半字,值在低半字
    while (FLASH->ISR & 0x20); // 等待完成
  }
  *((u32 *)addr) = ++FEEcounts; // 页首写累计擦除次数作为启用标记
  while (FLASH->ISR & 0x20); // 等待完成
  FLASH->CR1 = 0x5A5A0000; // 改为只读
  FLASH->PAGELOCK = 0x5A5A0000; // 重新锁定
  FEEaddr=addr; // 作为当前页
}
/***/
static void FEEerase(u32 addr) // 擦除指定页面(只擦一页,每P有多页要改)
{
  FLASH->PAGELOCK = 0x5A5A0000|(1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区
  while (FLASH->ISR & 0x20); // 等待空闲
  FLASH->CR1 = 0x5A5A0002; // 页擦除模式
  *((u32 *)addr) = 0; // 页面上写启动擦除
  while (FLASH->ISR & 0x20); // 等待完成
  FLASH->CR1 = 0x5A5A0000; // 改为只读
  FLASH->PAGELOCK = 0x5A5A0000; // 重新锁定
}
/***/
static void FEEerase_put(u32 addr) // 擦存
{       
  FEEerase(addr); //先擦除
  FEEput(addr); //再转存
}
/***/
static void FEEextr(u32 addr) // 提取数据
{
  u32 data;
  FEEaddr=addr; //作为当前页
  for(FEEoffset=1; FEEoffset<FEE_WORDS; FEEoffset++) // 页首字是启用标记,从下一个字开始
  {
    data=*(u32 *)(addr+FEEoffset*4); // 字内容
    if(~data) // 非空,有数据
    {
      FEE_data[data>>16] = data & 0xFFFF;// 替换数组成员值
    }
    else // 空,数据区结束
    {
      break;
    }
  }               
}
/***/
static void FEEeof(void) // 存满处理
{
  if(FEEoffset>=FEE_WORDS) //指针到达字数
  {
    u32 temp=FEEaddr; // 当前页
    FEEput((temp==FEE_P0ADDR)?(FEE_P1ADDR):(FEE_P0ADDR)); // 转存到另一页
    FEEerase(temp); //擦除旧页               
  }
}
/***/
void FEE_init(void) // 上电初始化
{
  u32 temp,temp0,temp1;
  
  SYSCTRL->AHBEN |= 0x5A5A0002; //开FLASH时钟
  
  temp=0;
  temp0 = *(u32 *)FEE_P0ADDR; //P0首字
  if(~temp0) temp|=1; // 非空(有0)
  if(~(*(u32 *)(FEE_P0ADDR + 4))) temp|=2; //P0数据非空
  temp1 = *(u32 *)(FEE_P1ADDR); // P1首字
  if(~temp1) temp|=4; // 非空
  if(~(*(u32 *)(FEE_P1ADDR + 4))) temp|=8; //P1数据非空
  switch (temp) /* 检查两个页面状态 */
  {
    case 0: case 2: // 0000:P0 P1全空 或 0010:P0初始化失败
      FEEcounts=1;
      FEEerase_put(FEE_P0ADDR); //擦存P0
      break;
    case 3: // 0011:P0启用中
      FEEcounts=temp0; // 累计擦除次数
      FEEextr(FEE_P0ADDR); //提取P0数据
      break;
    case 8: // 1011:P0>P1转存失败
      FEEcounts=temp0;
      FEEextr(FEE_P0ADDR); //提取P0数据
      FEEerase_put(FEE_P1ADDR); //擦存P1
      FEEerase(FEE_P0ADDR); //擦除P0
      break;
    case 12: // 1100:P1启用中
      FEEcounts=temp1;
      FEEextr(FEE_P1ADDR); //提取P1数据
      break;
    case 14: // 1110:P1>P0转存失败
      FEEcounts=temp1;
      FEEextr(FEE_P1ADDR); //提取P1数据
      FEEerase_put(FEE_P0ADDR); //擦存P0
      FEEerase(FEE_P1ADDR); //擦除P1
      break;
    case 15: // 1111:擦除失败
      if(temp0>temp1) // 标记数大的作为启用页
      {
        FEEcounts=temp0;
        FEEextr(FEE_P0ADDR); //提取P0数据
        FEEerase(FEE_P1ADDR); //擦除P1
      }
      else
      {
        FEEcounts=temp1;
        FEEextr(FEE_P1ADDR); //提取P1数据
        FEEerase(FEE_P0ADDR); //擦除P0
      }
      break;
  } /* 检查页面状态end */
  FEEeof(); // 满页处理
}
/***/
/*******************/
 楼主| zhanan 发表于 2025-12-31 22:49 | 显示全部楼层
/********* main.c *************/

#include "eeprom.h"

main()
{

  FEE_init(); // eeprom初始化

}

 楼主| zhanan 发表于 2025-12-31 22:50 | 显示全部楼层
本帖最后由 zhanan 于 2025-12-31 23:02 编辑

/********* 用户程序.c *********/

#include "eeprom.h"

/* 在全局变量定义中 */
// enum {MBid,JDyd} en_feedata_t; // eeprom变量名称,此句必须放在eeprom.h文件中
u16 FEE_data[2]={1,0}; // eeprom变量初始值:通信ID、角度原点
u8 mmbid; // 485通信从机ID
u16 mjdyd; // 角度原点,AS5600的角度值,设备安装时随便装,然后转到某个角度,记录下这个角度,以此作为角度原点

/*** 在某函数或多个函数中读取及保存 ***/
  mmbid=FEE_rd(MBid); // 读取保存的通信ID
  mjdyd=FEE_rd(JDyd); // 读取保存的角度原点
  
  FEE_wr(mmbid,MBid); // 保存修改后的通信ID
  FEE_wr(mjdyd,JDyd); // 保存修改后的角度原点
/**************/  
 楼主| zhanan 发表于 2025-12-31 22:57 | 显示全部楼层
这是一个实际应用,485通讯控制几个旋转机构。每个机构都有一个通信id。
旋转机构用AS5600角度传感器作为旋转角度判断,可以设置任意角度作为起始角度。
这两个参数修改后保存到flash中,每次开机后调用即可。
未来AI 发表于 2026-1-3 09:16 | 显示全部楼层
cw32系列MCU的Flash模拟EEPROM程序易于使用,通过编程将数据存储在MCU的Flash中,实现类似EEPROM的功能。
zephyr9 发表于 2026-1-6 18:53 | 显示全部楼层
cw32系列MCU的Flash模拟EEPROM程序,通过映射部分Flash空间作为EEPROM使用,实现数据的非易失性存储。
朝生 发表于 2026-1-22 23:40 | 显示全部楼层
这款cw32系列单片机MCU,其Flash模拟EEPROM程序操作简便,适合新手入门学习。
Pretext 发表于 2026-1-29 11:20 | 显示全部楼层
cw32系列MCU的Flash模拟EEPROM程序,利用其内置Flash存储,实现数据的非易失性存储,适用于需要数据保存的场合。
理想阳 发表于 2026-2-3 19:38 | 显示全部楼层
写入Flash通常通过特定的编程器或者固件更新程序来完成,不是直接写入。
AutoMotor 发表于 2026-2-6 09:43 | 显示全部楼层
如果是存储在EEPROM或Flash等存储器中的数据,可以通过编程擦除一页数据。具体操作需要看单片机的指令手册。
MintMilk 发表于 2026-2-6 23:40 | 显示全部楼层
在单片机中,EEPROM常用于存储不会因为断电而丢失的数据,比如配置参数和重要信息。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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