C51—模拟IIC总线实现EEPROM存取数据

- 什么是IIC总线

IIC总线是同步通信的一种特殊形式,具有接线口少、控制简单、器件封装形式小、通信速率高等特点。在主从通信中,可以有多个IIC总线器件同时连接到IIC总线上,所有与IIC兼容的器件都具有标准的接口,通过地址来识别通信对象,使他们可以经由IIC总线互相直接通信。
IIC总线由SDA数据线和SCL时钟线俩条线构成通信线路,既可发送数据也可以接收数据。在CPU和IC之间、IC与IC间都可以双向传播,每个器件都有唯一的地址,这样就可以使信息进行准确的传输。CPU发出的信号分为地址码和数据码俩部分,地址码用来选址,数据码是通信的内容。
总而言之,IIC总线就是器件之间的通信线路,主器件可以通过IIC总线向从器件发送数据,也可以从从器件接收数据。51单片机不具备IIC总线接口,但我们可以通过软件模拟IIC总线的工作时序,只需要正确调用各个函数就能够方便的拓展IIC总线接口器件。

-什么是EEPROM

EEPROM就是一种存储介质,就像ROM、PROM、FLASH储存器等,下面我粗略地介绍一下EEPROM的由来
ROM:只读存储器,其中的内容在工厂中就烧录好了,内容只能读,不能改,所以用户只能读取其中数据,一旦数据出错,ROM就相当于废掉了。
PROM:可编程的ROM,相比于原来的ROM,用户可以将自己所需要的数据写入,但也只有一次机会,数据一旦写入之后就无法修改。
EPROM:可擦除可编程ROM,相比较PROM,此芯片可以重复擦除和写入,虽然它解决了PROM只能写入一次的弊端,但EPROM数据的擦除是通过紫外线的照射来完成的,所以使用此芯片时对紫外线的防护是很重要的。
EEPROM电可擦除可编程ROM,鉴于EPROM擦除数据的不容易,EEPROM应运而生,它是以电子信号来修改其内容的,而且是以byte为最小修改单位的,不需要将数据全部清除来写入,可以实现定向修改数据。而且因为其数据保存具有掉电不丢失的特性备受追捧。
在下的程序就是通过51单片机模拟IIC总线对AT24C02(EEPROM的型号)进行数据的存取和修改。
AT24C02的储存容量为256B一般通过片内子地址寻址对内部256B中任意一个进行读\写操作,其寻址范围为00~FF。

-IIC总线的通信格式

就像串口通讯和红外遥控一样,IIC总线也有一套通信方式。
硬件结构
先来认识一下IIC总线的硬件结构,SDA是数据线,SCL是时钟线,总线上个器件均采用漏极开路结构与总线相连,因此SDA和SCL都需要上拉电阻,总线在空闲状态保持高电平,一旦某一器件输出低电平,都将使总线的信号变低,所以说各器件的SDA和SCL都是线“与”关系。
在这里插入图片描述
数据位有效性
IIC中数据的传输是通过SDA和SCL进行高低电平变化来完成的,在传送数据时,只有当时钟线SCL为高电平的时候数据线SDA上的电平才有效(即SDA高电平代表1;SDA低电平代表0),所以在SCL为高电平的时候SDA上的数据一定要保持稳定才行,不然数据的传输会很不稳定的。那就是说SCL为低电平的时候SDA怎么变化都没有关系,但SCL为高电平时SDA一定要稳定,可以理解为传输信号的改变是在SCL为低电平的时候进行的(SCL高电平时SDA的改变即为传输信号的改变)。
在这里插入图片描述
启始信号、停止信号
IIC每次进行数据传输时先由主机发出启始信号,代表数据传输的开始,信号格式为:在SCL高电平时期SDA产生下降沿(注意这与数据的传输不同,SCL高电平时器SDA要变化),这样从器件就会检测到该信号并做好准备。
相对应,停止信号时在SCL高电平时期SDA产生上升沿信号。俩种信号的时序图如下在这里插入图片描述
寻址信号
主机在发出启始信号后,再发出寻址信号,这里介绍一下七位寻址方式。
寻址信号由一个字节(8byte)构成,高七位为地址位,用来确定要进行通信的从器件,最低位是方向位,用来说明是传输数据还是接收数据。每一个从器件都会对比主机发送的地址码和自己的地址,如果安排上了,再判断从器件是接收主机数据还是向主机发送数据。
从器件的地址由固定地址和可编程地址俩部分组成,固定地址用于区别类别,可编程地址用来区别同类从器件。比如AT24C02有四位固定地址和三位可编程地址,三位可编程地址可以有八种表达方式,所以最多可以有八个AT24C02器件接再IIC中。
方向位为0时,主机向从机传输数据;方向位为1时,从机向主机传输数据。
应答信号
IIC总线协议规定:每传输1字节数据(含地址及命令)后,都要有一个应答信号,以确定数据传送是否被对方收到。应答信号由接收方产生,应答信号的格式为:在SCL为高电平时SDA为低电平(被动的,我们到时候只要检测一下SDA是否被拉低即可),表示数据传输正确,产生应答。但当主机为接收方时,对最后一字节不应答。
在这里插入图片描述
(在敲代码时我们一般将应答信号的检验放在数据发送函数中)

