STC单片机内部EEPROM的应用
STC各型号单片机内置的EEPROM的容量各有不同,见下表:
本文引用地址:https://www.eepw.com.cn/article/201611/316202.htm单片机芯片型号 | 起始地址 | 内置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
unsignedchar xdata Ttotal[512];
/*
---------------------------------------------------------------------
打开 ISP,IAP功能
---------------------------------------------------------------------
*/
voidISP_IAP_enable(void)
{
EA=0;/*关中断*/
ISP_CONTR|=0x18;/*0001,1000*/
ISP_CONTR|=WaitTime;/*写入硬件延时*/
ISP_CONTR|=0x80;/*ISPEN=1*/
}
/*
---------------------------------------------------------------------
关闭 ISP,IAP功能
---------------------------------------------------------------------
*/
voidISP_IAP_disable(void)
{
ISP_CONTR&=0x7f;/* ISPEN = 0 */
ISP_TRIG=0x00;
EA=1;/*开中断 */
}
/*
----------------------------------------------------------------------
公用的触发代码
----------------------------------------------------------------------
*/
voidISPgoon(void)
{
ISP_IAP_enable();/*打开 ISP,IAP功能 */
ISP_TRIG=0x46;/*触发ISP_IAP命令字节1 */
ISP_TRIG=0xb9;/*触发ISP_IAP命令字节2 */
_nop_();
}
/*
-----------------------------------------------------------------------
字节读
-----------------------------------------------------------------------
*/
unsignedchar 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;/*返回读到的数据*/
}
/*
------------------------------------------------------------------------
扇区擦除
------------------------------------------------------------------------
*/
voidsectorerase(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功能*/
}
/*
-------------------------------------------------------------------------------------
字节写
-------------------------------------------------------------------------------------
*/
voidbyte_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功能*/
}
/*
-----------------------------------------------------------------
字节写并校验
-----------------------------------------------------------------
*/
unsignedchar 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;
}
/*
--------------------------------------------------------------------------
数组写入
--------------------------------------------------------------------------
*/
unsignedchar 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[i]; /*取数据 */ 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[i]) { ISP_IAP_disable(); return Error; } in_addr++;/*指向下一个字节*/ } ISP_IAP_disable(); return Ok; } /* ----------------------------------------------------------------------------- 扇区读出 ----------------------------------------------------------------------------- */ /*程序对地址没有作有效性判断,请调用前事先保证他在规定范围内 */ voidarrayread(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[i]=ISP_DATA; iSectorAddr++; } ISP_IAP_disable();/*关闭IAP功能*/ } 主函数对EEPROM操作函数进行调用: #include #include #include #include inti; voiddelay(unsigned int time) { while(time--); } voidmain() { _ADOS(22.1184); //ADOS自动下载 //for(i=0;i<100;i++) //{ //Ttotal[i]=i; //} //arraywrite(0x8000,100,Ttotal); /* 第一次运行时向EEPROM中写入数据 然后再将写入函数注释掉,将先前写 入的数据读出,输出在P2口上。 */ arrayread(0x8000,100); for(i=0;i<100;i++) { P2=~Ttotal[i]; delay(10000); } while(1); }
评论