系列:

总线空闲状态

i2cSDASCL都为高电平的情况下,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

image-20250413212941644.png

启动与停止信号

2011071000524620.jpg

  • 启动信号:SCL为高电平的时候,SDA由高电平向低电平跳变。

  • 停止信号:SCL为高电平的时候,SDA由低电平向高电平跳变。

启动信号是由主控器主动建立的,在建立该信号之前I2C总线必须处于空闲状态。停止信号也是由主控器主动建立的,建立该信号之后,I2C总线将返回空闲状态。

数据位发送

进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。

2011071000553274.jpg

应答信号

I2C总线上的所有数据都是以字节传送的,发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。

  • 应答信号为低电平时:规定为有效应答位(ACK),表示接收器已经成功地接收了该字节;

  • 应答信号为高电平时:规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。

时钟同步

产生的同步SCL时钟的低电平周期**由低电平时钟周期最长的器件决定**,而SCL时钟的高电平时钟周期**由高电平时钟周期最短的器件决定**

SCL同步是由于总线具有“线与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。当所有的节点都发送高电平时,总线才能表现为高电平。正是由于“线与”逻辑功能的原理,当多个节点同时发送时钟信号时,在总线上表现的是统一的时钟信号,这就是SCL的同步原理。

如果接收器希望主控器降低数据的传送速度,可以通过将SCL线主动拉低延长其低电平时间的方法来通知主控器,当主控器在准备下一次传送发现SCL线的电平被拉低时就进行等待,直到被控器完成操作并释放SCL线的控制权。这样一来,主控器实际上受到接收器的时钟同步控制。可见,SCL线上的低电平是由时钟低电平最长的器件决定;高电平的时间由高电平时间最短的器件决定。这就是时钟同步,它解决了I2C总线的速度同步。

仲裁机制

I2C仲裁(Arbitration) 是指当多个主设备(master) 试图同时控制 I2C 总线时,用来决定哪个主设备获得总线控制权的过程。

为什么多个主设备会连接到一条总线

大多数情况下我们都是一个主设备(比如 SoC 或 MCU)+ 多个从设备(sensor、EEPROM、触摸屏等等),这已经能满足大多数应用了。

但在一些特殊的应用场景中,多主设计是“必须的”或者“非常方便的”

例如在嵌入式系统中:

主设备 A 主设备 B 从设备
主 MCU 辅助 MCU(低功耗协处理器) 一堆外设(EEPROM、sensor)
  • 主 MCU 是主系统,平常处理复杂业务逻辑。
  • 辅助 MCU 可能在主 MCU 掉电或休眠时接管 I²C,比如做环境监控、数据采集。
  • 两者都要访问 I²C 外设,所以就需要共享总线,各自当主设备。

如何实现的仲裁

举个例子:

Master A 发送:1010...

Master B 发送:1000...

两者在前 2 个时钟发送一致,都认为“我赢了”

到第 3 位:

  • A 发送的是 1(松手)
  • B 发送的是 0(拉低线)
  • A 读取 SDA 发现是 0(不是我预期的),立刻认输!

规则总结:

  • 只要 SDA 实际电平和我发出的不一致,我就输
  • 输了的主设备必须停止发送,等待下一次机会

这样可以保证,赢的控制器不需要重新开始传,提高了总线带宽。

为什么是谁拉低谁赢?

I2C 总线是 开放式总线

谁拉低,线就是低的。
谁放手,线保持原状态(如果别人拉低就是低)。

只有发送的是 1 的主设备才有可能检测到冲突

发送

因为它自己就拉低 SDA,总线电平一定是它想要的 0

这也就是我们上面提到的线与机制。

设备挂死

SCL挂死

一般是MCU作为从机的软件bug,这里不作探讨。

SDA挂死

I2C的线与机制导致只要任何一方把SDA设置为了低电平,那么在其非主动释放期间,会一直保持为低电平,如果此时系统复位或其他错误,就会导致SDA挂死。

I2C从机在以下情况会拉低SDA线:

  • 主机向从机写数据或地址时,从机如果发出ACK应答,则会第9个CLK的期间拉低SDA
  • 主机读数据的时候,从机会在bit为0时对应的CLK期间拉低SDA

如果在以上期间,发生了系统复位或错误,就会导致SDA挂死。

要想办法恢复,我们先得知道从机什么时候会释放SDA。由于刚刚的SCL下降沿没有给出来,恢复总线要做的第一件事情就是在想办法用GPIO在SCL线上模拟一个下降沿,让从机状态机继续走下去。只发一个下降沿并不一定能将SDA释放,因为我们并不清楚当主机复位异常发生时刻从机到底处于图中哪一个状态,所以需要逐个CLK去探测,直到见到SDA被释放了,我们才终止并且发送STOP条件告诉从机这次坑爹的通讯结束了(一般是模拟9个连续的CLK)。

大概传输流程

  • 主设备发起启动条件。

  • 主设备发送从设备地址。

  • 从设备确认地址(ACK)。

  • 主设备发送或接收数据(每字节后有 ACK/NACK)。

  • 主设备发出停止条件,结束通信。

Ref

I2C从机挂死分析和解决方法
I2C详解(二)
linux i2c 驱动框架分析
linux i2c 仲裁原理,I2C的基本原理和linux中I2C架构的实现