2. I2C通信实战:主机与从机通信流程、读写操作时序、多主机仲裁、时钟同步与拉伸

好,咱们直接进入正题。I2C 这玩意儿,看着简单,两根线嘛。但真要在项目里把它调通,尤其是碰上多设备、长距离、不同厂家的芯片混用,坑还真不少。这一章,我就带你从实战角度,把 I2C 的通信流程、读写时序、多主机仲裁和时钟同步这些核心机制,一个一个掰开揉碎了讲清楚。

2.1 主机与从机通信流程:一次完整的“对话”

I2C 通信,说白了就是主机和从机之间的一次“对话”。主机是发起者,从机是响应者。每次对话,主机都得先“点名”,告诉总线上所有设备:“我要跟某某说话啦!”

一次完整的通信流程,大致分这么几步:

  1. 起始条件(START):主机把 SDA 从高拉低,同时 SCL 保持高电平。这就像主持人敲了一下桌子,说“安静,开始!”
  2. 发送从机地址:主机发送 7 位或 10 位地址,后面紧跟一个读写位(0 表示写,1 表示读)。
  3. 从机应答(ACK/NACK):被点名的从机在第 9 个时钟周期把 SDA 拉低,表示“收到,我在”。如果没人应答,SDA 保持高电平,那就是 NACK,通信结束。
  4. 数据传输:主机或从机开始发送数据,每 8 位数据后跟一个应答位。
  5. 停止条件(STOP):主机把 SDA 从低拉高,同时 SCL 保持高电平。对话结束。

关键点:起始和停止条件,只有主机能产生。从机永远不能主动发起或结束一次通信。这是 I2C 协议的“铁律”。

2.2 读写操作时序:主机到底在干什么?

读写操作,是 I2C 最常用的场景。我习惯把写操作叫做“主机说,从机听”,读操作叫做“主机问,从机答”。

2.2.1 写操作时序

主机向从机写数据,流程很直接:

  • 主机发送 START 条件。
  • 主机发送从机地址 + 写位(0)。
  • 从机应答 ACK。
  • 主机发送寄存器地址(告诉从机数据写到哪里)。
  • 从机应答 ACK。
  • 主机发送数据字节,每字节后等待从机 ACK。
  • 主机发送 STOP 条件。

举个例子,往一个 EEPROM 的 0x10 地址写一个字节 0xAB:

START | 0xA0 (地址+写) | ACK | 0x10 (寄存器地址) | ACK | 0xAB (数据) | ACK | STOP

我的经验:写操作时,从机应答 ACK 是必须的。如果从机没应答,先检查地址对不对,再查电源和上拉电阻。我曾经被一个虚焊的地址引脚坑了整整一下午。

2.2.2 读操作时序

读操作稍微绕一点,需要先“告诉”从机读哪个地址,然后再“读”数据。这叫“复合格式”。

  • 主机发送 START 条件。
  • 主机发送从机地址 + 写位(0)。
  • 从机应答 ACK。
  • 主机发送要读取的寄存器地址。
  • 从机应答 ACK。
  • 主机发送 重复起始条件(Repeated START)。
  • 主机发送从机地址 + 读位(1)。
  • 从机应答 ACK。
  • 从机发送数据字节,主机每收到一字节后发送 ACK(表示还要继续读)或 NACK(表示读完了)。
  • 主机发送 STOP 条件。

从 0x10 地址读一个字节:

START | 0xA0 (地址+写) | ACK | 0x10 (寄存器地址) | ACK | REPEATED START | 0xA1 (地址+读) | ACK | 0xAB (从机发数据) | NACK (主机应答) | STOP

注意:读操作的最后一个字节,主机必须发送 NACK,然后才能发 STOP。如果发了 ACK,从机会以为你还要继续读,一直占用总线。我见过新手在这里卡死,总线一直拉低,再也起不来了。

2.3 多主机仲裁:谁抢到算谁的