模块化设计注解

这里只是挑重要模块进行注解,后面有完整代码。
延时10um函数

void dy()
{
	uint a;
	for(a=3;a>0;a--)
		;
}

IIC启始函数

void IICstart()//产生下降沿,启动信号
{
	SDA=1;
	dy();
	SCL=1;
	dy();
	SDA=0;
	dy();
	SCL=0;
	
}

IIC停止函数

void IICstop()//产生上升沿,停止信号
{
	SDA=0;
	dy();
	SCL=1;
	dy();
	SDA=1;
	dy();
	SCL=0;
}

IIC发送一个字节函数

uchar IICsendbyte(uchar dat)//将dat一个字节的内容发送出去,先发送最高位,并检测应答
{
	uint i;
	SCL=0
	for(i=0;i<8;i++)//8位逐次发送
	{
		dat<<=1;
		SDA=CY;//进行左移操作后最高位自动移入PSW寄存器中的CY位(可以用来zb)
		dy();
		SCL=1;
		dy();
		SCL=0;
		dy();
	}
	SDA=1;//拉高SDA电平,准备检测应答信号
	dy();
	SCL=1;
	while(SDA)
	{
		j++;
		if(j>200)//当超过2000umSDA还没被拉低,算应答失败或非应答
		{
			SDA=0;
			dy();
			return 0;//发送失败返回0
		}
	}
	SCL=0;
	dy();
	return 1;//发送成功返回1
}

IIC接收一个字节函数

uchar IICreadbyte()//读取一个字节数据,并返回
{
	uint i;
	uchar dat;
	for(i=0;i<8;i++)
	{
		SCL=1;
		dy();
		dat<<=1;//一定要先移位
		dat |=SDA;//与运算
		dy();
		SCL=0;
		dy();
	}
	return dat;
}

向AT24C02写字节

void AT24C02write(uchar addr,uchar dat)//往addr地址写入dat数据
{
	IICstart();//开始
	IICsendbyte(0Xa0);//发送寻址 0Xa0代表寻址AT24C02并且方向位为0,主机向从机传输数据
	IICsendbyte(addr);//发送储存地址
	IICsendbyte(dat);//发送储存数据
	ICCstop();//结束
}

从AT24C02读字节

uchar AT24C02read(uchar addr)//读取AT24C02中addr位置数据并返回
{
	uchar dat;
	IICstart();
	IICsendbyte(0Xa0);//伪写
	IICsendbyte(addr);
	IICstart();//开始读取
	IICsendbyte(0Xa1);//发送寻址 0Xa0代表寻址AT24C02并且方向位为1,从机向主机传输数据
	dat=IICreadbyte(addr);//读取数据
	IICstop();
	return dat;//返回数据
}

整体代码

