一.I2C串行总线概述 I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系。
I2C总线的数据传送
一、数据位的有效性规定
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
二、起始和终
止信号
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
基于以上知识我们可以来编写两个产生起始信号和终止信号的函数
void I2cStart()
{
SDA=1;
Delay10us();//函数功能:如其名,延时10us
SCL=1;
Delay10us();
SDA=0;
Delay10us();
SCL=0;
Delay10us();
}
void I2cStop()
{
SDA=0;
Delay10us();
SCL=1;
Delay10us();
SDA=1;
Delay10us();
}
字节传送与应答
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
传输八位字节的过程如下:
SCL时钟线循环跳变,每次到低电平的时候主机写入一次SDA数据信号,(必须在SCL低电平的时候写入SDA,因为SCL高电平时改变SDA将被认为是起始或终止信号)一共写八次,完成八位二进制码的传输,即传送了一个字节,发送完成后要将SDA置成高电平来等待从机应答
一个字节发送完成后,就要检测从机的应答,从机的应答是在第九个时钟序列对SDA写一个0。
下面我们编写一个函数来实现这个功能
该函数的参数为要发送的字节dat
返回值是从机的应答状态,如果如果反回1说明从机有应答。
unsigned char I2cSendByte(unsigned char dat)
{
unsigned char a=0,b=0;
//a作为循环变量,
//b作为计时变量,一个机器周期为1us,设立最大延时200us,如果超时应答,认为是非应答。
for(a=0;a<8;a++)//要发送8位,从最高位开始
{
SDA=dat>>7; //取出dat的最高为
dat=dat<<1;//向左移位,即改变了最高位
Delay10us();
SCL=1;
Delay10us();
SCL=0;
Delay10us()
}
SDA=1;//发送完成等待应答
Delay10us();
SCL=1;
while(SDA)//等待应答,也就是等待从设备把SDA拉低
{
b++;
if(b>200) //如果超过2000us没有应答发送失败,或者
//为非应答,表示接收结束
{
SCL=0;
Delay10us();
return 0;
}
}
SCL=0;
Delay10us();
return 1;
}
再写一个主机从从机读取字节的函数
反回读取的字节
unsignr I2cReadByte()
{
unsigned char a=0,dat=0;
SDA=1;//起始和发送一个字节之后SC0
Delay10us();
for(a=0;a<8;a++)//接收8个字节
{
SCL=1;
Delay10us();
dat<<=1;
dat|=SDA;//dat与SDA按位或,会把SDA的值写在了dat的最后一位
Delay10us();
SCL=0;
Delay10us();
}
return dat;
}
由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据),它必须将数据线置于高电平,而由主机产生一个终止信号以结束总线的数据传送。
如果从机对主机进行了应答,但在数据传送一段时间后无法继续接收更多的数据时,从机可以通过对无法接收的第一个数据字节的“非应答”通知主机,主机则应发出终止信号以结束数据的继续传送。
当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。
数据帧格式
1、总线的寻址策略
I2C总线协议有明确的规定:采用7位的寻址字节(寻址字节是起始信号后的第一个字节)。
(1) 寻址字节的位定义
D7~D1位组成从机的地址。D0位是数据传送方向位,为“0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。
主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己正被主机寻址,根据R/T位将自己确定为发送器或接收器。
从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的7位寻址位有4位是固定位,3位是可编程位,这时仅能寻址8个同样的器件,即可以有8个同样的器件接入到该I2C总线系统中。
(2)带寻址数据传输发送
I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
在总线的一次数据传送过程中,可以有以下几种组合方式:
a、 主机向从机发送数据,数据传送方向在整个传送过程中不变:
注:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。
A表示应答, A非表示非应答(高电平)。S表示起始信号,P表示终止信号。下同
B、主机在第一个字节后,立即从从机读数据
C。在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相。
上面介绍的是所有IIC总线通信均满足的规范,实际在应用过程当中,还要结合从机相关数据手册来编写
我们以期间At24c系列的E2PROM芯片为例,看一下他们的具体应用.
以下是数据手册,共13页,其实很多信息都是没用的
从数据手册可以看出
AT24C系列E2PROM芯片地址的固定部分为1010,A2、A1、A0引脚接高、低电平后得到确定的3位编码。形成的7位编码即为该器件的地址码。这里把A2、A1、A0引脚全部接地,得到他的地址是1010000,在主机往该地址发送数据是,作为从机的八位完整地址是10100000换算成16进制是ao。那么主机读数据是,就应该是a1.
传送数据时,单片机首先发送一个字节的被写入器件的存储区的首地址,收到存储器器件的应答后,单片机就逐个发送各数据字节,但每发送一个字节后都要等待应答。
/注意这里的存储区首地址在上述通信过程中,并非地址,而是与有效数据等价都是主机向从机发送的数据/
AT24C系列器件片内地址在接收到每一个数据字节地址后自动加1,在芯片的“一次装载字节数”(不同芯片字节数不同)限度内,只需输入首地址。装载字节数超过芯片的“一次装载字节数”时,数据地址将“上卷”,前面的数据将被覆盖。
当要写入的数据传送完后,单片机应发出终止信号以结束写入操作。写入n个字节的数据格式 :
下面我们编写函数来实现着个过程
参数分别是存储区的地址和要发送的一个数据,发送完成后即终止通信。
void At24c02Write(unsigned char addr,unsigned char dat)
{
I2cStart();
I2cSendByte(0xa0);//发送七位从机地址并在最后一位加0
I2cSendByte(addr);//发送要写入内存地址
I2cSendByte(dat); //发送数据
I2cStop();
}
(3)读出过程
(对应方式c)
单片机先发送该器件的7位地址码和写方向位“0”(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应
然后,再发一个字节的要读出器件的存储区的首地址,收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位(“1”),收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作
unsigned char At24c02Read(unsigned char addr)
{
unsigned char num;
I2cStart();
I2cSendByte(0xa0); //发送七位从机地址并在最后一位加0
I2cSendByte(addr); //发送要读取的地址
I2cStart();
I2cSendByte(0xa1); //发送读器件地址
num=I2cReadByte(); //读取数据
I2cStop();
return num;
}
主函数其实写起来就比较容易了,重点是要学会通过看数据手册中的一些抽象的信息来转换成对我们有用的代码