STC单片机内部EEPROM的应用 单片机运行时的数据都存在于RAM(随机存储器)中,在掉电后RAM中的数据是无法保留的,那么怎样使数据在掉电后不丢失呢?这就需要使用EEPROM或FLASHROM等存储器来实现。在传统的单片机系统中,一般是在片外扩展存储器,单片机与存储器之间通过IIC或SPI等接口来进行数据通信。这样不光会增加开发成本,同时在程序开发上也要花更多的心思。在STC单片机中内置了EEPROM(其实是采用IAP技术读写内部FLASH来实现EEPROM),这样就节省了片外资源,使用起来也更加方便。下面就详细介绍STC单片机内置EEPROM及其使用方法。 STC各型号单片机内置的EEPROM的容量各有不同,见下表:
单片机芯片型号 |
起始地址 |
内置EEPROM容量(每扇区512字节) |
STC89C51RC,STC89LE51RC |
0x2000 |
共八个扇区 |
STC89C52RC,STC89LE52RC |
0x2000 |
共八个扇区 |
STC89C54RD+,STC89LE54RD+ |
0x8000 |
共五十八个扇区 |
STC89C55RD+,STC89LE55RD+ |
0x8000 |
共五十八个扇区 |
STC89C58RD+,STC89LE58RD+ |
0x8000 |
共五十八个扇区 |
(内部EEPROM可以擦写100000次以上) 上面提到了IAP,它的意思是“在应用编程”,即在程序运行时程序存储器可由程序自身进行擦写。正是是因为有了IAP,从而可以使单片机可以将数据写入到程序存储器中,使得数据如同烧入的程序一样,掉电不丢失。当然写入数据的区域与程序存储区要分开来,以使程序不会遭到破坏。 要使用IAP功能,与以下几个特殊功能寄存器相关:
寄存器标识 |
地址 |
名称 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
初始值 |
ISP_DATA |
0xE2 |
ISP/IAP闪存数据寄存器 |
|
|
|
|
|
|
|
|
11111111 |
ISP_ADDRH |
0xE3 |
ISP/IAP闪存地址高位 |
|
|
|
|
|
|
|
|
00000000 |
ISP_ADDRL |
0xE4 |
ISP/IAP闪存地址低位 |
|
|
|
|
|
|
|
|
00000000 |
ISP_CMD |
0xE5 |
ISP/IAP闪存命令寄存器 |
- |
- |
- |
- |
- |
MS2 |
MS1 |
MS0 |
xxxxx000 |
ISP_TRIG |
0xE6 |
ISP/IAP闪存命令触发 |
|
|
|
|
|
|
|
|
xxxxxxxx |
ISP_CONTR |
0xE7 |
ISP/IAP控制寄存器 |
ISPEN |
SWBS |
SWRST |
- |
- |
WT2 |
WT1 |
WT0 |
00xx000 |
ISP_DATA: ISP/IAP操作时的数据寄存器。 ISP/IAP从Flash读出的数据放在此处,向Flash写的数据也需放在此处 ISP_ADDRH:ISP/IAP操作时的地址寄存器高八位。 ISP_ADDRL:ISP/IAP操作时的地址寄存器低八位。 ISP_CMD: ISP/IAP操作时的命令模式寄存器,须命令触发寄存器触发方可生效。
B7 |
B6 |
B5 |
B4 |
B3 |
B2 |
B1 |
B0 |
命令/操作模式选择 |
保留 |
命令选择 |
|
- |
- |
- |
- |
- |
0 |
0 |
0 |
待机模式,无ISP/IAP操作 |
- |
- |
- |
- |
- |
0 |
0 |
1 |
对用户的应用程序Flash区及数据Flash区字节读 |
- |
- |
- |
- |
- |
0 |
1 |
0 |
对用户的应用程序Flash区及数据Flash区字节编程 |
- |
- |
- |
- |
- |
0 |
1 |
1 |
对用户的应用程序Flash区及数据Flash区扇区擦除 |
ISP_TRIG:ISP/IAP操作时的命令触发寄存器。 当ISPEN(ISP_CONTR.7)=1时,对ISP_TRIG先写入0x46,再写入0xb9,ISP/IAP命令才会生效。 ISP_CONTR:ISP/IAP控制寄存器。
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
ISPEN |
SWBS |
SWRST |
- |
- |
WT2 |
WT1 |
WT0 |
ISPEN:ISP/IAP功能允许位。0:禁止ISP/IAP编程改变Flash,1:允许编程改变Flash SWBS:软件选择从用户主程序区启动(0),还是从ISP程序区启动(1)。 SWRST:0:不操作,1:产生软件系统复位,硬件自动清零。 ISP_CONTR中的SWBS与SWRST这两个功能位,可以实现单片机的软件启动,并启动到ISP区或用户程序区,这在“STC单片机自动下载”一节,亦有所应用。 如: ISP_CONTR=0x60; 则可以实现从用户应用程序区软件复位到ISP程序区开始运行程序。 ISP_CONTR=0x20; 则可以实现从ISP程序区软件复位到用户应用程序区开始运行程序。 用IAP向Flash中读写数据,是需要一定的读写时间的,读写数据命令发出后,要等待一段时间才可以读写成功。这个等待时间就是由WT2、WT1、WT0与晶体振荡器频率决定的。
设置等待时间 |
CPU等待时间(机器周期) |
WT2 |
WT1 |
WT0 |
读取 |
编程 |
扇区擦除 |
建议的系统时钟 |
0 |
1 |
1 |
6 |
30 |
5471 |
5MHz |
0 |
1 |
0 |
11 |
60 |
10942 |
10MHz |
0 |
0 |
1 |
22 |
120 |
21885 |
20MHz |
0 |
0 |
0 |
43 |
240 |
43769 |
40MHz |
(以上的建议时钟是(WT2、WT1、WT0)取不同的值时的标称时钟,用户系统中的时钟不要过高,否则可能使操作不稳定。) 以下是具体的实现代码:
EEPROM操作函数: #define RdCommand 0x01 #define PrgCommand 0x02 #define EraseCommand 0x03 #define Error 1 #define Ok 0 #define Waittime 0x01 #define PerSector 512
unsigned char xdata Ttotal[512];
/* --------------------------------------------------------------------- 打开 ISP,IAP 功能 --------------------------------------------------------------------- */ void ISP_IAP_enable(void) { EA=0;/* 关中断*/ ISP_CONTR|=0x18;/*0001,1000*/ ISP_CONTR|=WaitTime;/*写入硬件延时*/ ISP_CONTR|=0x80;/*ISPEN=1*/ }
/* --------------------------------------------------------------------- 关闭 ISP,IAP 功能 --------------------------------------------------------------------- */ void ISP_IAP_disable(void) { ISP_CONTR&=0x7f;/* ISPEN = 0 */ ISP_TRIG=0x00; EA=1;/* 开中断 */ }
/* ---------------------------------------------------------------------- 公用的触发代码 ---------------------------------------------------------------------- */ void ISPgoon(void) { ISP_IAP_enable();/* 打开 ISP,IAP 功能 */ ISP_TRIG=0x46;/* 触发ISP_IAP命令字节1 */ ISP_TRIG=0xb9;/* 触发ISP_IAP命令字节2 */ _nop_(); }
/* ----------------------------------------------------------------------- 字节读 ----------------------------------------------------------------------- */ unsigned char byte_read(unsigned int byte_addr) { ISP_ADDRH=(unsigned char)(byte_addr>>8); /* 地址赋值*/ ISP_ADDRL=(unsigned char)(byte_addr&0x00ff);
ISP_CMD&=0xf8; /* 清除低3位 */ ISP_CMD|=RdCommand;/* 写入读命令*/
ISPgoon();/* 触发执行*/ ISP_IAP_disable();/* 关闭ISP,IAP功能*/
return ISP_DATA;/* 返回读到的数据*/ }
/* ------------------------------------------------------------------------ 扇区擦除 ------------------------------------------------------------------------ */ void sectorerase(unsigned int sector_addr) { unsigned int iSectorAddr; iSectorAddr=(sector_addr&0xfe00);/* 取扇区地址*/ ISP_ADDRH=(unsigned char)(iSectorAddr>>8); ISP_ADDRL=0x00;
ISP_CMD&=0xf8;/* 清空低3位*/ ISP_CMD|=EraseCommand;/* 擦除命令3*/
ISPgoon();/* 触发执行 */ ISP_IAP_disable();/* 关闭ISP,IAP功能*/ }
/* ------------------------------------------------------------------------------------- 字节写 ------------------------------------------------------------------------------------- */ void byte_write(unsigned int byte_addr, unsigned char original_data) { ISP_ADDRH=(unsigned char)(byte_addr>>8); /* 取地址*/ ISP_ADDRL=(unsigned char)(byte_addr & 0x00ff);
ISP_CMD&=0xf8;/* 清低3位*/ ISP_CMD|=PrgCommand;/* 写命令2*/ ISP_DATA=original_data;/* 写入数据准备*/
ISPgoon();/* 触发执行*/ ISP_IAP_disable();/* 关闭IAP功能*/ }
/* ----------------------------------------------------------------- 字节写并校验 ----------------------------------------------------------------- */ unsigned char byte_write_verify(unsigned int byte_addr, unsigned char original_data) { ISP_ADDRH=(unsigned char)(byte_addr>>8); /* 取地址*/ ISP_ADDRL=(unsigned char)(byte_addr&0xff);
ISP_CMD&=0xf8;/* 清低3位*/ ISP_CMD|=PrgCommand;/* 写命令2*/ ISP_DATA=original_data;
ISPgoon();/* 触发执行*/
/* 开始读,没有在此重复给地址,地址不会被自动改变*/ ISP_DATA=0x00;/* 清数据传递寄存器*/
ISP_CMD&=0xf8;/* 清低3位*/ ISP_CMD|=RdCommand;/* 读命令1*/
ISP_TRIG=0x46;/* 触发ISP_IAP命令字节1 */ ISP_TRIG=0xb9;/* 触发ISP_IAP命令字节2 */ _nop_();/* 延时*/
ISP_IAP_disable();/* 关闭IAP功能*/
if(ISP_DATA==original_data)/* 读写数据校验*/ return Ok;/* 返回校验结果*/ else return Error; }
/* -------------------------------------------------------------------------- 数组写入 -------------------------------------------------------------------------- */ unsigned char arraywrite(unsigned int begin_addr, unsigned int len, unsigned char *array) { unsigned int i; unsigned int in_addr;
/* 判是否是有效范围,此函数不允许跨扇区操作 */ if(len > PerSector) return Error; in_addr = begin_addr & 0x01ff;/* 扇区内偏移量 */ if((in_addr+len)>PerSector) return Error; in_addr = begin_addr; /* 逐个写入并校对 */ ISP_IAP_enable();/* 打开IAP功能 */ for(i=0;i { /* 写一个字节 */ ISP_ADDRH=(unsigned char)(in_addr >> 8); ISP_ADDRL=(unsigned char)(in_addr & 0x00ff); ISP_DATA=array; /* 取数据 */ ISP_CMD&=0xf8;/* 清低3位 */ ISP_CMD|=PrgCommand;/* 写命令2 */
ISP_TRIG=0x46;/* 触发ISP_IAP命令字节1 */ ISP_TRIG=0xb9;/* 触发ISP_IAP命令字节2 */ _nop_();
/* 读回来 */ ISP_DATA=0x00;
ISP_CMD&=0xf8;/* 清低3位*/ ISP_CMD|=RdCommand;/* 读命令1*/
ISP_TRIG=0x46;/* 触发ISP_IAP命令字节1 */ ISP_TRIG=0xb9;/* 触发ISP_IAP命令字节2 */ _nop_();
/* 比较对错 */ if(ISP_DATA!=array) { ISP_IAP_disable(); return Error; } in_addr++;/* 指向下一个字节*/ } ISP_IAP_disable(); return Ok; }
/* ----------------------------------------------------------------------------- 扇区读出 ----------------------------------------------------------------------------- */ /* 程序对地址没有作有效性判断,请调用前事先保证他在规定范围内 */ void arrayread(unsigned int begin_addr, unsigned char len) { unsigned int iSectorAddr; unsigned int i; iSectorAddr = begin_addr; // & 0xfe00; /* 取扇区地址*/
ISP_IAP_enable(); for(i=0;i { ISP_ADDRH=(unsigned char)(iSectorAddr>>8); ISP_ADDRL=(unsigned char)(iSectorAddr & 0x00ff);
ISP_CMD&=0xf8;/* 清低3位*/ ISP_CMD|=RdCommand;/* 读命令1*/ ISP_DATA=0; ISP_TRIG=0x46;/* 触发ISP_IAP命令字节1 */ ISP_TRIG=0xb9;/* 触发ISP_IAP命令字节2 */ _nop_();
Ttotal=ISP_DATA; iSectorAddr++; } ISP_IAP_disable();/* 关闭IAP功能*/ }
主函数对EEPROM操作函数进行调用: #include #include #include #include
int i;
void delay(unsigned int time) { while(time--); }
void main() { _ADOS(22.1184); //ADOS自动下载
//for(i=0;i<100;i++) //{ //Ttotal=i; //} //arraywrite(0x8000,100,Ttotal); /* 第一次运行时向EEPROM中写入数据 然后再将写入函数注释掉,将先前写 入的数据读出,输出在P2口上。 */ arrayread(0x8000,100); for(i=0;i<100;i++) { P2=~Ttotal; delay(10000); } while(1); }
|