| 
 | 
 
路线栈欢迎您!
您需要 登录 才可以下载或查看,没有帐号?立即注册 
 
 
 
x
 
前言 
 
提出用FLASH模拟EEPROM的方案,对于保存少量的eeprom变量,速度快,占用资源少。用手里有一块HC32LFx3x-STK-V2.0开发板,芯片是HC32L136,试着将那个方案移植过来。HC32L136能用,HC32的L和F系列几乎拿来主义,因为HC32系列mcu的FLASH资源几乎是一样的。 
 
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(); // 满页处理
 
 - }
 
 - /***/
 
 - /*******************/
 
 
  复制代码 
结语 
 
使用FLASH模拟对比eeprom来说,有过之而不及。eeprom每次都是连擦带写,耗费3-4毫秒。本模拟一般写只耗费53微秒,换页时耗费时间,但换页很少发生。 
 
换页擦除FLASH会使cpu停顿,中断不能及时响应,如果风险,就要避免在需要响应这些中断的情况下写FLASH。这个问题在本身带eeprom的mcu上也是存在的。 
 
 |   
 
 
 
 |