3、I2C寻址与通信流程:7位寻址与10位寻址、读写操作流程、多主机仲裁

好,咱们今天聊聊I2C的寻址和通信流程。这部分内容,说白了就是I2C总线上设备之间怎么「打招呼」、怎么「传话」的规矩。你搞懂了这些,写驱动的时候心里就有底了。

3.1 7位寻址与10位寻址

I2C总线上每个设备都有一个唯一的地址。主机发数据前,得先喊一声这个地址,告诉总线「我要找谁」。这个地址有两种格式:7位和10位。

3.1.1 7位寻址

这是最常用的方式。地址占7个bit,加上一个读写位(R/W),凑成一个字节发送出去。

地址范围是0x00到0x7F。但要注意,0x00是通用呼叫地址,0x01是起始字节,0x02到0x07保留,0x78到0x7F也是保留地址。所以实际可用的地址大概在0x08到0x77之间。

地址格式:

| 7-bit Address | R/W |
| A6 A5 A4 A3 A2 A1 A0 | 0/1 |
  • R/W = 0:主机要写数据到从机
  • R/W = 1:主机要从从机读数据
个人经验:我刚开始做I2C驱动时,总搞不清地址要不要左移一位。其实很简单——你在数据手册里看到的地址,比如0x50,那是7位地址。发送时要左移一位,再拼上读写位。所以写操作发0xA0,读操作发0xA1。这个坑我踩过,后来养成了习惯:写代码前先确认地址格式。

3.1.2 10位寻址

7位地址最多挂127个设备,有些场景不够用。10位寻址就解决了这个问题,地址范围扩展到0x000到0x3FF。

10位寻址的发送分两步:

  1. 第一个字节:高5位是11110,接着是地址的高2位(A9、A8),最后是R/W位。
  2. 第二个字节:地址的低8位(A7~A0)。

格式如下:

第一个字节: | 1 1 1 1 0 | A9 A8 | R/W |
第二个字节: | A7 A6 A5 A4 A3 A2 A1 A0 |
注意:10位寻址不是所有从机都支持。我建议你在选型时先确认一下。另外,10位寻址的兼容性处理比较麻烦,驱动里要额外判断。我个人习惯是,能用7位就别用10位,除非真的挂了很多设备。

3.2 读写操作流程

I2C的通信流程,核心就是「起始信号 → 发送地址 → 读写数据 → 停止信号」。咱们分写操作和读操作来说。

3.2.1 写操作流程

主机往从机写数据,流程如下:

  1. 起始信号(S):SCL高电平时,SDA从高变低。
  2. 发送从机地址 + 写位:7位地址左移一位,最低位为0。
  3. 等待应答(ACK):从机拉低SDA表示应答。
  4. 发送寄存器地址:告诉从机你要写哪个寄存器。
  5. 等待应答(ACK)。
  6. 发送数据字节:可以连续发多个字节。
  7. 每发一个字节,等待一次应答。
  8. 停止信号(P):SCL高电平时,SDA从低变高。

时序图(简化):

S | Addr+W | ACK | RegAddr | ACK | Data1 | ACK | Data2 | ACK | ... | P
关键点:写操作时,从机地址和寄存器地址是分开发送的。很多新手会搞混,以为发完地址就直接发数据了。其实中间还有个寄存器地址的步骤。

3.2.2 读操作流程

读操作比写操作多一个步骤——需要先写寄存器地址,再重新发送起始信号,然后读数据。

  1. 起始信号(S)。
  2. 发送从机地址 + 写位。
  3. 等待应答。
  4. 发送要读取的寄存器地址。
  5. 等待应答。
  6. 重复起始信号(Sr):SCL高电平时,SDA再次从高变低。
  7. 发送从机地址 + 读位。
  8. 等待应答。
  9. 读取数据字节:主机每读一个字节,要发送ACK(表示还要继续读)或NACK(表示读完了)。
  10. 停止信号(P)。

时序图:

