|
路线栈欢迎您!
您需要 登录 才可以下载或查看,没有帐号?立即注册
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上也是存在的。
|
|