在下通过按键k1、k2、k3、k4实现数据在AT14C02中的保存、读取、自增、清零等操作,并通过数码管显示数值(38译码器),亲自测试可以运行。

 #include"reg52.h"
 #define uint unsigned int
 #define uchar unsigned char
 uchar num,duan[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
 sbit SDA=P2^0;
 sbit SCL=P2^1;

 sbit a=P2^2;
 sbit b=P2^3;
 sbit c=P2^4;

 sbit k1=P1^3;
 sbit k2=P1^2;
 sbit k3=P1^1;
 sbit k4=P1^0;

void delay10us()   //时间>4.7um就ok
{
    unsigned char a;
    for(a=3;a>0;a--);
}

void dy(uint x)		  //按键时用的延时函数
{
	while(x--);
}

void xs()			 //数码管显示
{
	uint i;
	for(i=0;i<4;i++)
	{
		switch(i)
		{
			case 0:a=0;b=0;c=0;P0=duan[num/1000];break;	 //千位
			case 1:a=1;b=0;c=0;P0=duan[num/100];break;	   //百位
			case 2:a=0;b=1;c=0;P0=duan[num/10%10];break;	  //十位
			case 3:a=1;b=1;c=0;P0=duan[num%10];break;	  //个位
		}	
		dy(100);
		P0=0X00;
    }	 
}

void IICstart()			 //起始信号
{
	SDA=1;
	delay10us();
	SCL=1;
	delay10us();
	SDA=0;
	delay10us();
}

void IICstop()			  //停止信号
{
	SDA=0;
	delay10us();
	SCL=1;
	delay10us();
	SDA=1;
	delay10us();
}

uchar IICsendbyte(uchar dat)		//发送一个字节的数据
{
	uint i,j=0;
	SCL=0;
	for(i=0;i<8;i++)				 //从最高位开始发送
	{
		dat<<=1;
		SDA=CY;						//左移时最高位会移入PSW寄存器的CY位中
		delay10us();
		SCL=1;
		delay10us();
		SCL=0;
		delay10us();
	}
	SDA=1;
	delay10us();
	SCL=1;
	while(SDA)		 //等待应答	 即等待设备把SDA拉低
	{
		j++;
		if(j>200)		 //如果超过2000um没有应答就算发送失败或非应答  表示接收结束
		{
			SCL=0;
			delay10us();
			return 0;
		}
	}
	SCL=0;			  //不管应答或非应答,SCL都置0
	delay10us();
	return 1;
}

uchar IICreadbyte()		   //读一个字节,从最高位开始
{
	uint i,dat=0;
	SDA=1;		 //因为下面开始时SCL=1,所以SDA要稳定
	delay10us();
	for(i=0;i<8;i++)
	{
		SCL=1;
		delay10us();
		dat<<=1;
		dat |=SDA;
		delay10us();
		SCL=0;
		delay10us();
	}
	return dat;    //返回读到的数据dat(一个字节)
}


void AT24c02write(uchar addr,uchar dat)	 //往AT24c02的addr地址写入dat数据
{
	IICstart();
	IICsendbyte(0Xa0);			  //发送要与之通信的器件地址,此处为AT24c02的地址
	IICsendbyte(addr);			  //发送要写入的内存地址,AT24c02内部可储存256字节,故地址可以为0~255
	IICsendbyte(dat);			  //发送数据
	IICstop();
}

uchar AT24c02read(uchar addr)	   //读取AT24c02中addr地址的值,并返回给函数
{
	uchar dat;
	IICstart();
	IICsendbyte(0Xa0);			  //伪写
	IICsendbyte(addr);
	IICstart();					   //真正开始读数据
	IICsendbyte(0Xa1);
	dat=IICreadbyte();
	IICstop();
	return dat;
}

void key()
{
	if(k1==0)
	{
		dy(1000);
		if(k1==0)
		{
			AT24c02write(1,num);
		}
		while(!k1);	   //检测放手
	}
	if(k2==0)
	{
		dy(1000) ;
		if(k2==0)
		{
			num=AT24c02read(1);
		}
		while(!k2);	   //检测放手
	}
	if(k3==0)
	{
		dy(1000);
		if(k3==0)
		{
			num++;
			if(num==256)
			num=0;
		}
		while(!k3);	   //检测放手
	}
	if(k4==0)
	{
		dy(1000);
		if(k4==0)
		{
			num=0;
		}
		while(!k4);	   //检测放手
	}
}

void main()
{
	while(1)
	{
		key();
		xs();
	}
}
posted @ 2019-04-23 23:18  Aspirant-GQ  阅读(352)  评论(0)    收藏  举报