S | Addr+W | ACK | RegAddr | ACK | Sr | Addr+R | ACK | Data1 | ACK | Data2 | NACK | P
避坑指南:我曾经在读取传感器数据时,忘了发重复起始信号,结果读到的数据全是错的。后来排查了半天才发现,原来读操作必须要有这个Sr。你写驱动时,记得检查I2C控制器是否支持重复起始功能。大部分都支持,但有些老芯片需要手动配置。

3.3 多主机仲裁

I2C总线允许多个主机同时发起通信。但总线只有一根SDA和一根SCL,冲突了怎么办?这就靠仲裁机制来解决。

3.3.1 仲裁原理

仲裁发生在SDA线上。多个主机同时发送数据时,谁先拉低SDA,谁就赢得仲裁。因为I2C总线是开漏输出,低电平会覆盖高电平。

仲裁过程:

  1. 每个主机在发送数据时,会同时监测SDA电平。
  2. 如果某个主机发送了高电平,但检测到SDA是低电平,说明有其他主机在拉低总线。
  3. 这个主机就知道自己仲裁失败了,立即停止发送,转为从机模式。
  4. 仲裁获胜的主机继续通信,不受影响。
核心要点:仲裁不会破坏数据。因为仲裁失败的节点在检测到冲突时就退出了,不会继续发送无效数据。总线上的数据始终是有效的。

3.3.2 仲裁与地址的关系

仲裁不仅发生在数据阶段,也发生在地址阶段。多个主机同时发送地址时,谁的地址小(二进制值小),谁更容易赢得仲裁。因为地址小的主机,会在某个位上先拉低SDA。

举个例子:

主机 发送地址(二进制) 仲裁结果
主机A 1010 000 失败(第5位为1,被拉低)
主机B 1010 001 获胜(第5位为0,拉低总线)
注意:仲裁只发生在SDA线上,SCL线由所有主机共同驱动。如果某个主机拉低了SCL,其他主机必须等待它释放。这就是时钟同步机制。我遇到过一个问题:两个主机时钟频率不一样,结果SCL被拉低的时间不一致,导致通信超时。后来我统一了所有主机的时钟频率,问题就解决了。

3.3.3 多主机系统的设计建议

如果你在设计多主机系统,有几点要注意:

  • 每个主机要有唯一的地址:仲裁时,地址小的主机优先级高。如果你想让某个主机优先通信,给它分配一个较小的地址。
  • 处理仲裁失败:仲裁失败的主机需要重试。我建议加一个重试机制,比如等待随机时间后再尝试。否则两个主机可能一直冲突。
  • 使用总线空闲检测:在发起通信前,先检测总线是否空闲(SDA和SCL都为高)。这样可以减少冲突概率。
个人习惯:我一般不在多主机系统里用I2C,除非真的有必要。因为仲裁和重试逻辑会增加软件复杂度。如果非要这么做,我会用带硬件仲裁支持的I2C控制器,这样驱动写起来省心很多。

3.4 本章知识体系

下面这张图帮你梳理了本章的核心逻辑:

I2C寻址与通信流程知识体系 寻址方式 7位寻址 (0x00~0x7F) 10位寻址 (0x000~0x3FF) 读写操作流程 写操作:S+Addr+W+Reg+Data+P 读操作:S+Addr+W+Reg+Sr+Addr+R+Data+P 多主机仲裁 仲裁原理:低电平获胜 地址仲裁:小地址优先 核心要点总结 1. 7位地址发送时需左移1位,拼上R/W位 2. 读操作必须使用重复起始信号(Sr) 3. 仲裁时,先拉低SDA的主机获胜 4. 多主机系统需处理仲裁失败重试 5. 10位寻址兼容性较差,非必要不用

嗯,这一章的内容就这些。寻址和通信流程是I2C的基础,你写驱动时天天都会用到。多主机仲裁虽然不常见,但理解了它的原理,遇到问题时就能快速定位。下一章咱们聊聊I2C的时序参数和电气特性,到时候我会分享一些调试经验。


专注资料整理