2. I2C通信实战:主机与从机通信流程、读写操作时序、多主机仲裁、时钟同步与拉伸
好,咱们直接进入正题。I2C 这玩意儿,看着简单,两根线嘛。但真要在项目里把它调通,尤其是碰上多设备、长距离、不同厂家的芯片混用,坑还真不少。这一章,我就带你从实战角度,把 I2C 的通信流程、读写时序、多主机仲裁和时钟同步这些核心机制,一个一个掰开揉碎了讲清楚。
2.1 主机与从机通信流程:一次完整的“对话”
I2C 通信,说白了就是主机和从机之间的一次“对话”。主机是发起者,从机是响应者。每次对话,主机都得先“点名”,告诉总线上所有设备:“我要跟某某说话啦!”
一次完整的通信流程,大致分这么几步:
- 起始条件(START):主机把 SDA 从高拉低,同时 SCL 保持高电平。这就像主持人敲了一下桌子,说“安静,开始!”
- 发送从机地址:主机发送 7 位或 10 位地址,后面紧跟一个读写位(0 表示写,1 表示读)。
- 从机应答(ACK/NACK):被点名的从机在第 9 个时钟周期把 SDA 拉低,表示“收到,我在”。如果没人应答,SDA 保持高电平,那就是 NACK,通信结束。
- 数据传输:主机或从机开始发送数据,每 8 位数据后跟一个应答位。
- 停止条件(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 通信的核心流程和机制串起来了。你可以把它当作一个快速参考。
2.6 常见问题与排查思路
实战中,I2C 通信出问题,无非就那几类。我列个表,方便你对照排查。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 无应答(NACK) | 地址错误、从机未上电、上拉电阻问题 | 用示波器抓地址波形,核对 datasheet |
| 数据错位 | 时钟频率不匹配、从机时钟拉伸超时 | 降低 I2C 频率,检查从机拉伸时间 |
| 总线死锁(SDA 一直低) | 从机未释放总线、主机未发 STOP | 强制复位从机,或给 SCL 发 9 个脉冲 |
| 多主机冲突 | 仲裁失败、上拉电阻太弱 | 检查 SDA 上升沿,换更小的上拉电阻 |
最后提醒一句:I2C 调试,示波器是王道。别光靠逻辑分析仪看高低电平,要看波形边沿、毛刺、时序关系。我见过太多“软件没问题,硬件也没问题,但就是不通”的案例,最后都是波形上找到的答案。