第8章 I2C总线AT24C02芯片应用
8.1I2C总线概述
-
I2C总线介绍
I2C总线是近年来微电子通信控制领域广泛采用的一种新型总线标准,。主从通信个主从通信I2C同时接收总线设备I2C总线,一切和I2C兼容设备有标准接口,通过地址识别通信对象,使其能够通过I2C总线相互直接通信。
。在CPU与被控IC(被控元件)之间,IC与IC之间都可以进行双向传送,各种被控器件均并联在总线上,但。在信息传输过程中,I2C根据其要完成的功能,总线上并联的每个装置,计时被控器(或主控器),也是发送器(或接收器)。,这样各IC虽然控制电路挂在同一条总线上,但它们是独立的。
-
I2C总线硬件结构
SCL是时钟线,SDA是数据线。
总线上的所有器件都使用漏极**开路结构(漏极开路(Open Drain)也就是说,高电阻状态适用于输入/输出。它可以独立输入/输出低电平和高电阻状态。如果需要高电平,则需要使用外部上拉电阻或使用LCX245等电平转换芯片。**因此,结构与总线相连,SCL和SDA均需连接上拉电阻。
I2C总线,一般以工作方式为主。**系统中只有一个主器件(单片机),其他器件都有I2C设备从总线外围。**在主工作模式下,发送主器件启动数据(发送启动信号),产生时钟信号,发出停止信号
-
I2C总线通信格式
下图为I2C数据传输的通信格式在总线上进行。
-
规定数据位的有效性
I2C当总线传输数据时,时钟信号为高电平时,数据线上的数据必须保持稳定,
-
总线空闲
I2C总线的SDA和SCL当两条线处于高电平时,规定为,也称为。同理,SDA和SCL哪条线处于高电平了,哪条线也就是被释放了。
-
发送启动信号
再利用I2C当总线进行数据传输时,首先是,启动I2C总线。在SCL高电平期,SDA下降边是启动信号。I2C从设备中检测到总线接口的信号
-
发送寻址信号
主机发送启动信号后,发出搜索信号。设备地址有7位和10位,这里有7位。搜索字节的定义如下。
搜索信号由一个字节组成,高7位为地址位,最低位为方向位,以显示主机和从设备的数据传输方向。
当主机发送地址时,总线上的每个从机器都将这7个地址代码与自己的地址进行比较。如果是一样的,他们认为自己正在被主机搜索,并根据方向位置(R/W将自己确定为发送器或接收器。
,可编程部分决定了可访问总线最大数量。如果一个从机的7位搜索位有4位是固定位,3位是可编程位,那么只能搜索8位可以有8个相同的设备连接到该设备I2C在总线系统中。
-
应答信号
I2C根据总线协议,每次传输字节数据(包括地址和命令字)(8个时钟周期)后,接收器反馈响应信号(第9个时钟周期),确定数据传输是否对方接收,**接收设备产生响应信号SCL当信号为高电平时,接收设备将被接收SDA拉低电平,即响应信号为低电平,表示数据传输正确,产生响应。响应信号为高电平时,规定为非响应信号,一般表示接收器未成功接收字节。**当主机作为接收设备时,主机对最后一字节不应答, 向发送设备表示数据传输结束。
无论上述情况如何,数据传输都将终止。此时,主机要么产生停止信号释放总线,要么产生重启信号并开始新通信。
-
数据传输
主机发送搜索信号,从设备响应中获得数据传输,每次1,每个传输时间对应一个时钟周期,即时钟脉冲发送一个位置,然后发送一个字节8个位置,即8个时钟周期,然后在第9个时钟周期释放数据线SDA,接收器反馈响应信号,但每次传输都应在接收响应信号后传输下一个字节。
-
发送停止信号
所有数据传输后,主机发送停止信号SCL高电平期,SDA上边缘信号产生,停止时序图如下。
8.2单片机模拟I2C总线通信
市场上很多单片机都是由I2C总线接口,工作时总线状态有硬件检测控制,无需人工干预。但是我们的51单片机没有I2C但我们可以通过软件模拟总线接口I2C控制总线的工作时间I2C总线接口设备。
为保证数据传输的可靠性,标准I2C总线的数据传输有严格的时序要求。单片机在模拟I2C在通信总线时,应编写以下关键部分:总线初始化、启动信号、响应信号、停止信号、写1字节、读1字节。
-
总线初始化
void init() { SCL=1; delay(); SDA=1;///拉高总线释放总线 delay(); }
-
启动信号
void start() { SDA=1; delay(); SCL=1; delay(); SDA=0; delay(); }
-
应答信号
void respons() { unsigned char i=0; SCL=1; delay(); while((SDA==1)&&(i<256)) i++; SCL=0; delay(); }
这里我们要注意一点,我们接收应答信号,是从器件将SDA拉低,所以我们这里是要接受到SDA拉低的信息。如果少了这个while函数,那么主机收不到应答信号时,就会一直停留在这里,我们让他一段时间接收不到应答信号后就自动退出。
-
停止信号
void stop() { SDA=0; delay(); SCL=1; delay(); SDA=1; delay(); }
-
写字节
-
主机在检测到总线为“空闲状态时”(即SDA、SCL均为高电平)时,发送一个启动信号,开始通信
-
主机接着发送一个寻址信号。该字节由7位外围器件地址和1位读写控制位R/W组成(此时R/W=0,表示主机对从机进行写操作)
-
相对应的从机收到寻址信号后向主机回馈应答信号 ACK(ACK=0),寻址信号也是1字节,所以也要反馈
-
主机收到从机的应答信号后开始发送第一个字节的数据
-
从机收到数据后返回一个应答信号 ACK
-
主机收到应答信号后再发送下一个数据字节
-
当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信
void write_byte() { unsigned char 1,temp; temp=date; for(i=0;i<8;i++) { temp=temp<<1; SCL=0;//这里发送数据的时候,SCL得是低电平才行,因为SDA送数据是送0,1的,只有SCL=0,SDA的电平才能改变;其实看前面应答信号下的图,你会发现,在刚刚送进去一个位时,SCL永远是低电平,然后在送的中途,它又是高电平,然后下一个字节开始了,又变成低电平。这里置零准确来说是为了第一位数据的传送,后面的它自己会变的。 delay(); SDA=CY;//这里你就理解成,temp那8位,从最高位开始一个个进入CY,然后就送到SDA上发出去 delay(); SCL=1; delay(); } SCL=0; delay(); SDA=1; delay();//这个SCL=0;SDA=1目的是释放数据线,使SDA处于空闲状态,为下次数据线的传输数据做准备。 }
串行发送1字节时,需要把其中的8位一位位地发出去,“
temp=temp<<1
”表示将temp左移一位,最高位将移入PSW寄存器的CY为中,然后将CY赋给SDA进而在SCL的控制下发送出去。PSW寄存器
PSW(程序状态标志寄存器)是一个8位寄存器,位于单片机的特殊功能寄存区,字节地址为D0H,用来存放运算结果的一些特征,如有无仅为,借位等。使用汇编语言是,PSW寄存器很有用,但在利用c语言编程时,编译器会自动控制该寄存器,很少有人人为操作它。
- CY——进位标志位,表示运算是否有进位(或借位)。
- 其他的先不讲了吧,暂时用不上。
移位操作
-
左移
C51中的操作符号是“<<”,每执行一次左移指令,被操作的数()将最高位移入单片机PSW寄存器的CY位,CY位中原来的数丢弃,最低位补0,其他位依次向左移动一位。
CY 最高位 最低位 X(可1可0) 0 1 1 0 1 0 1 1 左移后变成
CY 最高位 最低位 0 1 1 0 1 0 1 1 0 -
右移
这个和左移其实是同理的,只不过变成右移,然后将最低位放入CY中,然后最高位补0。
-
循环右移和循环左移
这两个不同于上面两个操作,是真的循环,最高位去到最低位或者最低位去到最高位,就是一个循环的样子。循环右移在C51库中有现成函数 _ crol_,循环左移的话就是 _cror _。直接使用就好了。
使用起来是
x=_crol _(x,n);
,这个表示的是将x个8位数据左移n位。 -
例子
用循环右移库函数来实现流水灯
#include <reg52.h> #include <intrins.h>//包含_crol_函数所在的头文件 #define uint unsigned int #define uchar unsigned char void delayms(uint); uchar aa; void main() { aa=0xfe; while(1) { P1=aa; delayms(500); aa=_crol_(aa,1); } } void delayms(uint xms) { uint i,j; for(i=xms;i>0;i--) for(j=xms;j>0;j--); }
-
-
读1字节
-
主机发送启动信号后,接着发送命令字节(其中 R/W=1)
-
对应的从机收到地址字节后,返回一个应答信号并向主机发送数据
-
主机收到数据后向从机反馈一个应答信号
-
从机收到应答信号后再向主机发送下一个数据
-
当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ACK=1 的非应答信号后便停止发送
-
主机发送非应答信号后,再发送一个停止信号,释放总线结束通信
uchar read_byte() { unsigned char i,k;//这里定义变量,其实就是定义了单片机中的一个缓冲寄存器,在RAM中的寄存器,默认8位都是0.那么下面的解释的通了 SCL=0; delay(); SDA=1;//这里释放数据线,让他之后的电平由信号决定 for(i=0;i<8;i--) { SCL=1;//这里将SCL拉高,上升沿相当于驱动力或者说是协议,唤醒对方处理,就是告诉被读器件要开始读了 delay(); k=(k<<1)|SDA;//由于k的8位全都是0,通过或运算就可以全部接收了 SCL=0;//这里告诉它我读完了 delay(); } delay(); return k; }
串行接收1字节时需要将8位一位一位地接收,再组合成1字节。上面定义一个临时变量k,将k左移一位后于SDA进行或运算,一次吧8个独立的位放入以字节中来完成接收。
-
8.3E2PROM AT24C02与单片机的通信实例
E2PROM:电可擦可编程只读存储器
E2PROM拥有I2总线接口,ATMEL公司生产的AR24C系列E2PROM,主要型号有AT24C01/02/04/08/16,02对应的储存容量是256X8。采用这类芯片可以解决掉电数据保存问题,可对所存数据保存100年,并可多次擦写,擦写次数可达10W次以上。
1.AT24C02引脚配置与引脚功能
AT24C02芯片的常用封装形式有直插式和贴片式两种。如下图所示
下图是它的引脚图
各引脚功能如下:
- 1、2、3引脚(A0、A1、A2)——可编程地址输入端
- 4(GND)——电源接地端
- 5(SDA)——串行数据输入/输出端
- 6(SCL)——串行时钟输入端
- 7(WP)——写保护输入端,用于硬件数据保护。其为低电平时,可以对整个存储器进行正常的读/写操作;为高电平时,存储器具有写保护功能,但读操作不受影响。
- 8(VCC)——电源正端
2.存储结构与寻址
AT24C02的存储容量为2Kb,内部分成32页,每页8B,共256B(这里注意一下,存储容量为2Kb等于2048位,256B表示256个字节,小写b表示bit,位;大写B表示byte,字节),。
-
芯片寻址
AT24C02的芯片地址为1010,期地址控制字格式为1010A2A1A0R/W,。R/W为芯片读写控制位,该位为0,表示对芯片进行写操作;该位为1,表示对芯片进行读操作。
-
片内子地址寻址
。
3.读/写操作时序
串行E2PROM一般有两种写入方式:字节写入方式、页写入方式。
页写入方式允许在一个写周期(10ms)左右对1字节到1页的若干字节进行编程写入,AT24C02的页面大小为8B。采用页写入方式可提高写入效率,但容易发生事故。AT24C系列芯片内地址在接收到每个数据字节后自动加1,故装载一页以内数据字节时,只用输入首地址。如果写到此页的最后1字节,主器件继续发送数据,数据将重新从该页的首地址写入,进而造成原来的数据丢失,这就是页地址空间的“上卷”现象。()
解决“上卷”的方式:在第8个数据后将地址强制加1,或是将下一页的首地址重新赋给寄存器。
-
字节写入方式
单片机在一次数据帧中只访问E2PROM一个单元。该方式下,单片机,然后(1010A2A1A0R/W这个,声明控制这个芯片),(00~FF)。上述2个字节都得到E2PROM响应后,发送8位数据,最后再发送1位停止信号。
发送流程如下
-
页写入方式
单片机在一个数据写周期内,可以连续 访问1页(8个)E2PROM存储单元,单片机先发送启动信号,接着送1字节的控制字,再送1字节的存储器起始单元地址。上述字节都得到E2PROM应答后,就可以发送最多1页的数据,并顺序存放在以指定起始地址开始的相继单元,最后以停止信号结束。
流程如下 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REdplo3X-1601302118650)(C:/Users/Asus/AppData/Roaming/Typora/typora-user-images/image-20200927165030037.png)]
-
指定地址读操作
读指定地址单元的数据。单片机在启动信号后先发送含有片选地址的写操作控制字,E2PROM应答后发送1字节的指定单元地址,再发送一个含有片选地址的读操作控制字。如果E2PROM做出应答,被访问单元的数据就会按SCL信号同步出现在串行数据/地址线SDA上。(这里简单来说,写入器件地址,然后写入器件内部地址,然后让给单片机一个读的指令,让单片机读取该地址器件内部的地址上的数据,就酱)
这种读操作的数据帧格式如下
-
指定地址连续读
该方式的读地址控制与前面指定地址读相同。单片机接收到每个字节数据后应做出应答,只要E2PROM检测到应答信号,其内部的地址寄存器就自动加1,指向下一个单元,并顺序将指向的单元的数据送到SDA串行数据线上。当需要结束读操作时,单片机接收到数据后在需要应答的时刻发送一个非应答信号,在发送一个停止信号即可。
4.TX-1C实验板与AT24C02连接
实例:利用定时器产生一个0~99秒变化的秒表,并且显示在数码管上,每过1秒,将这个变化的数写入板上AT23C02内部。当关闭实验板电源,并再次打开实验板电源时,单片机先从AT24C02中将原来写入的数读出来,接着此数继续变化并显示在数码管上
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
bit write=0;//定义了一个单个的二进制位,这个位的值初始是0,然后要么1要么0
sbit sda=P2^0;
sbit scl=P2^1;
sbit dula=P2^6;
sbit wela=P2^7;
uchar sec,tcnt;
uchar code table[]={
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
void delay(){
;;}
void delay1ms(uint z)
{
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
void start()//启动信号
{
sda=1;
delay();
scl=1;
delay();
sda=0;
delay();
}
void stop()//停止信号
{
sda=0;
delay();
scl=1;
delay();
sda=1;
delay();
}
void respons()//应答信号
{
unsigned char i;
scl=1;
delay();
while((sda==1)&&(i<256))
{
i++;
}
scl=0;
delay();
}
void init()//总线初始化
{
scl=1;
delay();
sda=1;
delay();
}
void write_byte(uchar date)//写操作
{
unsigned char i,temp;
temp=date;
for(i=0;i<8;i++)
{
temp=temp<<1;
scl=0;
delay();
sda=CY;
delay();
scl=1;
delay();
}
scl=0;
delay();
sda=1;
delay();
}
uchar read_byte()
{
unsigned char i,k;
scl=0;
delay();
sda=1;
delay();
for(i=0;i<8;i++)
{
scl=1;
delay();
k=(k<<1)|sda;
scl=0;
delay();
}
delay();
return k;
}
void write_add(uchar address,uchar date)//字节写入格式格式
{
start();
write_byte(0xa0);//10100000
respons();
write_byte(address);
respons();
write_byte(date);
respons();
stop();
}
uchar read_add(uchar address)
{
uchar date;
start();
write_byte(0xa0);
respons();
write_byte(address);
respons();
start();
write_byte(0xa1);
respons();
date=read_byte();
stop();
return date;
}
void display(uchar shi_c,uchar ge_c)//显示程序
{
dula=0;
P0=table[shi_c];//显示第一位
dula=1;
dula=0;
wela=0;
P0=0xfd;//11111101
wela=1;
wela=0;
delay1ms(500);
dula=0;
P0=table[ge_c];//显示第二位
dula=1;
dula=0;
wela=0;
P0=0xfe;//11111110
wela=1;
wela=0;
delay1ms(500);
}
void main()
{
init();
sec=read_add(2);//读出保存的数据赋予sec
if (sec>100)//防止首次读取出现错误
{
sec=0;
}
TMOD=0x01;//定时器工作在方式1
ET0=1;
EA=1;
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;//定时器0.05秒中断一次
TR0=1;//启动定时器
while(1)
{
display(sec/10,sec%10);
if(write==1)//判断计时器是否计时1秒
{
write=0;//清0
write_add(2,sec);//在24c02的地址2中写入数据sec
}
}
}
void t0() interrupt 1//定时器中断服务函数
{
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
tcnt++;//每过50ms,tcnt加1
if(tcnt==20)
{
tcnt=0;//重新再计
sec++;
write=1;//1秒写一次24c02
if(sec==100)//定时100秒,再从零开始计时
{
sec=0;
}
}
}
- 这里面
void delay(;;)
是一个微秒级别的延时函数,用空语句来实现短时间延时。,在晶振为11.0592MHz时,该延时函数延时大概4~5μs,用来操作I2C总线。 - 在主程序的开始出先读取上次写入AT24C02中的数据,下面两句是为了防止第一次操作AT24C02时出现读取数据大于100的情况,导致数码管乱码。