1、为什么采用漏极开路?
首先,连接到 I2C 上的设备是开漏输出的。以漏极开漏输出(OD)为例,是指将输出级电路结构改为一个漏极开路输出的 MOS 管。这样做的好处在于:
- 防止短路。
- 可以实现“线与”逻辑,可以减少一个与门的使用,简化电路。
结论:I2C支持多个主设备与多个从设备连接在同一根总线上,如果不开漏输出,会出现短路现象。
2、为什么要加上拉电阻?
但是,采用开漏输出时,如果没有上拉电阻的存在,只能输出低电位,无法输出高点位。
结论:连接上拉电阻后,才可以输出高电位。
在验证 I2C 接口时,我们同样要对上拉电阻做处理。如下:以从端驱动 SDA 和 SCL 为例
从端对SDA/SCL线的驱动:
assign SCL = scl_slave === 0 ? 1'b0 : 1'bz ;
assign SDA = sda_slave === 0 ? 1'b0 : 1'bz ;
也就是说:
- 当从端驱动 SDA/SCL 为低(0)时,从端会直接给 SDA/SCL 一个0值;
- 如果从端驱动 SDA/SCL 不为低(0)时,从端会给 SDA/SCL 一个高阻态值。
为什么是高阻态?而不是直接给高电位(1)呢?
因为高阻态是一个无效驱动,而高电位(1)是一个有效驱动,如果置为1会跟其他的驱动源造成多驱动冲突。为了避免多重驱动的问题,当 Slave 或者 Master 不打算驱动任何一根线时(SDA/SCL),那必须把 SDA/SCL 给一个高阻态(z)值。
经过上拉处理:
如果在整个总线上,master 和 slave 都没有给 SDA/SCL 一个有效驱动(即低电位),会通过上拉电阻将 SCL/SDA置为高电位(1)。
注意:这个给的高电位(1)是一个弱信号,可以防止形成多驱动源造成冲突。
assign (week0,week1) SCL = enable_pullup_registor ? 1: 0;
assign (week0,week1) SDA = enable_pullup_registor ? 1: 0;
举个例子:
I2C支持多主设备与多个从设备,下面 master1 给 slave1 发送一个从地址(0110 011),master2 给 slave2 发送一个从地址(0110 001),
解释上图中的例子:
- 当 master1 发送最后一个R/W位 = 0 时,此时,会直接将SDA线拉低,slave1 可以看到SDA线为低。
- 当 master2 发送最后一个R/W位 = 1 时,此时,master2 会给 SDA一个高阻值(Z),表明 master1 不对总线SDA做驱动,此时上拉电阻会将SDA信号线置为1(弱信号),slave2 看到的是SDA为高。
3、为什么要线与?
现在,我们已经知道了 I2C 为什么要漏极开路和添加上拉电阻的目的了,那么又为什么要线与?I2C 支持多个主设备与多个从设备连接在同一根总线上,如果多个设备同时占用总线,怎么判断谁先占用总线呢?所以就需要一种仲裁机制。I2C 没有 Arbiter 直接的来处理仲裁,而是通过线与的逻辑实现仲裁。
仲裁过程:当主设备A准备占用I2C时,需要在SCL为高时,将SDA拉高,再拉低,满足一个启动条件。当主设备A将SDA拉高后,需要检查SDA的电平:
- 如果此时SDA电平为高,说明主设备可以占用总线,然后主设备A会将SDA拉低,一次满足启动条件,开始传输;
- 如果此时SDA电平为低,说明总线已经被其他设备占用,主设备A会退出。
为什么SDA为低,就是被其他设备占用了呢?
因为线与逻辑的存在。只有总线上有其他的设备将SDA置为0,线与后,SDA线的电平为0。主设备A检查SDA线的电平时,会发现为低电平。所以仲裁时,哪个设备更早地将SDA线拉低,谁就抢占了优先权。
4、总结全文
- 漏极开路是为了防止I2C上连接多个主设备与多个从设备时出现短路;
- 上拉电阻的存在是为了在使用漏极开路之后,可以输出高电平;
- “线与”是为了在多个主设备同时占用总线时,具有仲裁的能力。