前言
++文章合集:++
一、I2C 设备间接线介绍
经常见到的应用场景都会如下图所示那样:会有一个主设备(图中的 MCU)、N 个从设备(图中的 I2C 设备 x)。

细节:
- 很明显,支持 I2C 的设备很多,不止 MCU 内部内置了 I2C 接口,其他设备内部也会内置 I2C 接口或者 I2C 的等效电路,从而让 MCU 和设备传感器等之间进行通信
- 上拉电源和上拉电阻,严格意义上与 I2C 无关,属于 MCU 或者 I2C 设备外部,因为 I2C 原理的需要,由硬件工程师走线部署
二、开漏输出

将上图中的MCU展开:

而 I2C 规定:
- 连接到 SDA 和 SCL 线上的所有设备(主设备 Master 和从设备 Slave )的输出级都必须采用开漏结构。
来源:《UM10204》(恩智浦半导体,原飞利浦半导体)发布的《I²C 总线规范和用户手册》的官方文档编号)-3.1.1 SDA and SCL signals 章节
The output stages of devices connected to the bus must have an open‑drain or open‑collector to perform the wired‑AND function
连接到总线上的设备的输出级必须采用开漏(Open‑Drain)或开集(Open‑Collector)结构,以实现线与(Wired‑AND)功能。
对于 STM32 而言,其实相当于下图中红色 X 掉的 P‑MOS 被永久关闭,就是 P‑MOS 不用了,物理上还存在,但是可以理解为 P‑MOS 被永远移除
GPIO 中 I/O 端口位结构图

进一步简化 I/O 端口位结构图:

最终简化 I/O 端口位结构图:

注意:
- 后续内容中,I2C 是支持设置各个设备的主从模式的,而为了方便表述,我们以 MCU+I2C 视角进行收发,让 MCU 主动发起通信,此时,MCU 叫做主设备,即 Master,其他被动通信设备我们叫做从设备,即 Slave
细节:
- 开漏输出,只能输出低电平 (0:N‑MOS 导通接地) 和高阻抗 (N‑MOS 断开),无法输出高电平 (1)
- 为了让 I2C 总线结构能够输出高电平,就需要上拉 VDD 和上拉电阻
有了上述理解,我们再把 I2C 通信拓扑结构再进一步简化一下

当所有 I2C 设备都以开漏输出的形式接入到 SCL 和 SDA 线,而开漏输出默认是断开的,那么在默认情况下,所有设备都默认是断开状态,同时 SCL 和 SDA 线会在上拉电阻的拉动下,都默认处于高电平,表示空闲状态。
- 开漏输出默认是断开:STM32CubeMX 甚至都不让你设置 I2C 配套的 GPIO 初始电平,就是因为默认是断开的

三、SDA 输出数据
下面以 MCU 的 I2C 片上外设的视角,即 Master 视角,只讨论在 SDA 线是如何输出高低电平,即 1 和 0 的,暂时其他所有内容都不考虑,讨论完 SDA 线,SCL 原理自然就懂。
很明显,I2C SDA pin 的 IO 端口位必须是开漏输出,只能输出高阻抗 (断开) 和低电平 (导通),无法主动输出高电平,高电平由外部上拉电阻提供。输出 1 或者高电平
- 如果我们想让 SDA pin 输出高电平,只要保持 N‑MOS 断开,那么 SDA 线默认就会在上拉 Vdd 的拉动下,自动保持高电平

输出 0 或者低电平
- 如果我们想让 SDA pin 输出低电平,只要保持 N‑MOS 闭合,那么 SDA 线就会接地,输出低电平。

细节:
- 经过上面的过程分析,我们会发现 I2C 发送 0/1 的原理其实很简单,精髓就在于上拉 Vdd 和开漏模式上
- I2C 输出 1,N‑MOS 断开,SDA 线上就是高电平;I2C 输出 0,N‑MOS 闭合,SDA 线上就是低电平
- 所以:I2C 如果有节奏的持续输出 1/0,对应的 SDA 线上就会持续体现出高、低电平。而其他从设备,自己也被挂接在 SDA 线上,也就能读取 SDA 线上的高低电平。
四、SDA 获取数据
还是只看 SDA,只不过这次看输入部分,并且观察 I2C 设备 1(即 Slave)接收,其实很简单,假设,主设备已经向 SDA 线上写入了高低电平。

I2C 接收方,是能直接感知到 SDA 线上的高低电平的
结论:对于 I2C 设备 1 来讲,从 SDA 数据线接入到自身 I2C 内部,在理解上等价于导线直接接入
I2C 未来对 SDA 信号线上的信号直接进行采样(在采样前可能有一级施密特触发器对波形进行了整形)
I2C 未来来了 SDA,当 I2C 切换为输入模式采样时,需要将 N‑MOS 管设置为断开模式,也就是所谓的释放总线。当 I2C 切换为输入模式时,需要将 N‑MOS 管设置为断开模式
所以,未来 SDA 线上的信号变化,接收方 I2C 能直接感知到,如果需要的话
五、SCL 时钟输出与输入
时钟的本质,也是高低电平
因为时钟的输入输出都和 SCL 时钟线有关,而各个主从设备输出或者接收时钟,原理和 SDA 输出输入数据完全一样,所以只要搞定 SDA 输出输入,那么 SCL 自然就懂了
✨注意在 I2C 通信中,一般由主设备主动产生时钟信息,而且只能有主设备主动产生时钟信息
六、时钟与数据
有了对 I2C 产生数据和时钟的原理的粗浅认识,下面我们就要进行更为准确的认识。
I2C 规定:
- 在 SCL 时钟线为高的时候,SDA 数据线上的数据,必须稳定不变
- 在 SCL 时钟线为低的时候,SDA 数据线上的数据才允许变化 (从高到低,或者从低到高,当然不变也行)

