AT24C04A芯片基础知识
当51单片机应用系统需要存放一些在掉电后需要保存的数据时,可以使用E2PROM。AT24C04是很常用的E2PROM芯片。
AT24C04A是Atmel公司出品的I2C总线接口E2PROM,有8KB的内部存储空间,采取8字节/页、256页、2个块的分页方式。
AT24C04A的电路简图如上所示,主要有A1、A2、WP、SDA、SCL五个引脚。
SCK:I2C总线的时钟引脚;
SDA:I2C总线的数据引脚;
A1、A2:地址引脚,用于决定AT24C04A芯片的I2C地址;
WP:写保护引脚。当该地址连接到GND时,芯片可以进行正常的读/写操作;当该引脚连接到VCC时,不同的芯片有不同的应用方式。
AT24C04A有自己独立的I2C总线地址,其地址结构为“1010+A2、A1+内部页选择位+读写选择位”。当A2、A1均为0时,对AT24C02A的内部页面1进行读操作的地址是0xA1,写操作地址是0xA0。
AT24C04A的操作分为写操作和读操作,写操作包括字节写和页面写两种工作方式;而读操作则分为指定位置读、连续读和当前地址读三种工作方式。
I2C总线基本知识
I2C的基本结构与主要特点
在单片机系统中,带有I2C总线接口的电路现在呗使用得越来越多,主要因为采用I2C总线接口的器件连接线和引脚数目少,成本低。且与单片机连接简单,结构紧凑,在总线上增加器件不影响系统正常工作,系统修改和可扩展性好,即使工作时钟不同的器件,也可以直接连接到总线上。
I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线,即可在连接与总线上的器件之间传递信息。
I2C总线的特点:
总线只有两根线,即串行时钟线(SCL)和串行数据线(SDA),这在设计中大大减少了硬件接口。
每个连接到总线上的器件都有一个用于识别的器件地址,器件地址由芯片内部硬件电路和外部地址引脚同时决定,避免了片选线的连接方法,并建立了简单的主从关系,每个器件既可以作为发送器件,又可以作为接收器。
同步时钟允许器件以不同的波特率进行通信。
同步时钟可以作为停止或重新启动串行口发送的握手信号。
串行的数据传输位速率在标准模式下可达100kb/s,快速模式下可达400kb/s,高速模式下可达3.4Mb/s。
连接到同一总线的集成电路数只受400pF的最大总线电容的限制。
I2C总线是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据。各种采用I2C总线标准的器件均并联在总线上,每个器件内部都有I2C接口电路,用于实现I2C总线的连接。
每个器件都有唯一的地址,器件两两之间都可以进行信息传送。当某个器件向总线上发送信息时,它就是发送器(也叫主器件),而当其从总线上接收信息时,它又成为接收器(也叫从器件)。在信息传输过程中,主器件发送的信号分为器件地址码、器件单元地址和数据3部分,其中器件地址码用来选择从器件,确定操作的类型(类型为发送信息或者接受信息);器件单元地址用于选择器件内部的单元;数据是在各器件间传递的信息。处理过程就像打电话一样,只有拨通号码才能进行信息交流。各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。
I2C信息传送与读写过程
当 I2C总线没有进行信息传送时,数据线(SDA)和时钟线(SCL)都为高电平。当主器件向某个器件传送信息时,首先应向总线传递开始信号,然后才能传送信息,当信息传送结束时,应传送结束信号,开始信号和结束信号规定如下:
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据;
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
开始信号和结束信号之间传递的是信息,信息的字节数没有限制,但每个字节必须为8位,高位在前,低位在后。
数据线SDA上每一位信息状态的改变只能发生在时钟线SCL为低电平期间,因为SCL为高电平期间SDA状态的改变已经被用来表示开始信号和结束信号。
主器件每次传送的信息的第一个字节必须是器件地址码,第二个字节为器件单元地址,用于实现选择所操作的器件的内部单元,从第三个字节开始,为传送的数据。
高4位为器件类型识别码(不同的芯片类型有不同的定义,EEPROM一般应为1010);接着三位为片选,同种类型器件最多可接8个,高7位用于选择对应的器件;最后一位为读写位,当为1时进行读操作,表示主器件从高总线上读取信息,为0时进行写操作,表示主器件将传送信息到总线上。
I2 ^{2}
2
C总线数据传送时,每传送一个字节数据后,都必须有应答信号。与应答信号相对应的时钟由主器件产生,发送器必须在这一时钟位上释放数据线,使其处于高电平状态,以便接收器在这一位上送出应答信号。从器件在接收到起始信号后,每接收一个地址编码或数据后都会会送一个低电平应答信号,用以表示已收到。主器件收到应答信号后,可根据实际情况做出是否继续传递信号的判断。若未收到低电平应答信号,则判断为从器件出现故障。
当主器件从从器件读取数据时,从器件发送数据,主器件接收数据,主器件接收到数据后,也发送一个低电平应答信号,从器件接收后,继续发送下一个数据,如果主器件没有发送低电平应答信号,而是高电平非应答信号,则从器件结束数据传送,且等待主器件的结束信号,然后结束读操作。
I2 ^{2}
2
C的读写操作
从当前地址读
从所选器件的当前地址读,读的字节数不指定,格式如下:
从指定单元读
从指定单元写
从所选器件的指定地址写,写的字节数不指定,格式如下:
在连续读工作方式的操作过程中,当地址计数器的值超过了器件的最大地址之后会自动溢出,从而覆盖最低地址数据。
实例应用
使用串口控制向AT24C04A中写入并且读出数据。单片机通过串口从PC接收5字节数据,然后将所接收数据写入AT24C04A的指定单元,再将对应单元的数据读出后通过串口发送出去。
#include <AT89X52.h> #include <intrins.h> #define R24C04ADD 0xA1 #define W24C04ADD 0xA0 sbit SDA = P1 ^ 7; //数据线 sbit SCL = P1 ^ 6; //时钟线 sbit LED = P2 ^ 0; //指示灯 bit bAck; //应答标志 当bbAck=1是为正确的应答 unsigned char wBuff[5]; //待写入字节缓冲区 unsigned char rBuff[5]; //读出的字节缓冲区 unsigned char rxCounter = 0; //接收计数器 unsigned char wrCounter = 0; //读写计数器 bit rxFlg = 0; //接收缓冲标志位 void StartI2C(); //启动函数 void StopI2C(); //结束函数 void AckI2C(); //应答函数 void SendByte(unsigned char c); //字节发送函数 unsigned char RevByte(); //接收一个字节数据函数 unsigned char WIICByte(unsigned char WChipAdd,unsigned char InterAdd,unsigned char WIICData); //WChipAdd:写器件地址;InterAdd:内部地址;WIICData:待写数据;如写正确则返回0xff, //否则返回对应错误步骤序号 unsigned char RIICByte(unsigned char WChipAdd,unsigned char RChipAdd,unsigned char InterDataAdd); //WChipAdd:写器件地址;RChipAdd:读器件地址;InterAdd:内部地址;如写正确则返回数据, //否则返回对应错误步骤序号 //向指定器件的内部指定地址发送一个指定字节 unsigned char WIICByte(unsigned char WChipAdd,unsigned char InterAdd,unsigned char WIICData) { StartI2C(); //启动总线 SendByte(WChipAdd); //发送器件地址以及命令 if (bAck==1) //收到应答 { SendByte(InterAdd); //发送内部子地址 if (bAck ==1) { SendByte(WIICData); //发送数据 if(bAck == 1) { StopI2C(); //停止总线 return(0xff); } else { return(0x03); } } else { return(0x02); } } return(0x01); } //读取指定器件的内部指定地址一个字节数据 unsigned char RIICByte(unsigned char WChipAdd,unsigned char RChipAdd,unsigned char InterDataAdd) { unsigned char TempData; TempData = 0; StartI2C(); //启动 SendByte(WChipAdd); //发送器件地址以及读命令 if (bAck==1) //收到应答 { SendByte(InterDataAdd); //发送内部子地址 if (bAck ==1) { StartI2C(); SendByte(RChipAdd); if(bAck == 1) { TempData = RevByte(); //接收数据 StopI2C(); //停止I2C总线 return(TempData); //返回数据 } else { return(0x03); } } else { return(0x02); } } else { return(0x01); } } //启动I2C总线,即发送起始条件 void StartI2C() { SDA = 1; //发送起始条件数据信号 _nop_(); SCL = 1; _nop_(); //起始建立时间大于4.7us _nop_(); _nop_(); _nop_(); _nop_(); SDA = 0; //发送起始信号 _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); SCL = 0; //时钟操作 _nop_(); _nop_(); } //结束I2C总线,即发送I2C结束条件 void StopI2C() { SDA = 0; //发送结束条件的数据信号 _nop_(); //发送结束条件的时钟信号 SCL = 1; //结束条件建立时间大于4us _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); SDA = 1; //发送I2C总线结束命令 _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); } //发送一个字节的数据 void SendByte(unsigned char c) { unsigned char BitCnt; for(BitCnt = 0;BitCnt < 8;BitCnt++) //一个字节 { if((c << BitCnt)& 0x80) SDA = 1; //判断发送位 else SDA = 0; _nop_(); SCL = 1; //时钟线为高,通知从机开始接收数据 _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); SCL = 0; } _nop_(); _nop_(); SDA = 1; //释放数据线,准备接受应答位 _nop_(); _nop_(); SCL = 1; _nop_(); _nop_(); _nop_(); if(SDA == 1) bAck =0; else bAck = 1; //判断是否收到应答信号 SCL = 0; _nop_(); _nop_(); } //接收一个字节的数据 unsigned char RevByte() { unsigned char retc; unsigned char BitCnt; retc = 0; SDA = 1; for(BitCnt=0;BitCnt<8;BitCnt++) { _nop_(); SCL = 0; //置时钟线为低,准备接收 _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); SCL = 1; //置时钟线为高使得数据有效 _nop_(); _nop_(); retc = retc << 1; //左移补零 if (SDA == 1) retc = retc + 1; //当数据为1则收到的数据+1 _nop_(); _nop_(); } SCL = 0; _nop_(); _nop_(); return(retc); //返回收到的数据 } void InitUART(void) { TMOD = 0x20; //9600bps SCON = 0x50; TH1 = 0xFD; TL1 = TH1; PCON = 0x00; EA = 1; ES = 1; TR1 = 1; } void Send(unsigned char x) { SBUF = x; while(TI == 0); TI = 0; } void Serial(void) interrupt 4 using 1 { if(RI == 1) //接收数据 { RI = 0; wBuff[rxCounter] = SBUF; rxCounter++; //计数器++ if(rxCounter>4) //到了第16个字节 { rxCounter = 0; //清除 rxFlg = 1; //接收缓冲标志位置位 } } } main() { unsigned char temp; InitUART(); //初始化串口 while(1) { while(rxFlg == 0); //等待接收标志位被置位 rxFlg = 0; //清除接收标志位 for(wrCounter=0;wrCounter<5;wrCounter++) { rBuff[wrCounter] = RIICByte(W24C04ADD,R24C04ADD,(0x01+wrCounter)); //连续读取一字节数据,共16字节 Send(rBuff[wrCounter]); //将数据通过串口发送 } for(wrCounter=0;wrCounter<5;wrCounter++) { temp = WIICByte(W24C04ADD,(0x01+wrCounter),wBuff[wrCounter]); // Send(temp); //连续写入16字节数据 } } }