#申请原创#
在 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(); // 满页处理
}
/***/
/*******************/
|
|