打印
[方案相关]

简单实用的FLASH模拟EEPROM程序在HC32L136上的实现

[复制链接]
7210|58
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
zhanan|  楼主 | 2023-2-11 21:48 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
#申请原创#
在 https://bbs.21ic.com/icview-3282436-1-1.html
我提出了一个用FLASH模拟EEPROM的方案,对于保存少量的eeprom变量,速度快,占用资源少。

手里有一块HC32LFx3x-STK-V2.0开发板,芯片是HC32L136,试着将那个方案移植过来。
HC32L136能用,HC32的L和F系列几乎拿来主义,因为HC32系列mcu的FLASH资源几乎是一样的。

和CW32比较,HC32操作FLASH要啰嗦一些:
其一,HC32需要设置FLASH擦写定时参数。
其二,解锁FLASH写操作是一个写3个字的序列,中间不能插入别的写操作。以下是手册第330页中的描述:
“注意:
–  写  0x5a5a、0xa5a5、写目标寄存器,这三步写操作之间不可插入任何写操作(写ROM、RAM、REG) ,否则无法改写目标寄存器的数值。如改写失败,需要重新进行这三步操作。 ”

其他的就一样了,一页有512字节,合128字,一个eeprom变量占用一个字,一页有127个次的eeprom变量保存空间,用两页轮换写,少量eeprom变量情况下,擦写寿命不低于eeprom吧,写速度比真的eeprom存储器更有优势,因为eeprom是边擦边写,擦的时间决定了快不了。
可以按字、半字、字节写,本模拟程序只用到按字写,按字写时间52uS,按字写是常规操作。换页时有转存和页擦除操作,页擦除时间5mS,变量如果比较多,转存和擦除时间要长一些,但换页不是经常发生。
页面擦写保护位设置也一样。但是在试验中发现一个问题:擦写保护位寄存器 SLOCK,手册里写的复位值是0,禁止擦写的意思,但芯片复位后读出来的却是全1,1是不保护,解锁的意思,这是什么情况呢?




对于定时参数,厂家给了一个基准时间,是在 HCLK=4MHz 的条件下,如果 HCLK 不一样就按频率乘数xFreq调整,并且在用到FLASH擦写时最好在几个推荐的频率上选择:
4M:xFreq=1,8M:xFreq=2,16M:xFreq=4,24M:xFreq=6,32M:xFreq=8
  enum en_flash_prgtimer /* HCLK=4M 基准定时参数 */
  {
    Tnvs      = 32u,    /* 0x20 */
    Tpgs      = 23u,    /* 0x17 */
    Tprog     = 27u,    /* 0x1B */
    Tserase   = 18000u, /* 0x4650 */
    Tmerase   = 140000u,/* 0x222E0 */
    Tprcv     = 24u,    /* 0x18 */
    Tsrcv     = 240u,   /* 0xF0 */
    Tmrcv     = 1000u,  /* 0x3E8 */
  };

对于解锁序列,只有中断会突然插入,由于FLASH擦写本身也是要停止CPU运行,中断也不会很好的处理,所以FLASH擦写期间干脆关闭总中断。对于不能关中断、对中断敏感的程序,在其运行时,可以不进行FLASH的擦写操作。

我的程序只有一个文件 eeprom.c ,添加到项目中,运用时有几个地方要改一下:
1. 定义 eeprom 变量
   eeprom 变量是用数组 u16 FEEdata[] 来组织的,成员就是 eeprom 变量,有几个eeprom变量,数组就设多大。
   这个方案限制了位宽只有16位,16位可以满足大部分的应用,可以扩展位宽,eeprom变量个数不到15个时,仅占用4位,剩余28位给变量的值用。
   数组不对外,免遭破坏,或引起混乱,数据访问是通过对外函数进行的。
   数组毕竟是用数字下标来访问,变量名称转变为数组下标还要烧脑,不如引入枚举来当下标,枚举名就是eeprom变量名。枚举类型要放到头文件里去,好让外部程序知道。

   枚举:typedef enum {En1,En2,En3,En4,En5,En6,En7,En8,En9} en_feedata_t;
   数组:u16 FEEdata[]={100,200,120,60,30,120,120,100,50};
         数组成员值就是eeprom变量的初始值(出厂值)

   然后,在程序的帮助下,对 eeprom 变量的操作就非常简单了:

             读操作:FEE_rd ( 枚举名 );

             写操作:FEE_wr ( 值, 枚举名 );

2. 指定所用到的页面地址
       #define FEE_P0ADDR  0x0000FC00  /* P0首地址 */
       #define FEE_LOCKS  (512*4) /* 解锁块字节数 */
   P0和P1用连续的两个页面即可,这样只需对 FEE_P0ADDR 指定即可,并且这两个页面都在一个 SLOCK 位保护区内。FLASH容量小的芯片,一个保护位保护4页(2048字节),而容量大的,一个保护位保护8页(4096字节),FEE_LOCKS要改一下。
   如果嫌一页128字不够用,用到2页,那 FEE_WORDS 和程序方面就都要改一下,主要是需要连续擦除2页,不是此文重点。