I2C 支持多个主机挂在同一总线上。但同一时刻,只能有一个主机控制总线。如果两个主机同时发起通信,怎么办?

答案是:仲裁。

仲裁发生在 SDA 线上。每个主机在发送数据时,都会监控 SDA 的电平。如果它发送了高电平,但检测到 SDA 是低电平,它就明白“有人抢线了”,立刻放弃控制权,转为从机模式。

仲裁的规则很简单:

  • 低电平优先:谁先拉低 SDA,谁就赢得仲裁。
  • 无损仲裁:仲裁过程中,数据不会丢失。赢得仲裁的主机继续通信,输掉的自动退出。
  • 地址和数据都参与仲裁:从机地址、寄存器地址、数据字节,都可能发生仲裁。

实战要点:多主机仲裁时,每个主机必须能正确检测 SDA 电平。如果上拉电阻太弱(比如 10kΩ 以上),SDA 上升沿变慢,可能导致仲裁误判。我建议上拉电阻选 4.7kΩ,总线电容大时降到 2.2kΩ。

2.4 时钟同步与拉伸:慢设备也能上高速

时钟同步和拉伸,是 I2C 里两个容易被忽略,但极其重要的机制。它们保证了不同速度的设备能正常通信。

2.4.1 时钟同步

多主机模式下,每个主机都有自己的时钟源。如果两个主机的时钟频率不一样,SCL 线怎么统一?

I2C 用“线与”的方式同步时钟。每个主机在 SCL 高电平期间,如果检测到 SCL 被其他主机拉低,它就会等待,直到 SCL 变高。最终,SCL 的低电平时间由最慢的主机决定,高电平时间由最快的主机决定。说白了,大家互相迁就,取个“最大公约数”。

2.4.2 时钟拉伸

时钟拉伸,是从机的“特权”。当从机处理数据来不及(比如 EEPROM 在写内部存储),它会主动拉低 SCL 线,告诉主机:“慢点,我还没准备好!”

主机检测到 SCL 被拉低,就会等待,直到从机释放 SCL。这保证了慢速从机不会被高速主机“逼死”。

避坑指南:我曾经用过一个温湿度传感器,它的时钟拉伸时间长达 150μs。而我用的 MCU 的 I2C 外设默认超时是 100μs。结果通信时断时续,查了三天才发现是超时配置太短。所以,用新芯片前,一定先看 datasheet 里“Clock Stretching”这一项。

2.5 知识结构总览

下面这张图,把 I2C 通信的核心流程和机制串起来了。你可以把它当作一个快速参考。

I2C通信核心流程与机制 I2C通信核心流程与机制 主机发起通信 发送起始条件 (START) 发送从机地址 + 读写位 从机应答 (ACK/NACK) 数据传输 (8位 + ACK) 发送停止条件 (STOP) 多主机仲裁 时钟同步 时钟拉伸 (从机) 关键点 START/STOP 只能由主机产生 仲裁是“低电平优先”

2.6 常见问题与排查思路

实战中,I2C 通信出问题,无非就那几类。我列个表,方便你对照排查。

现象 可能原因 排查方法
无应答(NACK) 地址错误、从机未上电、上拉电阻问题 用示波器抓地址波形,核对 datasheet
数据错位 时钟频率不匹配、从机时钟拉伸超时 降低 I2C 频率,检查从机拉伸时间
总线死锁(SDA 一直低) 从机未释放总线、主机未发 STOP 强制复位从机,或给 SCL 发 9 个脉冲
多主机冲突 仲裁失败、上拉电阻太弱 检查 SDA 上升沿,换更小的上拉电阻

最后提醒一句:I2C 调试,示波器是王道。别光靠逻辑分析仪看高低电平,要看波形边沿、毛刺、时序关系。我见过太多“软件没问题,硬件也没问题,但就是不通”的案例,最后都是波形上找到的答案。