2.30 I2C通信

I2C简介

通过学习I2C通信协议,会得到什么?

通过通信线,实现单片机读写外挂模块寄存器的功能,要实现在指定的位置写寄存器和在指定的位置读寄存器,实现的读写寄存器,就实现了对外挂模块的完全控制。

I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线。

两根通信线:SCL(Serial Clock)串行时钟线、SDA(Serial Data)串行数据线。同步,半双工。带数据应答。

支持总线挂载多设备(一主多从、多主多从)。

一主多从:单片机作为主机,主导I2C总线的运行,挂载在I2C总线的所有外部模块都是从机。从机只有被主机点名之后才能控制I2C总线,不能在未经允许的情况下去碰I2C总线,防止冲突。

多主多从:在总线上任何一个模块都可主动跳出来当主机。较为复杂,暂时不去了解。

I2C规定

作为一个通讯协议,I2C必须要在硬件与软件上都作出规定,硬件上的规定就是你的电路应该如何连接;软件上的规定,就是时序要怎么定义,字节要怎么传输,是高位先行还是低位先行。

硬件规定

单片机(CPU)作为总线的主机,主机的权限很大,包括对SCL线的完全控制,任何时候都是主机完全掌控SCL线,另外在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答的时候,主机才会转交SDA的控制权给从机。

对于从机,从机只能被动的读取SCL时钟线,而对于SDA数据线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令后,或者从机应答的时候,从机才能短暂的取得SDA的控制权。

接线要求 : 所有I2C设备的SCL连在一起,SDA连在一起。

设备的SCL和SDA均要配置成开漏输出模式(开漏模式下,输出高电平就是断开引脚,输出低电平就是强下拉) ,SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右。这样完全杜绝了电源短路的现象,保证了电路的安全。具体可以看视频部分的20分钟左右内容,属于是高光时刻。

I2C会出现一个“线与”的现象,就是只要有一个设备输出低电平,那么总线就会处于低电平,只有所有的设备都输出高电平,总线才处于高电平。可以利用这个特征,可以执行多主机模式下的时钟同步和总线仲裁。

问题:为什么会短路?和LED有什么不同?

在STM32中,有关I2C的部分:“设备的SCL和SDA均要配置成开漏输出模式(开漏模式下,输出高电平就是断开引脚,输出低电平就是强下拉) ,SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右。这样完全杜绝了电源短路的现象,保证了电路的安全。”这段话中,我有一处不明白,就是,当SCL和SDA如果都是推挽输出的情况下,可能会造成短路,这里的短路的意思是指如果主机的SDA是高电平,从机的SDA是低电平,会导致电源短路,我不太理解为什么会是电源短路?因为点亮LED灯不就是一个高电平一个低电平吗?这里的短路是指什么意思?

 

在I2C通信中,使用开漏(或开漏集电极)输出方式是非常关键的,这与推挽输出方式有本质的区别,尤其是在处理多主机或多从机的总线通信时。这种设计选择与LED点亮的简单电路有很大不同,主要涉及到电路的驱动方式和可能的冲突解决方法。

### 开漏与推挽输出的区别

1. **推挽输出**:
– 在推挽配置中,输出端可以被驱动到高电平(连接到电源Vcc)或低电平(连接到地GND)。
– 当两个设备的推挽输出连接到同一条线(比如I2C的SDA线)时,如果一个设备试图将线拉高(至Vcc),而另一个设备将线拉低(至GND),结果就是这两个电源之间形成了直接的电连接,这就是所谓的“电源短路”。

2. **开漏输出**:
– 在开漏配置中,输出端只能被拉低(连接到地GND),不能主动拉高至电源Vcc。要使线路达到高电平状态,必须依赖外部的上拉电阻。
– 这意味着如果没有任何设备拉低线路,线路自然通过上拉电阻达到高电平状态。如果任何一个设备拉低线路,则线路变为低电平。

### 为什么推挽输出会导致短路?

在I2C通信中,SDA和SCL线路通常被多个设备共享。如果使用推挽输出,设备A尝试将SDA线拉高,而设备B同时尝试将其拉低,就会在设备A的输出端(连接至Vcc)和设备B的输出端(连接至GND)之间形成一个直接的连接。这种情况下,电流将直接从Vcc通过这两个设备的输出端流向GND,造成电源短路,可能会损坏设备,并导致电流过大。

### 为什么LED灯的情况不同?

点亮LED灯时,通常是单一路径的简单电路,电流从电源流经限流电阻和LED,然后回到GND。此过程中,只有一个控制点(通常是一个GPIO引脚),不存在多个控制点尝试对同一电路施加不同电位的情形。因此,不存在推挽输出可能导致的短路风险。

### 总结

因此,在设计使用I2C等共享总线的系统时,选择开漏输出是为了避免这种由于多个设备同时尝试控制总线状态而产生冲突的风险。开漏输出,配合上拉电阻,允许任何一个设备能安全地将线路拉低,而在没有设备拉低线路时,通过上拉电阻自然而然地回到高电平状态,从而确保系统的稳定性和设备的安全。