3. void FEE_init(void) 是FLASH初始化的程序,负责找到最后保存的数据,建立起状态,必须放到主程序 main 开始的地方。

由于没用库,程序又不复杂,有问题很容易查。其他的看程序吧。程序状态图在这里再贴一下,方便看程序。




===== eeprom.h ====
#include "j_hc32l13x.h"
typedef enum {En1,En2,En3,En4,En5,En6,En7,En8,En9} en_feedata_t; // 变量ID

extern void FEE_init(void);
extern u16 FEE_rd(en_feedata_t);
extern void FEE_wr(u16,en_feedata_t);

===================================
==== eeprom.c =====

#include "eeprom.h"

//typedef enum {En1,En2,En3,En4,En5,En6,En7,En8,En9} en_eepdata_t; // 这个是头文件里的枚举类型备注,以便和值对应
static u16 FEEdata[]={100,200,120,60,30,120,120,100,50}; //变量表原始值,数组标号对应ID

  /*** flash擦写定时参数,基于HCLK ***/
  /*频率乘数,基于HCLK:1-4M,2-8M,4-16M,6-24M,8-32M(须插入FLASH等待周期) */
  #define xFreq  2  /* 当前频率乘数 */
  /***/
  enum en_flash_prgtimer /* HCLK=4M 基准定时参数 */
  {
    Tnvs      = 32u,    /* 0x20 */
    Tpgs      = 23u,    /* 0x17 */
    Tprog     = 27u,    /* 0x1B */
    Tserase   = 18000u, /* 0x4650 */
    Tmerase   = 140000u,/* 0x222E0 */
    Tprcv     = 24u,    /* 0x18 */
    Tsrcv     = 240u,   /* 0xF0 */
    Tmrcv     = 1000u,  /* 0x3E8 */
  };
  #define FlashUnlock() FLASH->BYPASS = 0x5A5A; FLASH->BYPASS = 0xA5A5 /* FLASH解锁命令 */
  #define Irq_Disable asm("CPSID i") /* 禁止总中断 */
  #define Irq_Enable  asm("CPSIE i") /* 开放总中断 */
  /* --------- */

