3. I2C 寻址与通信流程:7位/10位地址模式、读写操作流程、多主机仲裁机制

好,咱们今天聊点实在的。I2C 通信,说白了就是主设备跟从设备之间怎么打招呼、怎么传数据。你想想看,一条总线上挂着几十个芯片,主设备怎么找到它想找的那个?这就得靠寻址了。

3.1 7位地址模式:最常用的寻址方式

7位地址模式是 I2C 最基础的寻址方式。总线上最多能挂 112 个从设备(0x00 和 0x01 是保留地址)。每个从设备都有一个唯一的 7 位地址,加上读写位(R/W),正好凑成一个字节发送出去。

我刚开始做 I2C 驱动时,总觉得 7 位地址就是设备手册上写的那个数。后来才发现,这里有个坑——地址左移。手册上写的是 7 位地址,但实际发送时要左移一位,最低位放读写标志。

关键点: 7位地址 0x50(EEPROM 常见地址),实际发送字节 = 0xA0(写)或 0xA1(读)。

举个例子,AT24C02 这个 EEPROM,它的地址引脚是 A0、A1、A2。如果全部接地,7 位地址就是 0x50。写操作时发送 0xA0,读操作时发送 0xA1。这个转换关系,我建议你直接记在脑子里,调试的时候能省不少时间。

3.2 10位地址模式:扩展寻址空间

7 位地址不够用怎么办?I2C 规范里定义了 10 位地址模式。说白了,就是用两个字节来传输地址。第一个字节的前 5 位固定为 11110,后 2 位是地址的高 2 位;第二个字节是地址的低 8 位。

地址模式 地址范围 最大设备数 传输字节数
7位地址 0x00 ~ 0x7F(保留地址除外) 112 1字节(含R/W位)
10位地址 0x000 ~ 0x3FF 1024 2字节

我在一个多路传感器采集项目里用过 10 位地址。当时总线上挂了 20 多个传感器,7 位地址不够分,只能上 10 位模式。说实话,大部分嵌入式场景用不到 10 位地址,但你要知道它的存在,万一哪天碰到了,别慌。

我的习惯: 能用 7 位地址就别用 10 位。10 位地址兼容性差,有些老设备不支持,而且通信效率也低一些。

3.3 读写操作流程:从 START 到 STOP

I2C 的读写流程,我习惯把它拆成几个固定步骤。你只要记住这个套路,写驱动就跟填空一样简单。

3.3.1 写操作流程

  1. START 条件: SCL 高电平时,SDA 从高变低。
  2. 发送从设备地址 + 写位(0): 主设备发送 1 字节,包含 7 位地址和 W 位。
  3. 等待 ACK: 从设备拉低 SDA 表示应答。
  4. 发送寄存器地址: 告诉从设备你要写哪个寄存器。
  5. 等待 ACK。
  6. 发送数据字节: 可以连续发多个字节。
  7. 每字节后等待 ACK。
  8. STOP 条件: SCL 高电平时,SDA 从低变高。

写 EEPROM 时有个细节——页写入。AT24C02 每页 8 字节,你一次最多写 8 个字节,超过就会回卷覆盖。我曾经在这个坑里栽过跟头,调试了一下午才发现是跨页写入的问题。

3.3.2 读操作流程

读操作比写操作多一个步骤,需要先写后读。这叫复合格式。

  1. START 条件。
  2. 发送从设备地址 + 写位(0)。
  3. 等待 ACK。
  4. 发送寄存器地址。
  5. 等待 ACK。
  6. 重复 START 条件(Sr): 不发送 STOP,直接再发一次 START。
  7. 发送从设备地址 + 读位(1)。
  8. 等待 ACK。
  9. 读取数据字节: 主设备接收,每字节后发送 ACK。
  10. 读取最后一个字节时发送 NACK: 告诉从设备不用再发了。
  11. STOP 条件。
注意: 重复 START 和 STOP+START 是有区别的。重复 START 期间总线不会被释放,其他主机不会抢占总线。这在多主机系统中很关键。

3.4 多主机仲裁机制:谁抢到算谁的

多主机仲裁,说白了就是多个主设备同时想控制总线时,怎么决定谁说了算。I2C 的仲裁机制设计得很巧妙——基于 SDA 线的线与逻辑。

为什么会这样?因为 I2C 的 SDA 和 SCL 都是开漏输出,多个设备可以同时拉低总线。谁先拉低,谁就赢了。

3.4.1 仲裁过程

  • 多个主设备同时发送 START 条件。
  • 每个主设备都开始发送自己的地址和数据。
  • 每个主设备都在监控 SDA 线。如果它发送的是高电平,但检测到 SDA 是低电平,说明有其他设备在拉低总线。
  • 检测到冲突的主设备立即停止发送,退出仲裁。
  • 赢得仲裁的主设备继续通信,不受影响。

我记得有一次做双主控系统,两个 MCU 共享一条 I2C 总线。调试时发现偶尔会出现数据错乱,查了半天才发现是仲裁机制没处理好。其中一个主设备在仲裁失败后没有释放总线,导致死锁。

避坑指南: 我曾经在仲裁失败的处理上吃过亏。记住三点:
1. 仲裁失败后,主设备必须释放 SDA 和 SCL。
2. 失败的主设备可以在总线空闲后重试。
3. 仲裁过程中不会丢失数据,因为赢得仲裁的设备会继续完整传输。

3.5 知识结构图:I2C 寻址与通信流程

下面这张图把本章的核心知识点串起来了。你可以把它当成一个快速参考,写代码前瞄一眼,思路就清晰了。

I2C 寻址与通信流程知识结构 寻址模式 7位地址 10位地址 通信流程 写操作 读操作 多主机仲裁 线与逻辑 冲突检测 写操作流程:START → 地址+W → ACK → 寄存器地址 → ACK → 数据 → ACK → ... → STOP 读操作流程:START → 地址+W → ACK → 寄存器地址 → ACK → Sr → 地址+R → ACK → 数据 → NACK → STOP 仲裁流程:多主机同时发送 → 检测SDA冲突 → 失败方释放总线 → 获胜方继续通信 关键时序信号 START: SCL高时SDA下降沿 | STOP: SCL高时SDA上升沿 | ACK: 从设备拉低SDA

3.6 实战要点总结

嗯,到这里,I2C 寻址和通信流程的核心内容就讲完了。我最后给你列几个实战中一定会碰到的要点:

  • 地址确认: 写驱动前,先用逻辑分析仪抓一下地址字节,确认左移后的值对不对。
  • ACK 检测: 每个字节发送后都要检查 ACK。没有 ACK 说明从设备没响应,别傻等。
  • 重复 START: 读操作一定要用重复 START,别发 STOP 再发 START,那样总线会被释放。
  • 仲裁处理: 多主机系统里,每个主设备都要实现仲裁失败后的重试机制。
我的调试习惯: 先用逻辑分析仪看波形,确认 START、地址、ACK、数据、STOP 的时序都对,再写驱动代码。这样能省掉至少一半的调试时间。