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都是线“与”关系。
C51—模拟IIC总线实现EEPROM存取数据
数据位有效性
IIC中数据的传输是通过SDA和SCL进行高低电平变化来完成的,在传送数据时,只有当时钟线SCL为高电平的时候数据线SDA上的电平才有效(即SDA高电平代表1;SDA低电平代表0),所以在SCL为高电平的时候SDA上的数据一定要保持稳定才行,不然数据的传输会很不稳定的。那就是说SCL为低电平的时候SDA怎么变化都没有关系,但SCL为高电平时SDA一定要稳定,可以理解为传输信号的改变是在SCL为低电平的时候进行的(SCL高电平时SDA的改变即为传输信号的改变)。
C51—模拟IIC总线实现EEPROM存取数据
启始信号、停止信号
IIC每次进行数据传输时先由主机发出启始信号,代表数据传输的开始,信号格式为:在SCL高电平时期SDA产生下降沿(注意这与数据的传输不同,SCL高电平时器SDA要变化),这样从器件就会检测到该信号并做好准备。
相对应,停止信号时在SCL高电平时期SDA产生上升沿信号。俩种信号的时序图如下C51—模拟IIC总线实现EEPROM存取数据
寻址信号
主机在发出启始信号后,再发出寻址信号,这里介绍一下七位寻址方式。
寻址信号由一个字节(8byte)构成,高七位为地址位,用来确定要进行通信的从器件,最低位是方向位,用来说明是传输数据还是接收数据。每一个从器件都会对比主机发送的地址码和自己的地址,如果安排上了,再判断从器件是接收主机数据还是向主机发送数据。
从器件的地址由固定地址和可编程地址俩部分组成,固定地址用于区别类别,可编程地址用来区别同类从器件。比如AT24C02有四位固定地址和三位可编程地址,三位可编程地址可以有八种表达方式,所以最多可以有八个AT24C02器件接再IIC中。
方向位为0时,主机向从机传输数据;方向位为1时,从机向主机传输数据。
应答信号
IIC总线协议规定:每传输1字节数据(含地址及命令)后,都要有一个应答信号,以确定数据传送是否被对方收到。应答信号由接收方产生,应答信号的格式为:在SCL为高电平时SDA为低电平(被动的,我们到时候只要检测一下SDA是否被拉低即可),表示数据传输正确,产生应答。但当主机为接收方时,对最后一字节不应答。
C51—模拟IIC总线实现EEPROM存取数据
(在敲代码时我们一般将应答信号的检验放在数据发送函数中)

模块化设计注解

这里只是挑重要模块进行注解,后面有完整代码。
延时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();
}
}
上一篇:linux apache 自动监护脚本


下一篇:软件模拟IIC实现EEPROM