目录
一、什么是IIC
IIC是两线式串行通信总线。(串行:一位一位传输)
IIC总线可以将单片机与其他具有IIC总线通信接口的外围设备连接起来,如图1所示,通过串行数据线(SDA)和串行时钟线(SCL)与连接到该双线的器件传递信息。每个IIC器件都有一个唯一的识别地址(IIC总线支持7位和10位地址),而且根据需要,都可以作为一个发送器和接收器(这是由具体的器件功能决定的)。
IIC器件在执数据传输时,可以看作主机也可以看做是从机。主机是初始化总线数据传输并产生允许传输时钟信号的器件,此时任何被寻址的其他IIC器件都被认为是从机。
IIC总线有:标准(100kbit/s)、快速(400kbit/s)和高速(3.4Mbit/s)三种数据传输速度模式,支持高速模式的可以向下支持低速模式。IIC总线连接的IIC器件数量受到总线的最大电容400pF限制,总线连接的器件越多,连线越长,分布电容越大(最多也就是400pF)。
在图1中,如果单片机向IIC器件2中写入数据,会先从SDA数据线中送出IIC器件2的地址,那么挂在总线上众多的器件只有IIC器件2与总线接通,单片机再将数据从SDA数据线送出,该数据则被IIC器件2接收。这里单片机是主兼发送器的,IIC器件2及其他器件均为从机,IIC器件2为接收器。
图1 单片机通过IIC总线连接多个IIC器件
二、IIC总线通信协议
通信协议是通信各方必须遵守的规则,否则通信无法进行,IIC总线通信协议主要内容有:
1、总线空闲:SCL和SDA均为高电平( SCL=1 SDA=1 )。
2、开始信号:SCL为高电平,SDA出现下降沿,该下降沿即为开始信号。
3、数据传送:开始信号出现后,SCL为高电平时从SDA读取的电平为数据,且在SCL为高电平时,SDA的电平不允许变化,只有SCL为低电平时,SDA上的电平才可以发生改变。SDA传送数据时,从高位到低维逐位进行,一个SCL脉冲高电平对应1位数据。
4、停止信号:SCL位高电平,SDA出现上升沿,该上升沿为停止信号,停止信号过后,总线被认为空闲(SCL、SDA均为高电平)。
三、IIC总线的数据传送格式
IIC总线可以一次传送单字节数据,也可以依次传送多字节数据,但不管是单字节还是多字节都应该在满足协议的条件下进行。这里详细介绍一下单字节数据传送格式,多字和单字节很类似,会了单字节,多字节也就会了。
3.1 单字节数据传送格式
IIC总线的单字节数据传送格式如图2所示。
单字节的传送格式为:开始信号——传送的数据(由高位到低位)——应答信号(ACK)——停止信号。在开始信号发出之后,SDA上的数据在每一个SCL的高电平期间保持稳定,在SCL低电平期间开始变化,进行数据传送。
传送8位后,发送端(单片机)向接收端(IIC器件)数据传送完毕,需要IIC器件向单片机做出回应,这个回应是接收端通过控制 SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成,数据传送可以继续进行。
图2 IIC总线的单字节数据传送格式
这个回应其实就是数据或地址传输过程中的响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当接收端设备接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号——即特定的低电平脉冲,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号——即特定的高电平脉冲,发送方接收到该信号后会产生一个停止信号,结束信号传输。所以看有没有应答信号,就是看在数据发送完成后,SDA线上的电平变化(写程序时的思路)。应答响应时序图如图3:
图3 应答响应时序图
3.2 多字节数据传送格式
为了提高工作效率,IIC总线往往需要一次传送多个字节,图4是典型的IIC总线多字节数据传送格式,该格式为:开始信号——第1个字节数据(7位从机地址+1位读/写设定值)——应答信号——第2个字节数据(8位从机内部单元地址)——应答信号——第3个字节数据(8位数据)——应答信号(或停止应答信号)——停止信号。
图4 典型的IIC总线多字节数据传送格式
step1:首先就是开始信号,有了开始信号才能进行下一步的传输。
step2:第1个字节数据是为了选择应向哪一个IIC器件传输数据,用前7位确定地址,用最后一位0/1来确定传送方向。0——写数据(主机向从机发送数据),1——读数据(主机从从机那里读取数据)。接下来是应答信号。
step3:第2个字节数据是为了确定由主机传送过去的数据应该写在从机的什么位置,所以发送过去的是一个8位从机内部单元地址。接下来是应答信号。
step4:第3个字节数据就是真正要发送的内容了,如果还要继续发送数据,第4个字节数据也是真正要发送的内容,不需要再发一次从机内部的单元地址,因为地址会自动加一(这里所针对的IIC器件就是EEPROM,如果其他器件有什么新的规定,或者下一个地址想重新选择,可以当做我没说)。接下来是应答信号。
step5:最后就是停止信号。
四、IIC总线存储器24C02(EEPROM)
4.1 芯片介绍
图5 24C02的引脚功能
E0-E2:器件地址设置引脚。IIC总线最多可同时连接8个24C02芯片,在外部将E0-E2引脚接高、低电平可给8个芯片设置不同的器件地址(000~111),当这些引脚悬空时,默认值为0。
SCL:串行时钟脉冲输入引脚
SDA:串行数据输入/输出引脚
MODE:多字节/页面写入模式(后面会介绍,莫要着急)
\WC:写控制
表1 24C02的引脚功能说明
4.2 器件地址的设置
当24C02和其他器件挂在IIC总线时,为了区分他们,需要给每一个器件设定一个地址,该地址即为器件地址,挂在同一IIC总线上的器件地址不能相同。24C02有E0-E2三个地址引脚,可以设置8个不同的器件地址。
24C02的器件地址为7位,其中高4位固定为1010,低3位由E0、E1、E2引脚的电平值决定。最后1位是读写位,决定了数据传送的方向。
表2 24C02地址设置
4.3、写操作和读操作
(1)写操作(读写位为0)
在第三节IIC总线的数据传送格式已经详细的说明了过程,此处就简单用图来看看整体的一个过程。
a. 单字写操作
图6 24C02单字节写操作色数据格式
b. 页写操作
页写操作即多字节写操作,24C02可根据需要一次写入2~16个字节数据
图7 24C02页写操作的数据格式(一次写入16个字节数据时)
(2)读操作(读写位为1)
24C02的读操作分为立即地址读操作、选择读操作和连续读操作
a. 立即地址读操作
立即地址读操作是指不发送字节地址而是直接读取上次操作地址N之后的地址N+1数据,24C02的N值为0~255(00H~FFH),如果上一次的地址是N=255,立即地址读操作会跳转读取地址0的数据。
立即地址读操作的数据格式如图8所示。主机发送开始信号,再发送7位器件地址和读写信号,这一步骤是选择要与哪个IIC器件进行数据传输,并确定好传输方向。被选中的器件先向主机发送一个ACK信号,再向主机发送一个字节数据,主机无须发出ACK信号应答,但要发出一个停止信号给IIC器件,即24C02。
图8 24C02单字节读操作的数据格式
b. 选择读操作
选择读操作是指取任意地址单元的字节数据。选择读操作的过程如图9所示。
主器件先发送开始信号和7位器件地址,再发送一个低电平读写信号执行伪写操作,这样做的目的是:要先选择从被选器件的哪个地址中读取数据(此处所选的地址是第n单元地址),所以先由主器件执行写操作(在这里其实就是伪写),告诉被选设备是哪个地址,接着是被选设备对主器件的应答。然后发送n单元字节地址地址,被选器件应答。
做好前面的准备工作之后,接下来:
主器件发送开始信号和7位器件地址,再发送一个高电平的读信号,表明数据传输方向,被选器件作出应答,然后将n单元字节数据发送给主器件。主器件不需要发出ACK应答,但需要发送一个停止信号。
图9 24C02选择读操作数据格式
c. 连续读操作
连续读操作是指从指定单元一次连续取多个字节数据。
数据传送的过程是前面两种的重复操作。由立即地址读操作启动的连续读的数据格式,24C02内部由256个字节存储单元,如果24C02将第256个字节单元(地址为FFH)的数据传送给主器件后,主器件继续回复ACK信号,24C02就会从头开始将第1个存储单元(地址为00H)的数据传送给主器件。
图10 24C02连续读操作的数据格式
五、代码实现
前面的头文件和一些定义就不写了。
/************************** 延时 ****************************/
void delay_10us(u16 time)
{
while(time--);
}
/************************** iic启动 ****************************/
void iic_start()
{
SDA=1;
delay_10us(1);
SCL=1;
delay_10us(1);
SDA=0;
delay_10us(1);
SCL=0;
}
/************************** iic停止 ****************************/
void iic_stop()
{
SDA=0;
delay_10us(1);
SCL=1;
delay_10us(1);
SDA=1;
delay_10us(1);
}
/************************** iic发送 ****************************/
u8 iic_SendByte(u8 dat)
{
u8 i,j;
j=0;
SCL=0;
for(i=0;i<8;i++)
{
if(dat&0x80)
SDA=1;
else
SDA=0;
dat<<=1;
delay_10us(1);
SCL=1;
delay_10us(1);
SCL=0;
delay_10us(1);
}
SDA=1;
delay_10us(1);
SCL=1;
while(SDA)
{
j++;
if(j>200)
{
SCL=0;
delay_10us(1);
return 0; //无应答,返回0
}
}
SCL=0;
delay_10us(1);
return 1; //有应答,返回1
}
/************************** iic读数据 ****************************/
u8 iic_ReadByte()
{
u16 i;
u8 dat=0x00;
for(i=0;i<8;i++)
{
SCL=1;
delay_10us(1);
dat<<=1;
if(SDA)
dat++;
delay_10us(1);
SCL=0;
delay_10us(1);
}
return dat;
}
/************************** 向24c02中写数据 ****************************/
void at24c02_write_one_Byte(u8 addr, u8 dat) //函数名不能用数字开头,否则会报错
{
iic_start();
iic_SendByte(0xa0);
iic_SendByte(addr);
iic_SendByte(dat);
iic_stop();
}
/************************** 从24c02中读数据 ****************************/
u8 at24c02_read_one_Byte(u8 addr)
{
u8 temp=0;
iic_start();
iic_SendByte(0xa0);
iic_SendByte(addr);
iic_start();
iic_SendByte(0xa1);
temp=iic_ReadByte();
iic_stop();
return temp;
}
参考《零基础学51单片机(C语言版)》
作者:蔡杏山