I2C 笔记

Lobsters Hottest 工具

摘要

对 I2C 串行通信协议的详细技术解释,涵盖其拓扑结构、开漏架构以及使用线与逻辑的总线仲裁。

<p><a href="https://lobste.rs/s/akyfrt/notes_on_i2c">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/20 20:31

# Emaan Rana 来源:https://rana-emaan.com/read/notes/i2c/ > 我在7天内重写了三次,并且在这些图表上花了太多时间,希望这次能达到标准。 ## I2C - 代表 Inter-Integrated Circuit(集成电路间总线),缩写为 I2C 或 I²C。 - 它是一种同步、双线制的串行通信总线,用于短距离连接低速外设。 ## SDA - 代表 `Serial DAta`(串行数据)。 - 在设备之间双向传输数据。 ## SCL - 代表 `Serial CLock`(串行时钟)。 - 承载时钟信号,用于同步所有数据传输。 ## 拓扑结构 - 总线上至少有一个控制器和一个目标。 - 常见情况是单个控制器和一个或多个目标。 - 不常见的情况下,可以有多个控制器(多控制器)。 - 目标可以连接到线路上的任意点。 - 每条线都连接到各自的上拉电阻,这些电阻连接到相同的正电压。 ## 开漏 - 在 I2C 中,`SDA` 和 `SCL` 空闲时为 `HIGH`(高电平),即默认状态为高电平。因此,I2C 被认为是一个“开漏”系统。 - 当漏极闭合时,`SDA` 和 `SCL` 被拉至 `LOW`(低电平,接地)。 - 当漏极断开时,`SDA` 和 `SCL` 返回 `HIGH`(高电平,连接到正电压)。 ### 总线仲裁 - I2C 可以在同一总线上支持多个控制器。解决冲突的方式自然源于开漏设计。 #### 线与逻辑 - 在开漏总线上,驱动 `LOW` 是一个主动动作(将线路拉至地),而驱动 `HIGH` 是一个被动释放(让上拉电阻将线路恢复到正电压)。 - 如果一个设备拉 `LOW`,而另一个设备释放为 `HIGH`,则线路变为 `LOW`。也就是说: - **拉低总是覆盖释放**。 - 因此,总线在逻辑上等同于将所有设备的输出进行“与”运算。这个特性称为“线与逻辑”,它是实现仲裁的电气基础。 #### 解决冲突 - 每个发送中的控制器都必须在发送时监视 `SDA`。 - 在每个位上,控制器比较它试图发送的内容与 `SDA` 线上实际出现的内容。 - 如果匹配,则无冲突,控制器继续发送。 - 如果控制器试图发送 `HIGH` 但读回的是 `LOW`,则说明另一个控制器正在拉低,该控制器失去仲裁并停止驱动总线,等待下一个“停止条件”,然后再次尝试申请总线。 - 获胜的控制器永远不会知道发生了冲突。它在线路上看到自己的位,并继续其未受损坏的交易。 - 总之,开漏使仲裁成为电气设计的被动结果,而非主动的协议特性。 > 两个控制器都可以通过在“起始条件”期间将 `SDA` 拉低来成功申请总线。冲突在紧随其后的地址传输期间解决:当两个控制器逐位输出它们的目标地址时,它们第一个不同的位就是仲裁的决胜点: > - 试图发送 `HIGH` 而另一个发送 `LOW` 的控制器检测到不匹配并退出。 > - 获胜的控制器继续传输,甚至不知道发生了仲裁。 > 如果两个控制器在整个交易过程中发送了**相同**的位呢? > - 两者都不会检测到冲突! > - 两个控制器都看到总线与它们发送的每一位相匹配,都认为它们拥有总线,并且都成功完成了交易! > - 规范实际上指出了这一点: >> 如果发送内容相同,两个控制器实际上可以完成整个交易而不会出错。 > - 这不是一个缺陷,而是设计的故意特性。目标仍然收到一个连贯的交易,两个控制器驱动总线的事实是不可见的且无害的,因为它们发送给目标的信号是相同的。 > - 出于其他原因(我不在此赘述),规范建议多控制器系统应设计其软件协议,以尽可能避免相同传输的场景,或者接受相同交易是无害的重复。 随着我们稍后探索这些概念,这一点会变得更加清晰。如果现在还不明白,请稍后再回头重读这一部分! ## 上拉 - 拉**低**一条线通常比拉**高**一条线快得多。 - 上拉时间是总线电容和上拉电阻值的函数。 - 这些电阻的典型值为 1-10 kΩ。上拉电阻是一种折衷: - 高电阻会增加上拉线路所需的时间,从而限制最大总线速度。 - 较低的电阻可实现更快的通信,但需要更高的功耗。 ## 速度 | 模式 | 速度 | |------|------| | 标准模式 | 100 kbps | | 快速模式 | 400 kbps | | 快速+模式 | 1 Mbps | | 高速模式 | 3.4 Mbps | | 超快模式 | 5 Mbps | - I2C 可以以不同的总线速度运行,通常称为模式。 - 硬件被指定为符合其理论上能达到的最高模式。 - 超快模式是只写的,并对协议进行了一些修改。 - 高速模式设备向后兼容较低速度。 ## 角色 - 在 I2C 中,有些角色不会改变,我称之为“固定角色”。 - 固定角色描述了谁控制总线。 - 还有一些角色会根据交易类型(读/写)**发生变化**,我称之为“交易角色”。 - 交易角色描述了在交易过程中谁在说话,谁在监听。 - 控制器始终是控制器,目标始终是目标,但它们可能是发送者或接收者,具体取决于当前交易(读/写)的方向。 ## 固定角色 - 这些角色由硬件/软件设计决定,在交易期间不会改变。 ### 控制器 - 控制器是在 I2C 交易期间“拥有”总线的设备。 - 控制交易何时开始(`START` 条件)和何时停止(`STOP` 条件)。 - 决定交易是“读”还是“写”。 - 驱动时钟线 `SCL`。 - 选择哪个目标将参与交易。 > 注意:这原本被称为“Master(主设备)”,在许多解释和库中仍如此称呼。目前正在努力将其改为“Controller(控制器)”,这也是我选择使用的术语。 ### 目标 - 目标是一种被动参与者,从不会主动发起任何操作。 - 持续监听其地址,以便参与由控制器发起的交易。 - 仅在控制器寻址时响应。 - 从不触碰时钟线 `SCL`,**除了**有时执行 [时钟拉伸](https://rana-emaan.com/read/notes/i2c/#clock-stretching)。 > 注意:这原本被称为“Slave(从设备)”,在许多解释和库中仍如此称呼。目前正在努力将其改为“Target(目标)”,这也是我选择使用的术语。 ## 交易角色 - 这些角色是控制器或目标在交易中可能承担的角色,**取决于**交易是“读”还是“写”。 ### 发送者 - 实际驱动数据线 `SDA` 并发送数据的设备。 - 发送者是: - `读`:目标 - `写`:控制器 ### 接收者 - 读取数据线 `SDA` 的设备。 - 接收者是: - `读`:控制器 - `写`:目标 ## R/W 位 - 这是控制器在交易期间设置的一个位,决定执行“读”还是“写”,进而决定交易角色(谁是发送者,谁是接收者)。 - `读`:1 - 控制器希望从目标接收数据。 - `写`:0 - 控制器希望向目标发送数据。 ### 读 - 控制器 `读`: - 发送者:目标 - 接收者:控制器 > 注意:在控制器 `读` 期间,控制器仍保持其固定角色(控制 `SCL`、发起交易),但承担了接收者的交易角色,而目标则作为发送者拥有 `SDA`。 ### 写 - 控制器 `写`: - 发送者:控制器 - 接收者:目标 ## ACK/NACK 位 - 这是交易中每个字节数据之后的“握手”位。 - 在 **数据传输** 期间,`ACK/NACK` 位**始终**由交易的接收者设置,接收者可以是控制器**或**目标。 - 在 **目标选择** 期间(在任何数据传输之前),`ACK/NACK` 位**始终**由目标设置,无论交易是“读”还是“写”,也不管目标是发送者还是接收者。 ### ACK - 代表 `ACKnowledge`(确认)。 - 拉低至 0,这在开漏系统中是主动操作。 ### NACK - 代表 `Negative ACKnowledge`(否定确认)。 - 释放为高电平 1,这在开漏系统中是被动操作或“无操作”。 #### 读 - 在“读”交易期间,控制器是接收者,并在数据传输期间设置 `ACK/NACK` 位。 - 当控制器设置 `NACK` 位时,它**不**表示错误(这种情况仅发生在“读”交易中)。 - 在这种情况下,`NACK` 告诉发送者(这里是目标)停止向控制器发送数据。 #### 写 - 在“写”交易期间,目标是接收者,并在数据传输期间设置 `ACK/NACK` 位。 - 当目标在目标选择**或**数据传输期间设置 `NACK` 位时,表示发生了错误。 ## 信号 - I2C 总线上的所有设备持续监视 `SDA` 和 `SCL`。 - 每个设备都需要知道交易何时开始和结束,以便适时参与。 ## 数据有效性 - “数据有效性”是控制数据位传输的规则。 - I2C 规范定义“数据有效性”为: > - `SDA` 线上的数据必须在时钟 `HIGH` 期间保持稳定。数据线 `SDA` 的 `HIGH` 或 `LOW` 状态只能在 `SCL` 线上时钟信号为 `LOW` 时改变。 ## 条件 - “数据有效性”规则能够为两个特殊的 `START` 和 `STOP` 条件创造信号空间。 - 这些条件故意违反“数据有效性”规则,以便明确地指示 I2C 交易的 `START` 和 `STOP`。 - `START` 和 `STOP` 条件是 `SDA` 在 `SDA` 为 `HIGH` 时发生的跳变。 - 当设备在 `SCL` 为 `HIGH` 时检测到 `SDA` 跳变,它知道这不可能是数据位,并将其解释为: - `START`(如果 `SDA` 下降) - `STOP`(如果 `SDA` 上升) ### 起始条件 - “`START` 条件”发生在 `SDA` 被拉低而 `SCL` 仍为 `HIGH` 时。 - “`START` 条件”发生后,`SCL` 被拉低,交易开始。 ### 停止条件 - “`STOP` 条件”发生在 `SDA` 被释放为 `HIGH` 且 `SCL` 也为 `HIGH` 时。 - “`STOP` 条件”发生后,交易完成,I2C 总线空闲,可供申请。 ## 时钟拉伸 - “时钟拉伸”是一种机制,允许目标设备通过将 `SCL` 保持为 `LOW` 来暂停 I2C 交易。 - 通常控制器控制时钟 `SCL`,它驱动时钟,目标响应。 - 由于两条线都是开漏的,目标可以将 `SCL` 保持为 `LOW` 来暂停控制器。 - 如果控制器试图释放 `SCL` 为高以开始下一个时钟脉冲,但目标将其保持为 `LOW`,那么 `SCL` 将保持低电平。 - 控制器被设计为在继续之前检查 `SCL` 是否确实变为 `HIGH`;如果发现 `SCL` 没有变高,它会等待。 - 目标将 `SCL` 保持为 `LOW` 所需的时间,然后将其释放为 `HIGH`,允许交易正常进行。 - 目标在需要额外时间处理或准备数据时可能使用“时钟拉伸”。 > 注意:并非所有 I2C 控制器都支持时钟拉伸。如果目标拉伸时钟而控制器不支持,可能会导致问题。 ## 帧 - “帧”可以定义为 I2C 协议中传输的基本单元,始终由 9 位组成,其中最后一位是 `ACK/NACK` 位。 - “帧”这个术语并非 I2C 规范的“官方”用语,但在理解和讨论 I2C 时经常被使用。 - 有两种类型的帧,虽然都由 9 位组成,但第一个字节的构成略有不同: 1. 目标地址帧 2. 数据帧 ## 目标地址帧 - “目标地址帧”始终是任何 I2C 交易中的**第一个**帧。 - 它确定哪个目标将参与交易,以及将发生何种类型的交易(读/写)。 ### 目标地址 - “目标地址帧”的前 7 位是“目标地址”。 - I2C 上的每个目标必须有一个固定地址。 - 地址通常为 7 位(较少使用 10 位),先发送最高有效位(MSB)。 - 地址对于设备来说是硬编码的,并且可能(部分)通过外部地址线或跳线进行配置。 - 例如,MPU-6050 的默认 I2C 地址是 `0x68`,但可以通过焊接设备上的跳线配置为 `0x69`。 - 如果我们想在 I2C 总线上放置多个相同的设备,以便将其区分为两个不同的目标,这非常有用。 - 由于地址是 7 位,地址范围是 128 个唯一地址,从 `0x00` 到 `0x7F`。 - 实际上,其中一些地址被 I2C 规范保留。 ### R/W 位 - “目标地址帧”中的第 8 位是“R/W 位”。 - I2C 交易可以是“读”或“写”。这通过设置“R/W 位”来决定。 - `读`:1(释放为 `HIGH`) - `写`:0(拉低) ### ACK/NACK 位 - “目标地址帧”中的第 9 位(最后一位)是“`ACK/NACK` 位”。 - “目标地址帧”中的 `ACK/NACK` **始终**由目标设置。 ## 数据帧 - “数据帧”包含 I2C 交易的实际数据有效载荷。 - I2C 交易中的第一个“数据帧”始终紧跟在“目标地址帧”之后。 ### 数据字节 - “数据字节”是 8 位,先发送最高有效位(MSB),**就是**实际的数据有效载荷。 - 如前所述,数据由发送者(取决于 R/W 位,控制器或目标)发送。 > - 注意 `ACK/NACK` 由接收者设置,但“数据字节”由发送者设置。 - 这些数据字节可能都是数据,但通常其中一个字节指示目标设备中的内部地址或寄存器位置。 - 例如,如果控制器想要向目标中的特定寄存器写入某个值,数据字节可能是该寄存器的地址或位置,第二个数据字节则是要写入该位置的实际数据。 - 在许多情况下,一个 I2C 交易中会发送多个数据字节。 - 额外的字节简单地连接到前一个字节之后,中间用 `ACK` 位分隔。稍后会更清楚地说明这一点。 ### ACK/NACK 位 - “数据帧”中的第 9 位(最后一位)是“`ACK/NACK` 位”。 - 由交易中的接收者设置。 ## 交易 - 此图一目了然地展示了 I2C 交易。 - 交易始终遵循相同布局: 1. 起始条件 2. 目标地址帧 3. 数据帧 4. 重复起始 (https://rana-emaan.com/read/notes/i2c/#repeated-start)(可选) 5. 停止条件 ## 写 - 此图具体展示了 I2C “控制器 `写`”,并说明了是控制器还是目标设置了数据线 `SDA` 上的位。 ## 多次写 `START` 目标地址 `W` `ACK` 数据字节 `ACK` ... 数据字节 `ACK` `STOP` > 注意:与“多次读”不同,在一连串的 `写` 中,目标是 `ACK/NACK` 位的设置者,并且它**不**以 `NACK` 终止写序列,而是进行 `ACK` 和 `STOP`。 ## 代码 ```c #define MPU6050_ADDRESS 0x68 #define TICKS (1000 / portTICK_PERIOD_MS) // 1 秒 void write(uint8_t reg, uint8_t data) { i2c_cmd_handle_t cmd = i2c_cmd_link_create(); // 命令链 ```

相似文章

Intel 8087浮点芯片的指令解码

Ken Shirriff

对Intel 8087浮点协处理器指令解码的详细逆向工程分析,解释主CPU与协处理器之间的交互、微码ROM的使用以及总线接口单元。