个人博客:http://brainware360.cn/
EEPROM可以随机访问和修改其中的任何一个字节,可以往每个bit中写入0或者1,掉电后数据不丢失,可以保存100年,可以擦写100w次。具有较高的可靠性,但是电路复杂/成本也高。因此目前的EEPROM都是几十千字节到几百千字节的,绝少有超过512K的,常用来保存用户数据,运行过程中可以改变。
下面在MSP430平台下以AT24C02为例说明该类可擦除ROM的使用。AT24C02的存储容量为2K bits,内容分成32页,每页8 bytes,共256 bytes。
AT24C02对外以I2C总线形式传输数据,外观如下图所示:
A0、A1和A2三个引脚作寻址用;SDA和SCL即I2C总线的数据与时钟引脚;WP作写保护用,即WP在保持高电平时,MCU对AT24C02 的读写操作无效;VCC和GND分别接电源和接地。
MCU在挂载多片AT24C02时,其器件地址由8位构成,除A0、A1和A2三位外,其他位都被锁定为如下所示:
最低位为R/W,进行读操作时,该位 = 1,写操作时,该位 = 0。地址写入正确时,AT24C02将会应答以”0”。
AT24C02的读写遵循I2C总线的相关规范,所以有传输的开始条件和停止条件。每一次正常的读写均以开始条件开始,停止条件结束,这两者的时序如下图所示:
其软件模拟的实现为:
void start(void)
{
SCL_H;
SDA_H;
_NOP();
SDA_L;
_NOP();
SCL_L;
_NOP();
}
void stop(void)
{
SDA_L;
_NOP();
SCL_H;
_NOP();
SDA_H;
_NOP();
}
SCL_H、SDA_H、SDA_L和SCL_L均为宏定义,分别表示MCU向AT24C02相应引脚输出高电平和低电平。
AT24C02的写操作分两种模式,分别为字节写入和页写入两种。
1、字节写入
该写操作需要以MCU写入开始条件开始,继以器件地址,收到AT24C02的应答后继续写入要进行写操作的字地址(即要将数据写入该AT24C02的什么位置),收到应答后才正式写入一个字节的数据,收到应答后写入停止条件,一个完整的字节写入才算完成。
其软件模拟的实现为:
uchar Write_1Byte(uchar wdata,uchar dataaddress)
{
start();
write1byte(deviceaddress);
if(check())
write1byte(dataaddress);
else
return 0;
if(check())
write1byte(wdata);
else
return 0;
if(check()) stop();
else return 0;
delay_10ms(); //等待EEPROM完成内部写入
return 1;
}
delay_10ms()是因为AT24C02的每两次写操作之间存在一个写入时间周期tWR,其最大值为5 ms。check()检查AT24C02的应答操作,收到AT24C02回复的0,则应答正确,其软件模拟实现为:
uchar check(void)
{
uchar slaveack;
SDA_H;
_NOP(); _NOP();
SCL_H;
_NOP(); _NOP();
SDA_in;
_NOP(); _NOP();
slaveack = SDA_val; //读入SDA数值
SCL_L;
_NOP();
SDA_out;
if(slaveack) return FALSE;
else return TRUE;
}
SDA_in和SDA_out分别表示MCU(MSP430)连接AT24C02的I/O引脚方向改为输入或输出,此处SDA_in的作用是为MCU接收AT24C02的应答信号做准备,接收完成后改回SDA_out。SDA_val = P2IN&BIT5,P2.5连接AT24C02的SDA引脚,接收应答信号。
write1byte()向I2C总线写一个字节的数据,其软件模拟实现为:
void write1byte(uchar wdata)
{
uchar i;
for(i = 8;i > 0;i--)
{
if(wdata & 0x80) write1();
else write0();
wdata <<= 1;
}
SDA_H;
_NOP();
}
write1()向I2C总线写一位“1”的数据,其软件模拟实现为:
void write1(void)
{
SDA_H;
_NOP();
SCL_H;
_NOP();
SCL_L;
_NOP();
}
write0()向I2C总线写一位“0”的数据,其软件模拟实现为:
void write0(void)
{
SDA_L;
_NOP();
SCL_H;
_NOP();
SCL_L;
_NOP();
}
2、页写入
AT24C02可以实现8位的页写入操作。页写入的形式大体和字节写入类似,只是在MCU写入第一个字节的数据后将会继续写入第二个数据,而不会像字节写入一样写入停止条件。如果写入AT24C02的数据超过8位,超过的数据将会覆盖已被写入的部分。
其软件模拟的实现为:
uchar Write_NByte(uchar * outbuf,uchar n,uint dataaddress)
{
uchar flag,dataaddressl,dataaddressh;
dataaddressl = dataaddress;
dataaddressh = dataaddress>>8;
start();
write1byte(deviceaddress); //写入器件地址
if(check() == 1)
write1byte(dataaddressh); //写入数据字地址
else
return 0;
if(check())
write1byte(dataaddressl);
else
return 0;
if(check())
flag=writeNbyte(outbuf,n);
else
return 0;
delay_10ms(); //等待EEPROM完成内部写入
if(flag)
return 1;
else
return 0;
}
writeNbyte()向I2C总线写N个字节(对于AT24C02,N = 8)的数据,其软件模拟实现为:
uchar writeNbyte(uchar * outbuffer,uchar n)
{
uchar i;
for(i = 0;i < n;i++)
{
write1byte(* outbuffer);
if(check())
{
outbuffer++;
}
else
{
stop();
return FALSE;
}
}
stop();
return TRUE;
}
AT24C02的读操作分三种模式,分别为当前地址读取、随机读取和顺序读取。
1、当前地址读取
上一次读写操作完成后,数据地址计数器加1,并停留在当前位置。这一位置只要不掉电就会一直有效。当R/W = 1时,就可以将当前地址位置的数据读出。
其软件模拟的实现为:
uchar Read_1Byte_currentaddress(void)
{
uchar temp;
start();
write1byte((deviceaddress|0x01));
if(check())
temp = read1byte();
else
return 0;
mnack();
stop();
return temp;
}
uchar Read_NByte_currentaddress(uchar * readbuf,uchar n)
{
start();
write1byte((deviceaddress|0x01));
if(check())
readNbyte(readbuf,n);
else
return 0;
return 1;
}
read1byte()从I2C总线读取一个字节,其软件模拟的实现为:
uchar read1byte(void)
{
uchar rdata = 0x00,i;
uchar flag;
for(i = 0;i < 8;i++)
{
SDA_H;
_NOP();
SCL_H;
SDA_in;
_NOP();
flag = SDA_val;
rdata <<= 1;
if(flag) rdata |= 0x01;
SDA_out;
SCL_L;
_NOP();
}
return rdata;
}
readNbyte()从I2C总线读取一个字节,其软件模拟的实现为:
void readNbyte(uchar * inbuffer,uchar n)
{
uchar i;
for(i = 0;i < n;i++)
{
inbuffer[i] = read1byte();
if(i < (n-1)) mack();
else mnack();
}
stop();
}
mack()完成I2C的MCU应答操作,其软件模拟的实现为:
void mack(void)
{
SDA_L;
_NOP(); _NOP();
SCL_H;
_NOP();
SCL_L;
_NOP();_NOP();
SDA_H;
_NOP();
}
mnack()完成I2C的MCU无应答操作,其软件模拟的实现为:
void mnack(void)
{
SDA_H;
_NOP(); _NOP();
SCL_H;
_NOP();
SCL_L;
_NOP(); _NOP();
SDA_L;
_NOP();
}
2、随机读取
从AT24C02的指定地址读取1个字节的数据。随机读取的操作先发送一个写操作来骗过AT24C02器件,使其内部的数据地址值修改,但是发送完毕数据地址后并不发送数据,而是发送一个开始信号,此时AT24C02中的数据地址值已经被修改了,然后通过“当前地址读取”去读取此地址上的数据。如下图所示:
其软件模拟的实现为:
uchar Read_1Byte_Randomaddress(unsigned int dataaddress)
{
uchar temp,dataaddressh,dataaddressl;
dataaddressl = dataaddress;
dataaddressh = dataaddress>>8;
start();
write1byte(deviceaddress);
if(check())
write1byte(dataaddressh);
else
return 0;
if(check())
write1byte(dataaddressl);
else
return 0;
if(check())
{
start();
write1byte((deviceaddress|0x01));
}
else
return 0;
if(check())
temp = read1byte();
else
return 0;
mnack();
stop();
return temp;
}
uchar Read_NByte_Randomaddress(uchar * readbuf,uchar n,uchar dataaddress)
{
start();
write1byte(deviceaddress);
if(check())
write1byte(dataaddress);
else
return 0;
if(check())
{
start();
write1byte(deviceaddress|0x01);
}
else
return 0;
if(check())
readNbyte(readbuf,n);
else
return 0;
return 1;
}
read1byte()从I2C总线读取一个字节,其软件模拟的实现为:
uchar read1byte(void)
{
uchar rdata = 0x00,i;
uchar flag;
for(i = 0;i < 8;i++)
{
SDA_H;
_NOP();
SCL_H;
SDA_in;
_NOP();
flag = SDA_val;
rdata <<= 1;
if(flag) rdata |= 0x01;
SDA_out;
SCL_L;
_NOP();
}
return rdata;
}
readNbyte()从I2C总线读取N个字节,其软件模拟的实现为:
void readNbyte(uchar * inbuffer,uchar n)
{
uchar i;
for(i = 0;i < n;i++)
{
inbuffer[i] = read1byte();
if(i < (n-1)) mack();
else mnack();
}
stop();
}
2、顺序读取
顺序读取的中的第一个字节既可以以“当前位置读取”实现,也可以以“随机读取”实现,只要读取完毕后MCU给AT24C02回复应答信号而不发送停止条件,读取操作一直持续下去。当然,每一次读取都会使数据地址计数器加1。当读到计数器的边界时,数据地址值就会翻转到最开始的位置继续读取下去,直到AT24C02收到MCU发送的停止条件。