单片机运转时的数据都存在于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
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);
}