细节
- 为了实现上述时序图效果,I2C 模块设计者必须要让自己设计的硬件做到,SCL 高电平时间要 "窄" 于 SDA 数据保持时间。SDA 的数据保持时间必须覆盖住 SCL 高电平的采样时刻,并且留有余量
- 在发送方角度,只有当自己产生的 SCL 是低电平了,自己才能改变 SDA 的数据
- 我们之前介绍了 SDA 和 SCL 线上,能在 Master 的控制下,产生高低电平,那么可以通过控制 SDA 和 SCL 的高低电平的保持时长,从而形成不同样式的时序结构,这点也是可理解的。
- 所谓的变化,本质就是允许 Master 发送数据。变化不一定必须更改,从 0 到 0,也是变化。
关于接收方采集:
- 只要在 SDA 稳定期间,原则上都是可以进行数据采集的,I2C 常见的采集方式两种:1. 上升沿采集 2. 高电平采集【不考虑下降沿采集】
- 原则上,在 SDA 稳定期间,接收方都可以进行采集,也一定能稳定获取对应的数据

但是在翻阅了大量的资料,没有明确找到到底是什么采集方式,我们这里为了方便讲解,统一成为高电平采集。
时钟和数据的关系
当我们 I2C 发送数据的时候,同时也有时钟也要产生,那么时钟和数据的关系是:

极端情况
- SDA 持续输出高电平,SCL 多次高低脉冲进行采样
- SDA 持续输出低电平,SCL 多次高低脉冲进行采样


结论
- SDA 上的电信号被识别成为数据,是 SCL 的高电平决定的
至此,我们已经能够初步理解,I2C 是如何使用 SDA 和 SCL 发送数据和接收数据的:
- 主机的 SDA 和 SCL,通过开漏输出,断开和闭合 N‑MOS,通过上拉电阻和导通接地,对外输出高低电平
- 从机通过 SDA 和 SCL 的复用输入,感知到外部电平变化,获取时钟和数据信号
- 通过 SCL 高电平采集,就可以从 SDA 线上获取指定的数据
七、"线与"(wired‑AND)
刚刚我们只站在通信角度,来看待 I2C 的,下面我们换一个角度,来看待 I2C,在一个 I2C 通信拓扑结构中:
只要有任何一个设备将自己的 N‑MOS 管导通接地,SCL/SDA 总线就会被拉低,整条 SCL/SDA 总线就被强制设置为低电平。一票否决
只有所有设备都不拉低总线(都处于高阻态,即 N‑MOS 管断开),才能通过上拉电阻将总线拉高。
结论:对 SDA 来讲,多个 I2C 设备,无论是谁,大家都输出高电平,但只要有一个导通 N‑MOS,输出低电平,那么 SDA 线上就是低电平,所有设备未来感知到 SDA 上的数据就都是 0
SCL 同理,不再演示
该特性类似于,有 int a,b,c,d 四个变量,每个变量只能是 0 或者 1,那么计算
result = a & b & c & d的结果时,只要有至少一个变量是 0,结果就是 0。只有大家都是 1,result = 1 & 1 & 1 & 1结果才是 1。而 I2C 对于 SCL 和 SDA 线,使用开漏模式在硬件上实现了若干个针脚输出的逻辑与功能,我们把这种特性叫做 "线与"(Wired‑AND)
不要纠结是 & 还是 &&,在二进制角度,他们的真值表现是一致的。
八、为什么需要线与特性呢?
时钟同步 (Clock Synchronization):
主设备给从设备发送数据,时钟高电平时,从设备采集 SDA 线上数据,进而让从设备获得有效数据。但是从设备需要更多时间来处理历史数据,进而导致当前数据无法及时处理,从设备便把 SCL 线拉低, 因为线与特性,SCL 必然为低电平,即便主设备想拉高也不行。
主设备检测到 SCL 为低后,只能暂停产生时钟,直到从设备释放 SCL,SCL 自动被拉高之后,主设备再恢复通信过程。这种特性叫做 I2C 时钟拉伸,拉伸一般是接收方拉伸,从而实现主从设备通信的同步的过程。
I2C 时钟拉伸(Clock Stretching)是 I2C 协议中一种从设备 "强制" 主设备等待的机制,不过不用过于担心,这个过程是通信双方硬件自动完成的。
总线仲裁( Arbitration ):
当有多个主设备同时竞争总线控制权时,它们都在发送数据(通常是地址)。如果某个主设备发送 1(释放总线),但检测到总线实际是 0(被其他主设备拉低),它就知道自己输掉了仲裁,会立即停止发送并转为从设备角色,发送 0 的主设备赢得仲裁,仲裁过程不会破坏数据。 谁先拉 SDA 线,谁仲裁胜利,因为失败的会退出。