MCS-51单片机总结——No4.存储之AT24C04A

cisco03251单片机 2021-09-22 09:30:31 3382阅读 举报

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字节数据
     }
   }
}






版权声明:
作者:cisco032
链接:https://www.dianziwang.net/p/1d8ff3507c6a3a.html
来源:51单片机
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以点击 “举报”


登录 后发表评论
0条评论
还没有人评论过~