1.认识IIC
1、IIC协议概述:
- IIC(Inter-Integrated Circuit,集成电路总线)是一种串行通信协议,也被称为I2C协议。它是由荷兰的PHILIPS公司(现在philips公司将其半导体部门拆分出来并更名为NXP半导体公司)在1982年开发的一种简单、高效的通信协议。
- IIC协议主要用与连接电路之间进行短距离的数字通信,比如连接微控制器及其外围设备。
- IIC协议使用两线式串行总线。
- IIC有点像串口,但是串口是全双工,但是IIC属于半双工同步通信方式,数据传输线只有一根,传输和接收要去占用SDA线。
2、IIC协议特点:
- 简单性:IIC协议只使用了两根信号线,并且由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。能够支持40个组件。
- IIC协议的通信速率:在一些特定的应用或特定的环境下(比如用于学习),为了提供高通信的稳定确定性和可靠性,某些设备或系统选择将IIC协议的传输速率限制在较低的速率,例如10 Kbps。但它并不代表IIC协议的最大通信速率就是10 Kbps。根据协议版本不同IIC协议的通信速率会有不同,标准模式的IIC协议支持最大传输速率为100 Kbps,快速模式则为 400 Kbps,高速模式则为 3.4 Mbps。
- 多主控(multimastering):其中任何能够进行发送和接收的设备都可以成为主控设备,这些主控设备可以是MCU、OLED、手持传感器、陀螺仪等其他I2C接口器件(如下图),这些I2C接口器件都共用这两根串行总线。当然,在任何时间点上只能有一个主控。主控设备负有责任发起和控制通信,它可以向其他设备发送命令、读取数据,并负责协调总线上的通信活动。其他设备被称为从属设备(Slave Device),它们被主控设备控制并响应主控设备的命令。从属设备不能主动发起通信,而正在主控设备的指示下进行相应的操作。因此,虽然任何设备都可以作为IIC协议中的主控设备,但在实际应用中,根据设备的功能和设计,选择合适的设备作为主控设备,其他设备则作为从属设备,以满足通信需求和数据交换的要求
- 硬件地址查找:每个从设备在总线上都有唯一一个硬件地址,主设备通过这个地址来选择与之通信的从设备。
3、IIC协议构成:IIC协议是两线式串行总线,两根信号线分别是数据线SDA和时钟线SCL。所有用IIC通讯的设备都需要遵循相同的通讯时序。
- SDA(Serial Data Line,数据线):用于传输数据。
- SCL(Serial Clock Line)时钟线:用于传输时间信息,时钟信号是由主控器件产生的。
- 在时间信息的控制下,主设备(如微控制器)可以与多个从设备(如传感器、存储器、显示器等)进行双向通信。
- 所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。
4、单片机使用IIC协议的注意点:
- 由于51单片机开发板上刚好有硬件串口,也就是有与串口对应的寄存器支持,所以,51单片机的串口通信协议(波特率、数据位、起始位、停止位)是通过配置寄存器来完成的。
- 但是51单片机上没有IIC这种硬件设备,所以无法通过配置寄存器来驱动串行通信协议,需要分析I2C接口器件时序后转化为代码,通过I/O口来模拟IIC协议(类似于LCD模块、DHT11模块这些非标协议)。
- 接下来,我们以51单片机作为主设备,以OLED屏幕作为从设备,连接方式是直连。当然如果有多个从设备,那么就需要用到面包板,实现如上图中并联的效果。
- C语言在调用函数时有一个压栈和弹栈的过程,所以即使你调用的函数什么也没有做,在晶振为11.0592MHz的情况下,也需要耗费5微秒的时间。而单片机中_nop()_就是一个空函数,一个nop的耗时为5微秒。
5、IIC总线的信号类型:IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号。
- 类似于串口,多机通讯无非要关注:起始位,停止位,数据位,波特率。
- 那么在IIC协议中,开始信号就相当于设置起始位,结束信号就相当于设置停止位,程序中根据时序图通过延时等方法去控制时间点相当于设置波特率。
6、分析时序图的正确逻辑:回顾DHT11模块的时序分析,我们做的还不好,正确的分析逻辑应该如下。
- 对总线上的信号进行分类:因为不同信号的表达方式是不一样的,比如启动信号是置高低电平,响应信号是被拉成高低电平。分类完成后的每部分信号都是总体通讯时序图中的一部分。
- 在每种信号时序上标注字母:每种信号时序图上可能有多个数据线,所以标注的点最好划分的细致一点
- 三步骤读时序图:读时序图关注开始、转折、结尾。读的时候进行记录,可以使用“置高/低电平”、“电平被拉高/低”、“从低/高电平转折为高/低电平”、“维持高/低电平”、“延时”、“卡x点,直到数据线变成高/低电平”等字眼帮助判断。
- 编写代码:将时序逻辑复制到代码编辑器中,照着时序分析写代码(一个时序分析完之后就进行一次函数封装)。
- 注意:当时序图中总线多的时候(比如LCD1602的读写时序图中有4根总线),那么就需要先查看引脚说明。
2.IIC协议通讯时序分析1:起始、终止、应答
1、起始信号:
- o点:
- SCL置高电平
- SDA置低电平
- a点:
- SCL维持高电平
- SDA从低电平转折为高电平,时间未知
- a—>b:
- SCL维持高电平
- SDA延时4.7微秒
- b—>c:
- SCL维持高电平
- SDA从高电平转折为低电平,时间未知
- c—>d:
- SCL延时4微秒
- SDA维持低电平
- d点:
- SCL从高电平转折为低电平,时间未知
- SDA维持低电平
-
void IIC_Start() { // a点: scl = 1; // SCL置高电平 sda = 1; // SDA从低电平转折为高电平,时间未知 // a—>b: // SCL维持高电平 _nop_(); // SDA延时4.7微秒 // b—>c: // SCL维持高电平 sda = 0; // SDA从高电平转折为低电平,时间未知 // c—>d: _nop(); // SCL延时4微秒 // SDA维持低电平 // d点: scl = 0; // SCL从高电平转折为低电平,时间未知 // SDA维持低电平 }
2、终止信号:
- o点:
- SCL置低电平
- SDA置低电平
- a点:
- SCL从低电平转折为高电平,时间未知
- SDA维持低电平
- a—>b:
- SCL维持高电平
- SDA延时4微秒
- b—>c:
- SCL维持高电平
- SDA从低电平转折为高电平,时间未知
- c—>d:
- SCL维持低高电平
- SDA延时4.7微秒
- d点:
- SCL维持高电平
- SDA从高电平转折为低电平,时间未知
-
void IIC_Stop() { // a点: scl = 1; // SCL从低电平转折为高电平,时间未知 sda = 0; // SDA置低电平 // a—>b: // SCL维持高电平 _nop_(); // SDA延时4微秒 // b—>c: // SCL维持高电平 sda = 1; // SDA从低电平折为高电平,时间未知 // c—>d: // SCL维持低高电平 _nop_(); // SDA延时4.7微秒 // d点: // SCL维持高电平 sda = 0; // SDA从高电平转折为低电平,时间未知 }
3、应答信号ACK:应答信号的时序图如下图所示,由于SDA是被读量,所以只在SCL数据线上划分点。
- 目的:IIC的设计是当SDA和SCL总线上连接有上拉电阻(电平被拉高)时,实际上是不走数据的,也就是释放数据线。在从设备收到完整的一个字节(8位)之后,就主动去拉低SDA总线上的电平,代表接收器成功地接收了该字节。
- 响应形式:
- 应答信号为低电平时,规定为有效应答位(ACK,Acknowledgement Character,简称应答位),表示接收器已经成功地接收了该字节;
- 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
- o点:
- SCL置低电平
- SDA:在主控设备发送完成一个字节数据后,就立马发送一个高电平,代表释放SDA数据线
- o—>a:
- SCL延时
- a点:
- SCL从低电平转折为高电平,代表释放SCL数据线
- a—>b:
- SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
- SDA被读
- SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
- b点:
- SCL从高电平转折为低电平
- b—>c:
- SCL延时
- 实际上我们不是很关心ACK函数,只有在后面显示出问题了,比如说显示乱码了才会去调用这个函数进行判断。
void IIC_ACK() { char flag; // o点: // SCL置低电平 sda = 1; // SDA:在主控设备发送完成一个字节数据后,就立马发送一个高电平,代表释放SDA数据线 // o—>a: _nop_(); // SCL延时 // a点: scl = 1; // SCL从低电平转折为高电平,代表释放SCL数据线 // a—>b: _nop_(); // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态 flag = sda; // SDA被读 _nop_(); // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态 // b点: scl = 0; // SCL从高电平转折为低电平 // b—>c: _nop_(); // SCL延时 return flag; }
3.IIC协议通讯时序分析2:数据发送
1、回顾串口和LCD的数据发送:
- 在学习串口的时候,我们不去关心数据的发送需要根据数据发送时序图写什么代码,只需要往SBUF里面传输数据就可以了,这是因为单片机上有对应的寄存器硬件;
- 在学习LCD1602的时候,我们也不去关心数据的发送需要根据写操作时序图写什么代码,只需要往单片机的P0这个I/O口组(和LCD的DB0~DB7相连)里面传输数据就可以了,这也是因为有硬件。
- 二者相同点:数据传输伴随着一个脉冲信号。
- 串口发送数据时,脉冲信号由串行移位寄存器SHIFT提供:
- SHIFT为低电平时进行一位数据的发送;
- SHIFT由低变高(上升沿)代表发送结束;
- SHIFT为高电平时允许数据翻转;
- SHIFT由高变低(下降沿)代表开始发送下一位数据。
- LCD发送数据时,脉冲信号由使能引脚E提供:
- 在E由低变高(上升沿)之前就开始发送一字节数据;
- 一字节数据发送完毕之后E由高变低(下降沿)。
- 串口发送数据时,脉冲信号由串行移位寄存器SHIFT提供:
2、IIC数据发送的时序:
- 与起始/终止信号的区别:IIC在进行数据发送的时候,需要注意:
- 在SCL为高电平时,允许SDA上进行一位数据的传输,期间不允许数据的翻转;
- 在SCL为低电平时,允许数据的翻转。
- 如果在SCL为高电平时出现了SDA上数据的翻转,那么IIC接收器会误以为这是起始信号或终止信号。所以每次SDA上进行数据翻转前都要保证SCL处于低电平。
- 时序逻辑:
- o点:
- SCL置低电平:允许数据翻转
- SDA发送1bit数据
- o—>a:
- SCL延时5微秒
- SDA正在发送数据
- a点:
- SCL从低电平转折为高电平
- a—>b:
- SCL延时5微秒
- SDA正在发送数据
- b点:
- SCL从高电平转折为低电平:为下一次数据翻转做准备
- b—>c:
- SCL延时5微秒
- o点:
3、IIC协议发送一个字节数据:
- 编程逻辑:我们用字符型变量cdata来保存8bit的待发数据。
- cdata从哪里来?答:函数传递。
- 怎么往SDA线上发1个字节?答:将cdata的每一bit依次传输给sda,共传输8次。
- 前提:基于SDA总线数据传输时高位先出的原则。不同的数据传输方法对应不同的读取方式。
- 方法:不同于串口的数据位是低位在先,IIC的数据位是高位在先。根据高位先出的原则,每次从SDA发送出去的都是字符型变量cdata的最高位。
- 让cdata位与上二进制数1000 0000,即十六进制的0x80(按位运算):这是取位技巧,将取到的最高位结果发送给SDA。
- 传输完1bit后让cdata进行左移1位(移位运算):剔除cdata的最高位。
-
void IIC_Send_Byte(char cdata) { int i; for(i=0; i<8; i++){ // o点: scl = 0; // SCL置低电平:允许数据翻转 sda = cdata & 0x80; // SDA发送1bit数据 // o—>a: _nop_(); // SCL延时5微秒 // SDA正在发送数据 // a点: scl = 1; // SCL从低电平转折为高电平 // a—>b: _nop_(); // SCL延时5微秒 // SDA正在发送数据 // b点: scl = 0; // SCL从高电平转折为低电平:为下一次数据翻转做准备 // b—>c: _nop_(); // SCL延时5微秒 cdata = cdata << 1; } }