1.什么是IIC
IIC 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,两条线可以挂多个设备。 IIC设备(绝大多数)里有个固化的地址,只有在两条线上传输的值等于IIC设备的固化地址时,其才会作出响应。通常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。
2.IIC物理层介绍
IIC通讯的主机和从机之间只用连接2根线,一个是SCL(控制时钟),一个是SDA(传递数据,双向)。SCL和SDA总线通过上拉电阻接到电源。上拉电阻4.7K-10K。
图1 IIC协议物理层连接图
需要注意的几点:很重要
1.总线通过上拉电阻接到电源。当 IIC 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。高阻态的意思就是相当于断路(IIC设备没有和总线连接着)。可以看到SCL、SDA空闲是都是高电平。
2. 每个连接到总线的从机设备都有一个独立的地址(有些是出厂时定死的,没法改变),主机可以利用这个地址进行指定访问从机。主机先广播一个从机地址,总线上的从机都会收到这个地址,收到后和自身地址对比,若不同,忽略,若相同则给主机发送应答信号,确立连接。
3.总线上的各个设备,主机和从机。都采用开漏模式与总线相连。目的是可以输出高阻态。总线上的设备通过把SCL、SDA两个总线在不同时间拉低拉高,来表示数据0、1.。设备想要输出0时,则设备接地,总线被拉低。当设备想输出1时,不能直接输出高电平(会导致设备之间短路),而是输出高阻态(断路),让总线上的上拉电阻去把总线拉高。
4.SCL线的时钟同步:SCL由于具有线“与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。当所有的节点都发送高电平时,总线才能表现为高电平。正是由于线“与”逻辑功能的原理,当多个节点同时发送时钟信号时,在总线上表现的是统一的时钟信号。
5.SDA线的仲裁:SDA线的仲裁也是建立在总线具有线“与”逻辑功能的原理上的。节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线。
3.IIC协议层介绍
知道了IIC的物理层连接后,就是IIC的协议层。规定了IIC设备间通信的几个基本信号:起始信号、停止信号、读、写、应答、非应答。
3.0 几个知识点:
(1)下降沿:信号从高电平到低电平的跃变动作
(2)上升沿:信号从低电平到高电平的跃变动作
(3)释放总线:让总线为高电平(通过高阻态,让上拉电阻拉高),线与特性。
SDA=0时,SDA脚在IC内部被直接接在GND上,它只能是低电平,外部器件也无法把SDA脚拉高,这叫拉低。
SDA=1时,SDA脚为高阻态,这时SDA可以被别的器件拉低或者拉高
3.1 起始信号和停止信号
图2 IIC开始信号和停止信号的定义
(1)主机控制时钟线。当开始发送开始信号时,SCL、SDA必须处于空闲状态(都为高)。当SCL为高时,让SDA产生一个下降沿跳变。即代表开始信号。
(2)主机控制时钟线。当SCL为高时,让SDA产生一个上升沿跳变。即代表停止信号。当产生停止信号后,SCL、SDA必须处于空闲状态。无论读写方向如何。开始信号和停 止信号都是由主机控制。
3.2 写
图3 IIC写
(1)写数据写一个字节。按位写。主机用SDA的高低电平表示一位1或0高位先写。
(2)在IIC主机写数据到从机的过程中,时钟线和数据线都由主机控制。
(3)写数据的过程是利用SCL、SDA互相配合。当SCL为高电平期间,去判断SDA线的电平,把SDA的数据(1或0)写入从机。这里需要注意一点。从图3中可以看到SDA的高电平(或低电平)时间比SCL为高电平的时间长。这就意味着,在SCL被拉高时,SDA必须提前准备好数据。并且在SCL变低之前,SDA的状态不能变。这样保证数据的有效性。用伪代码表示为:
SDA=1(或0) //准备数据(主机)
SCL=1 //拉高SCL
delay //延时
SCL=0 //拉低SCL。SDA数据有效,已被写入从机
3.3 读
图4 IIC写(从机->主机)
(0)读和写的时序图一样,只是方向不同。
(1)读数据读一个字节。按位读。从机用SDA的高低电平表示一位1或0,高位先读。
(2)在IIC主机读数据的过程中(从机->主机),时钟线由主机控制,数据线由从机控制。
(3)读数据的过程是利用SCL、SDA互相配合。主机控制SCL表示什么时候读,从机控制SDA表示主机读的时候的数据。流程:当从机接收到主机要读数据的命令后,从机先在SDA线上准备好数据。主机拉高SCL,延时,拉低SCL,SDA数据有效。用伪代码表示为:
SDA=1(或0) //准备数据(从机)
SCL=1 //拉高SCL(主机)
delay //延时
SCL=0 //拉低SCL。SDA数据有效,已被读入主机
3.4应答、非应答
图5 应答和非应答时序图
(0)先来说一下应答和非应答的时序图,再说应答和非应答信号的作用。
(1)应答和非应答其实和上面读写步骤一样,都是在SCL被拉高前,在SDA线上准备好数据。其实就是和读写时写一个数据0和数据1的时序一样。那可能会有一个疑问,应答和非应答和普通读写一样,那怎么区分到底是数据还是应答非应答信号呢?
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
反之亦然,主机从从机读取数据后也会给从机发送应答、非应答信号。
从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输(读或写)后紧跟着的时钟周期,低电平0表示应答,1表示非应答。
(2)也就是说在读写过程中第9个时钟周期的数据被当作了应答、非应答的信号。因为发送数据时按字节发送的(8位)。
(3)应答和非应答可以由主机发给从机,也可以由从机发送给主机。谁发信号,谁控制SDA,但SCL始终由主机控制。
之前在学习过程中,听到别人介绍,主机发送应答信号发送非应答信号,不发送应答信号。真的晕了。现在我总结一下,说的更清楚一些。应答和非应答都是一种信号,应答表示通信还要继续,非应答表示通信要结束了。
看到现在应该要明白的几点:
SCL一直由主机控制,开始和结束信号都由主机控制
SDA根据读写确定传输数据方向:主机->从机、从机->主机
SDA数据按位发送(或读取),每发送8位,第9位被判定为应答和非应答
应答和非应答可由主机发送给从机,可以由从机发送给主机。
先从主机写数据开始理解应答和非应答:阴影部分表示主机发送,白色部分表示从机发送。
图6 主机写从机
(1)主机发送开始信号S,发送从机地址+0(共8位,0表示写方向),广播地址,由所有从机匹配地址,匹配后给主机发送应答信号A,主机接收到从机的应答信号A后,开始写入数据(8位),每当主机发送了8位数据后,若从机还想继续接收则发送应答信号A,如果不想在接收,或者接收出错,则从机发送非应答信号。一旦从机发送非应答信号,此次通信接收,则必须由主机发送停止信号P。
(2)总结:从机发应答,通信继续,主机继续发数据。从机发非应答,通信结束,主机不再发数据,主机接着发停止信号。释放总线。
再从主机读数据开始理解应答和非应答:阴影部分表示主机发送,白色部分表示从机发送。
图6 主机读从机
(1)主机发送开始信号S,发送从机地址+1(共8位,1表示读方向),广播地址,由所有从机匹配地址,匹配后给主机发送应答信号A,从机紧接着发送数据(主机在接收到应答信号时就应该开始接收数据了),每当主机接收到数据后,主机发送应答信号A,则从机继续发数据,当主机发送非应答信号,则从机不再发送数据,主机紧接着发送停止信号P。
(2)总结:主机发应答,通信继续,从机继续发数据。主机发非应答,通信结束,从机不再发数据,主机接着发停止信号。释放总线。
总结:现在已经介绍完了IIC的几个基本信号时序,和通信逻辑。再用IIC驱动其他模块时,使用IIC基本协议去定义具体的协议。协议一层一层封装。
IIC的EEPROM时序图:
4.IIC软件模拟
使用2个普通IO引脚接SCL、SDA。引脚配置成开漏模式。
4.1开始信号
1 //产生IIC起始信号
2 void IIC_Start(void)
3 {
4 SDA_OUT(); //SDA线输出
5 IIC_SDA=1;
6 IIC_SCL=1;
7 delay_us(4);
8 IIC_SDA=0;//SCL为高,SDA下降沿
9 delay_us(4);
10 IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
11 }
4.2停止信号
1 void IIC_Stop(void)
2 {
3 SDA_OUT();//SDA线输出
4 IIC_SCL=0;
5 IIC_SDA=0;
6 delay_us(4);
7 IIC_SCL=1;
8 IIC_SDA=1;//SCL为高,SDA上升沿。发送I2C总线结束信号
9 delay_us(4);
4.3主机应答
1 void IIC_Ack(void)
2 {
3 IIC_SCL=0;
4 SDA_OUT();
5 IIC_SDA=0;
6 delay_us(2);
7 IIC_SCL=1;
8 delay_us(2);
9 IIC_SCL=0;
10 }
4.4主机非应答
1 void IIC_NAck(void)
2 {
3 IIC_SCL=0;
4 SDA_OUT();
5 IIC_SDA=1;
6 delay_us(2);
7 IIC_SCL=1;
8 delay_us(2);
9 IIC_SCL=0;
10 }
4.5主机等待从机应答或非应答
1 u8 IIC_Wait_Ack(void)
2 {
3 u8 TimeOut=0;
4 SDA_IN(); //SDA设置为输入
5 IIC_SDA=1;
6 delay_us(1);
7 IIC_SCL=1;
8 delay_us(1);
9 while(READ_SDA)
10 {
11 TimeOut++;
12 if(TimeOut>250)
13 {
14 IIC_Stop();
15 return 1;
16 }
17 }
18 IIC_SCL=0;//时钟输出0
19 return 0;
20 }
主机根据从机在SDA线上的反馈,来判断从机是发送应答还是非应答。
4.6写
1 void IIC_Send_Byte(u8 txBuffer)
2 {
3 u8 i;
4 SDA_OUT();
5 IIC_SCL=0;//拉低时钟开始数据传输
6 for(i=0;i<8;i++)
7 {
8 if((txBuffer&0x80)>>7)
9 IIC_SDA=1;
10 else
11 IIC_SDA=0;
12 txBuffer<<=1;
13 delay_us(2);
14 IIC_SCL=1;
15 delay_us(2);
16 IIC_SCL=0;
17 delay_us(2);
18 }
19 }
写数据,高位先写,按位写。
4.7读
1 u8 IIC_Read_Byte(void)
2 {
3 unsigned char i,rxBuffer=0;
4 SDA_IN();//SDA设置为输入
5 for(i=0;i<8;i++ )
6 {
7 IIC_SCL=0;
8 delay_us(2);
9 IIC_SCL=1;
10 rxBuffer<<=1;
11 if(READ_SDA)
12 {
13 rxBuffer++;
14 }
15
16 delay_us(1);
17 }
18 return rxBuffer;
19 }
5.参考博客:
https://www.cnblogs.com/CodeWorkerLiMing/p/10830778.html
https://blog.csdn.net/xx_0305401/article/details/81914528
https://www.cnblogs.com/aaronLinux/p/6218660.html
https://blog.csdn.net/qq_38410730/article/details/80312357
https://blog.csdn.net/qq_38410730/article/details/80312357
https://blog.csdn.net/weixin_44586750/article/details/88599065
https://blog.csdn.net/Xiaomo_haa/article/details/87918394
https://blog.csdn.net/Do_Not_Ask_Me/article/details/97136905
https://blog.csdn.net/zhanghuaichao/article/details/48266309
https://blog.csdn.net/Firefly_cjd/article/details/51921129#t0