软件规定(时序)

起始条件:SCL高电平期间,SDA从高电平切换到低电平。

I2C总线处于空闲状态时,SCL和SDA都处于高电平,当主机把SDA下拉,从机捕获到SCL处于高电平,SDA处于下降沿的信号时,从机就会把自身复位,等待主机的召唤。

在SDA下降沿之后,主机要再把SCL拽下来,一方面是要占用这个主线,另一方面也是为了更方便我们这些基本单元的拼接,即保证除了起始和终止条件外,每个时序单元的SCL都是以低电平开始,低电平结束。

终止条件:SCL高电平期间,SDA从低电平切换到高电平。

起始条件和终止条件都是主机产生的,从机不允许产生。

主机发送一个字节的时序单元:

主低放,从高取。

SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。

要注意一下是高位先行,所以第一位是一个字节的最高位,第八位是一个字节的最低位。

这里主机就比较悠闲,掌控着时钟线可以慢慢悠悠的放数据,而从机就比较抓马了,必须尽快读取数据,因为主机可不等从机哟。

主机接收一个字节的时序单元:

从低放,高主取。

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,释放SDA就相当于切换成输入模式)。

所有设备包括主机都处于输入模式,当主机需要发送的时候,就可以主动拉低SDA;而主机在被动接收的时候,就必须先释放SDA,不要去动SDA,以免影响别人发送,因为总线是线与的特征,任何设备拉低,总线就是低电平。

应答机制的时序单元:

发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。

接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。

简单理解就是,我们在调用发送一个字节之后,就要接跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据。如果从机收到了,那在应该为这里,在主机释放SDA的时候,从机就应该立刻把SDA拉下来,然后再SCL高电平期间,主机读取应答位,如果应答位为0,就说明从机确实收到了。

I2C完整时序:

从机设备地址,在I2C协议标准里分为7位地址和10位地址,我们目前只讲7位地址的模式,因为7位地址比较简单而且应用范围最广。

在每个I2C设备出厂时,厂商都会为它分配一个7位地址,这个地址可以在具体的芯片手册中找到。会出现一个问题,相同型号的芯片一般地址是一样的,要怎么避免?这就需要用到地址中的可变部分了,一般芯片器件的最后几位可以在电路中改变的。

1.指定地址写

对于指定设备(Slave Address),在指定地址(Reg Address)下(从机设备的寄存器地址),写入指定数据(Data)。

在产生起始条件后,必须是发送一个字节的时序,字节的内容,必须是从机地址+读写位。正好从机地址是7位,读写位是1位,正好1个字节。

发送从机地址,就是确定通信的对象,发送读写位,就是确认接下来是要写入还是要读出。

读写位置1,代表主机要进行读出的操作。

读写位置0,代表主机要进行写入的操作。

接下来紧跟着的单元,就是接受从机的应答位,在这个时刻,主机要释放SDA,应答位结束之后,从机释放SDA。

第二字节,就可以送到指定设备的内部了,从机设备可以自己定义第二个字节和后续字节的用途。

如果主机不再需要继续传输,就可以产生停止条件,在停止条件之前,先拉低SDA,为后续SDA的上升沿做准备,然后释放SCL,再释放SDA,这样就产生了SCL高电平期间,SDA的上升沿。

这样一个完整的数据帧就拼接完成了。

2.当前地址读

对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)。

如果主机想要读取从机的数据,就可以执行这个时序。

我们可以看到,主机并没有指定寄存器的地址,从机就发送数据过来了,那么从机是发送的哪个寄存器的数据呢?

这就需要用到“当前地址指针”了,再从机中,所有的寄存器都被分配到了一个线性的区域中,并且会有一个单独的指针变量,指示着其中一个寄存器,这个指针上电默认,一般指向0地址,并且每写入一个字节和读出一个字节后,这个指针就会自动自增一次,移动到下一个位置,那么在调用当前地址读的时序时,主机没有指定要读哪个地址,从机就会返回当前指针指向的寄存器的地址。

由于当前地址读并不能指定读的地址,所以这个时序用的很少。

3.指定地址读

对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)。

第一步:

第一个字节:寻找指定从机设备。

第二步:

第二个字节:用来指定地址,这个数据就写入到了从机的地址指针里了。

第三步:

再来个起始条件。(Sr就是重复起始条件,相当于另启一个时序),因为读写位只能是跟着起始条件的第一个字节,所以想要切换读写方向,必须要另启一个起始条件。

第四步:

第一个字节:寻找指定从机设备。

第五步:

主机接收一个字节。

第六步:

主机不应答,就是主机不把SDA拉低,从机读到SDA为1,就代表主机没有应答。从机收到非应答之后,就知道主机不想继续了,从机就会释放总线,把SDA控制权交还给主机。