#define FEE_P0ADDR  0x0000FC00  /* P0首地址 */
#define FEE_WORDS  (512/4) /* 字数 = 页字节数/4 */
#define FEE_P1ADDR (FEE_P0ADDR + FEE_WORDS*4) /* P1首地址 */
#define FEE_LOCKS  (512*4) /* 解锁块字节数 */

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 FEEdata[id]; // 直接读取数组成员
}
/***/
void FEE_wr(u16 data, en_feedata_t id) // eeprom写
{
  if(data!=FEEdata[id])
  {
    u32 addr;
    FEEdata[id]=data; // 更新数组成员
   
    Irq_Disable; // 禁止中断
    addr=FEEaddr+FEEoffset*4; // 写FLASH地址
    FlashUnlock(); FLASH->SLOCK = (1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区
    while (FLASH->CR & 0x10); // 等待空闲
    FlashUnlock(); FLASH->CR_f.OP = 0x1; // 编程模式
    *((u32 *)addr) = ((u32)id)<<16|data; //id在高半字,值在低半字
    while (FLASH->CR & 0x10); // 等待完成
    FlashUnlock(); FLASH->CR_f.OP = 0; // 改为只读
    FlashUnlock(); FLASH->SLOCK = 0; // 锁定页面
    Irq_Enable; // 开放中断
    FEEoffset++; // 指向下一个字
    FEEeof(); // 存满处理
  }
}
/***/
static void FEEput(u32 addr) // 转存
{
  FEEaddr=addr; // 作为当前页
  Irq_Disable; // 禁止中断
  FlashUnlock(); FLASH->SLOCK = (1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区,转存用到的地址必须都在同一个锁定区
  while (FLASH->CR & 0x10); // 等待空闲
  FlashUnlock(); FLASH->CR_f.OP = 0x1; // 编程模式
  for(FEEoffset=1; FEEoffset<=(sizeof(FEEdata)/sizeof(FEEdata[0])); FEEoffset++) // 数组各成员
  {
    *((u32 *)(addr+FEEoffset*4)) = ((FEEoffset-1)<<16)|FEEdata[FEEoffset-1]; // id在高半字,值在低半字
    while (FLASH->CR & 0x10); // 等待完成
  }
  *((u32 *)addr) = ++FEEcounts; // 页首写累计擦除次数作为启用标记
  while (FLASH->CR & 0x10); // 等待完成
  FlashUnlock(); FLASH->CR_f.OP = 0; // 改为只读
  FlashUnlock(); FLASH->SLOCK = 0; // 重新锁定
  Irq_Enable; // 开放中断
}
/***/
static void FEEerase(u32 addr) // 擦除指定页面
{
  Irq_Disable; // 禁止中断
  FlashUnlock(); FLASH->SLOCK = (1<<(addr/FEE_LOCKS)); // 解锁地址所属锁定区
  while (FLASH->CR & 0x10); // 等待空闲
  FlashUnlock(); FLASH->CR_f.OP = 0x2; // 页擦除模式
  *((u32 *)addr) = 0; // 页面上写启动擦除
  while (FLASH->CR & 0x10); // 等待完成
  FlashUnlock(); FLASH->CR_f.OP = 0; // 改为只读
  FlashUnlock(); FLASH->SLOCK = 0; // 重新锁定
  Irq_Enable; // 开放中断
}
/***/
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) // 非空,有数据
    {
      FEEdata[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_timeinit(void)
{
  /*** flash擦写定时参数寄存器配置 ***/
  Irq_Disable; // 禁止中断
  FlashUnlock(); FLASH->TNVS_f.TNVS       = Tnvs    * xFreq;
  FlashUnlock(); FLASH->TPGS_f.TPGS       = Tpgs    * xFreq;
  FlashUnlock(); FLASH->TPROG_f.TPROG     = Tprog   * xFreq;
  FlashUnlock(); FLASH->TSERASE_f.TSERASE = Tserase * xFreq;
  FlashUnlock(); FLASH->TMERASE_f.TMERASE = Tmerase * xFreq;
  FlashUnlock(); FLASH->TPRCV_f.TPRCV     = Tprcv   * xFreq;
  FlashUnlock(); FLASH->TSRCV_f.TSRCV     = Tsrcv   * xFreq;
  FlashUnlock(); FLASH->TMRCV_f.TMRCV     = Tmrcv   * xFreq;
  Irq_Enable; // 开放中断
  /*** --- ***/
}
/***/
void FEE_init(void) // 上电初始化
{
  u32 temp,temp0,temp1;

  FEE_timeinit(); // 初始化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=0;
      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(); // 满页处理
}
/***/
/*******************/


使用特权

评论回复
沙发
yang377156216| | 2023-2-13 09:22 | 只看该作者
赞一个,还可以适用一下 easyflash or flashdb 这种开源的小组件哟

使用特权

评论回复
板凳
uytyu| | 2023-3-4 12:40 | 只看该作者
这个是否还需要重新编写flash读写程序?

使用特权

评论回复
地板
jimmhu| | 2023-3-4 13:00 | 只看该作者
这个为什么不直接外接eeprom呢?

使用特权

评论回复
5
hearstnorman323| | 2023-3-4 13:11 | 只看该作者
感觉内部的flash读写怕超出范围的。

使用特权

评论回复
6
ccook11| | 2023-3-4 13:20 | 只看该作者
HC32L136可以外接多大的flash呢?

使用特权

评论回复
7
lzbf| | 2023-3-4 19:26 | 只看该作者
把Flash部分扇区当作EEPROM使用??

使用特权

评论回复
8
kkzz| | 2023-3-4 19:35 | 只看该作者
可以一字节一字节的写入吗?              

使用特权

评论回复
9
mmbs| | 2023-3-4 19:40 | 只看该作者
FLASH在写新的数据前必须先擦除,而且经常是只允许整页擦除,没有办法擦除一个字节

使用特权

评论回复
10
deliahouse887| | 2023-3-4 20:19 | 只看该作者
简易的flash模拟eeprom功能,只需实现底层的flash操作接口

使用特权

评论回复
11
1988020566| | 2023-3-4 20:27 | 只看该作者
eeprom与flash 选哪个  

使用特权

评论回复
12
10299823| | 2023-3-4 20:33 | 只看该作者
EEPROM可擦写寿命更多,约5万次,FLASH只有10万次,但是速度更快。

使用特权

评论回复
13
jkl21| | 2023-3-4 20:56 | 只看该作者
模拟eeprom和eeprom有什么区别

使用特权

评论回复
14
minzisc| | 2023-3-4 21:36 | 只看该作者
可否把内部Flash部分扇区当作EEPROM使用??

使用特权

评论回复
15
pentruman| | 2023-3-4 22:01 | 只看该作者
EEPROM_Emul还是挺轻量的  

使用特权

评论回复
16
wengh2016| | 2023-3-4 22:06 | 只看该作者
https://gitee.com/epoko/eeprom_in_flash   

使用特权

评论回复
17
mollylawrence| | 2023-3-4 22:14 | 只看该作者
直接读写相关的数据不好吗?              

使用特权

评论回复
18
adolphcocker| | 2023-3-4 22:38 | 只看该作者
如何使用片上flash来模拟EEPROM

使用特权

评论回复
19
backlugin| | 2023-3-4 22:48 | 只看该作者
FLASH和EEPROM的最大区别是FLASH按扇区操作,EEPROM则按字节操作  

使用特权

评论回复
20
pmp| | 2023-3-5 11:14 | 只看该作者
https://gitee.com/epoko/eeprom_in_flash   

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

179

帖子

0

粉丝