STC8A芯片内部都有一定容量的Flash可以当作EEPROM。虽然擦除次数为10万次+,低于真正EEPROM芯片的100万次+,但是存储一些不经常修改的数据还是没有问题的,例如单片机的一些工作状态参数,在最初调整正常后很少再做调整。这样就可以省去一个EEPROM芯片的成本,大概1元左右。
1. EEPROM操作相关寄存器和基本函数
STC8的EEPROM操作涉及的寄存器一共有6个,分别是:
- 数据寄存器: IAP_DATA
- 高、低地址寄存器:IAP_ADDRH和IAP_ADDRL
- 命令寄存器:IAP_CMD
- 触发寄存器:IAP_TRIG
- 控制寄存器:IAP_CONTR
手册例程中给出了擦除、写(编程)、读(IAP模式和MOVC模式两种)、关闭IAP模式的例程,直接拷贝到自己的项目中,根据需要稍作修改即可。
- 关闭IAP程序:void IapIdle()
- IAP模式及MOVC模式读取程序:char IapRead(int addr)
- 写(编程)程序:void IapProgram(int addr, char dat)
- 擦除程序:void IapErase(int addr)
2. 注意事项
以手册中给出几个底层例程为基础,很容易就可编写出项目中操作EEPROM的功能模块。
有几点需要注意的问题,记录一下:
2.1 擦除操作是按照扇区进行的
与读、写操作不同,擦除操作是按照扇区进行的,也就是说要擦除的话,会把IapErase(int addr)函数中addr地址参数所在的512字节的扇区的内容统统删除,给出的addr只要在同一个扇区,无论指向哪个具体的地址,都会擦除同一个扇区。原因如下:
手册中记载“由于擦除是以 512 字节为单位进行操作的,所以执行擦除操作时所设置的目标地址的低 9位是无意义的。例如:执行擦除命令时,设置地址 1234H/1200H/1300H/13FFH,最终执行擦除的动作都是相同的,都是擦除 1200H~13FFH 这 512 字节”。
2.2 可自定义EEPROM大小的芯片,如何自定义
STC8系列芯片中的大部分型号EEPROM空间都是固定的,只有少部分型号可以自定义EEPROM空间大小,例如我使用的STC8A8K64S4A12。
该芯片有64K大小的Flash空间,在STC-ISP中按照扇区为单位(512字节,相当于0.5k)可定义0.5~64k大小的EEPROM空间。需要注意的是,自定义的EEPROM是被安排在64k Flash的末尾的,比如自定义了1k大小的EEPROM,那么前63k是Flash,最后1k是EEPROM。
在我的项目中,只有几个系统参数需要调整,留下1个扇区0.5k大小的EEPROM空间就足够了。
2.3 如何确定MOVC模式读取的基地址
与IAP模式的读取相比,MOVC模式的读取快捷便利,但是需要提供扇区的基地址,也就是下面程序片段中的宏IAP_OFFSET。
像上面定义了0.5k大小的EEROM时,该如何确定这个基地址呢?具体方法如下:
- 64k Flash的最后一个字节的地址是0xFFFF
- 定义0.5k的EEPROM就往前查512个字节(0x200)作为基地址,也就是0xFE00
- 同样也可计算出1k、2k、3k的基地址
1 //使用的时候,将对应宏前面的注释符号去掉即可。 2 //#define IAP_OFFSET 0x2000 //STC8A8K08S4A12 3 //#define IAP_OFFSET 0x4000 //STC8A8K16S4A12 4 //#define IAP_OFFSET 0x6000 //STC8A8K24S4A12 5 //#define IAP_OFFSET 0x8000 //STC8A8K32S4A12 6 //#define IAP_OFFSET 0xA000 //STC8A8K40S4A12 7 //#define IAP_OFFSET 0xC000 //STC8A8K48S4A12 8 //#define IAP_OFFSET 0xE000 //STC8A8K56S4A12 9 //#define IAP_OFFSET 0xF000 //STC8A8K60S4A12 10 //#define IAP_OFFSET 0xF000 //STC8A8K60S4A12 11 /*自己计算给出*/ 12 //#define IAP_OFFSET 0xF300 //STC8A8K64S4A12-3k 13 //#define IAP_OFFSET 0xF800 //STC8A8K64S4A12-2k 14 //#define IAP_OFFSET 0xFC00 //STC8A8K64S4A12-1k 15 //#define IAP_OFFSET 0xFE00 //STC8A8K64S4A12-0.5k 16 unsigned char IapRead(int addr) 17 { 18 addr += IAP_OFFSET; //使用 MOVC 读取 EEPROM 需要加上相应的偏移 19 return *(unsigned char code *)(addr); //使用 MOVC 读取数据 20 }
2.4 如何确定擦除操作的时间
擦除操作需要相对准确的时间,时间短了还没擦除干净,时间长了又会影响Flash寿命。手册给出了要求的时间范围:
手册指出,在擦除操作期间“此时 MCU 系统不给 CPU 供应时钟, CPU 没有时钟,所以无法工作, 也就是说,针对 EEPROM 操作所需要的等待时间是硬件自动完成的,用户不需要加额外的软件延时。”
可见不能用常用的软件延时方式来控制擦除时间。那么该如何确定具体的时间呢?
控制寄存器的B2~B0三个位的组合给出了对应的等待时间(以时钟周期为单位),比如选择011时,擦除操作就会等待60000个时钟周期,如果单片机系统时钟为12MHz,那么60000个时钟周期耗时:1秒÷12MHz×60000个周期 = 5ms。5ms的时间正好位于要求的4~6ms之内。因此,当系统时钟为12MHz时选择“011”是恰当的。
如果选择的系统时钟没有在手册给出的举例频率之中(下表最后一列),按照上述计算方法计算一下,看是否在4~6ms之间即可。比如系统时钟工作在11.0592MHz,1秒÷11.0592MHz×60000个周期 = 4.608ms,也在4~6微秒范围之内,这时选择“011”也是恰当的。(已经实际测试)
再试算一下18MHz的情况(没有实际测试):
- 1秒÷18MHz×60000个周期 = 7.5ms,超过4~6ms范围,选“011”长了!
- 1秒÷18MHz×100000个周期 = 5.5ms,符合4~6ms要求,选“010”组合正好!
1 //系统工作参数结构 2 typedef struct { 3 int systemSleepingTimeSpan; 4 float temperature; 5 float coolingTime; 6 float stopTime; 7 } SysParamTypedef; 8 9 /*************************************************** 10 *说明:从eeprom中读取系统工作参数 11 *参数:db为系统工作参数结构类型的指针 12 ***************************************************/ 13 void GetDatabase(SysParamTypedef *db) 14 { 15 int i; 16 for(i = 0; i < sizeof(SysParamTypedef); i++) 17 { 18 *((char*)db + i) = IapRead(i); 19 } 20 } 21 22 /*************************************************** 23 *说明:向eeprom中写入系统工作参数 24 *参数:db为系统工作参数结构类型的指针 25 ***************************************************/ 26 void SaveDatabase(SysParamTypedef *db) 27 { 28 int i; 29 if(IapErase(0) == FAILURE) 30 ErrorHandle(Error_IapFailure); 31 for(i = 0; i < sizeof(SysParamTypedef); i++) 32 { 33 if(IapWrite(i, *((char *)db + i)) == FAILURE) 34 ErrorHandle(Error_IapFailure); 35 } 36 }