4. 中断、时钟与定时器基础

I2C 这玩意儿,看着简单,就两根线:SCL 和 SDA。但里面的门道,说实话,真不少。我刚开始用 I2C 的时候,觉得不就是发个地址、读个数据嘛,能有多复杂?结果被总线锁死、从机不响应、数据错位这些问题折腾了好几个通宵。今天咱们就把 I2C 的几种核心通信模式掰开揉碎了讲清楚。

4.1 主从模式:谁说话,谁听话

I2C 总线上的设备,分两种角色:主机(Master) 和 从机(Slave)。主机负责产生时钟信号、发起通信、决定什么时候开始什么时候结束。从机呢,就是被主机点名的那位,主机叫它,它才能回话。

我个人习惯把主机想象成「会议主持人」,从机就是「参会者」。主持人拿着话筒(SCL 时钟),点名谁,谁才能发言。其他参会者只能听着,不能插嘴。

关键点: 同一时刻,总线上只能有一个主机。从机之间不能直接通信,必须通过主机中转。

主从模式的通信流程,我总结为四步:

  1. 主机发送起始条件(START):SCL 高电平时,SDA 从高变低。这相当于主持人说「会议开始」。
  2. 主机发送从机地址 + 读写位:7 位或 10 位地址,加上 1 位 R/W 位。从机收到后,如果地址匹配,会回复一个 ACK(将 SDA 拉低)。
  3. 数据传输:每 8 位数据后跟一个 ACK/NACK。主机发送时钟,从机或主机根据读写方向提供数据。
  4. 主机发送停止条件(STOP):SCL 高电平时,SDA 从低变高。主持人说「散会」。

这里有个坑,我刚开始做项目时踩过:起始条件和停止条件之间的时间间隔不能太短。有些从机芯片处理速度慢,连续快速启动停止,它会反应不过来。我建议在两次传输之间至少留 4.7μs 的间隔(标准模式 100kHz 下)。

4.2 多主机仲裁:多个主持人抢话筒

一个 I2C 总线上可以有多个主机。但问题来了:如果两个主机同时想说话,怎么办?

I2C 的解决方案是仲裁(Arbitration)。说白了,就是谁先发现总线被占用了,谁就主动退让。

仲裁发生在 SDA 线上。多个主机同时发送数据时,它们都会监测 SDA 的电平。如果一个主机发送了高电平(1),但检测到 SDA 是低电平(0),说明有其他主机在拉低总线。这时候,这个主机就知道自己「输」了,立刻停止发送 SDA 数据,转为从机模式监听总线。

我的经验: 仲裁只发生在 SDA 上,SCL 时钟线不会仲裁。多个主机必须输出相同的时钟频率,否则仲裁会失败。我曾经在一个项目里用了两个不同晶振精度的 MCU 做主机,结果仲裁时老是出错。后来统一用外部晶振,问题就解决了。

仲裁的规则很简单:低电平优先。谁先拉低 SDA,谁就赢得总线控制权。输掉的主机不会丢失数据,因为它会在下一个时钟周期重新尝试。

多主机仲裁的典型场景:

  • 多个 MCU 共享一个传感器总线
  • 热插拔设备需要动态加入通信
  • 冗余系统,一个主机挂了,另一个自动接管

4.3 时钟同步与时钟拉伸:慢设备也有发言权

这两个概念,很多初学者容易搞混。我分开讲。

时钟同步(Clock Synchronization)

多主机模式下,每个主机都有自己的时钟源。如果两个主机的时钟频率不一样,怎么办?I2C 的时钟同步机制就是干这个的。

原理其实很简单:线与(Wired-AND)。SCL 线是开漏输出,多个主机同时驱动 SCL 时,谁先拉低 SCL,SCL 就被拉低。所有主机都要等到 SCL 被释放(变为高电平)后,才能继续自己的时钟周期。

结果就是:所有主机的时钟周期被同步到最慢的那个。低电平时间由拉低时间最长的主机决定,高电平时间由拉高时间最短的主机决定。

注意: 时钟同步不是让所有主机的时钟频率变得一样,而是让它们的时钟边沿对齐。实际频率会低于任何一个主机的标称频率。我曾经调试过一个系统,两个主机分别用 100kHz 和 400kHz,结果同步后的实际频率只有 80kHz 左右。

时钟拉伸(Clock Stretching)

时钟拉伸是从机的特权。当从机需要更多时间处理数据时,它可以在主机释放 SCL 后,主动拉低 SCL,迫使主机等待。主机检测到 SCL 被拉低,就会暂停发送时钟脉冲,直到从机释放 SCL。

我遇到过最典型的例子是 EEPROM 写入。AT24Cxx 系列在写入一页数据后,需要 5ms 左右的内部编程时间。如果主机不等待,直接发下一个命令,EEPROM 不会响应。正确的做法是:主机在发送完写入命令后,发一个起始条件 + 从机地址,如果 EEPROM 拉低 SCL(时钟拉伸),主机就等着;直到 EEPROM 释放 SCL 并回复 ACK,才表示写入完成。

避坑指南: 不是所有从机都支持时钟拉伸!有些从机(比如某些加速度传感器)根本不实现这个功能。如果你的主机驱动里写了等待时钟拉伸的逻辑,而从机不支持,就会死等。我曾经被这个问题坑过一次,后来在驱动里加了超时机制,超过 10ms 就报错退出。

4.4 高速模式(Hs-mode):飙车到 3.4MHz

标准模式 100kHz,快速模式 400kHz,高速模式直接干到 3.4MHz。速度提升了近 9 倍。但高速模式不是随便就能用的,它有一些特殊要求。

参数 标准模式 快速模式 高速模式
最大频率 100 kHz 400 kHz 3.4 MHz
上升时间 1000 ns 300 ns 40 ns
下降时间 300 ns 300 ns 40 ns
上拉电阻 4.7kΩ - 10kΩ 1.5kΩ - 4.7kΩ 0.5kΩ - 1.5kΩ
从机数量限制 127 127 受总线电容限制

高速模式的工作原理,我简单说一下:

  • 主机先进入快速模式:发送一个特殊的「主码(Master Code)」00001XXX,然后跟一个 NACK。这相当于告诉所有从机:「我要飙车了,支持的准备好」。
  • 切换到高速时钟:主机将 SCL 频率提升到 3.4MHz。只有支持 Hs-mode 的从机才会继续响应,不支持的从机自动忽略后续通信。
  • 电流源上拉:高速模式下,普通的上拉电阻不够用,因为 RC 常数太大,上升时间跟不上。主机需要打开一个内部电流源(通常 2mA - 4mA)来快速拉高 SCL 和 SDA。
  • 退出高速模式:发送停止条件后,总线自动回到快速模式或标准模式。
硬件注意事项: 高速模式对 PCB 布局要求很高。总线电容不能超过 100pF,走线长度尽量控制在 10cm 以内。我建议在 SCL 和 SDA 上串联 22Ω - 33Ω 的电阻,抑制过冲和振铃。另外,Hs-mode 的从机地址只能用 7 位,不能用 10 位地址。

说实话,我在实际项目中很少用到 3.4MHz 的高速模式。大部分场景下,400kHz 的快速模式已经够用了。只有在传输大量数据(比如摄像头配置、音频数据流)时,我才会考虑用 Hs-mode。而且,不是所有 MCU 的 I2C 外设都支持高速模式,用之前一定要查数据手册。

I2C 通信模式知识体系 主从模式 • 主机产生时钟 • 从机响应地址 • START / STOP 条件 • 7位/10位地址 • ACK / NACK 机制 • 一主多从拓扑 多主机仲裁 • 多个主机共存 • SDA 线与仲裁 • 低电平优先 • 仲裁失败自动退让 • 不丢失数据 • 时钟频率需一致 时钟同步与拉伸 • 时钟同步:多主机 • SCL 线与同步 • 同步到最慢主机 • 时钟拉伸:从机 • 从机拉低 SCL • 主机等待释放 高速模式 (Hs-mode) • 频率 3.4 MHz • 主码 + NACK 进入 • 电流源上拉 • 总线电容 ≤ 100pF • 仅 7 位地址 • 退出后回到 F/S 模式 核心基础 • 开漏输出 + 上拉电阻 • SCL 时钟线 • SDA 数据线 • 双向半双工 • 多设备共享总线 • 地址唯一性 实践要点 • 上拉电阻选型 • 总线电容控制 • 超时机制 • 电平转换(3.3V↔5V) • 总线锁死恢复 • 逻辑分析仪抓波形

嗯,以上就是 I2C 四种核心通信模式的全部内容。主从模式是基础,多主机仲裁解决冲突,时钟同步和拉伸保证兼容性,高速模式满足高性能需求。搞懂了这些,你在调试 I2C 总线时就能快速定位问题——是地址冲突?是时钟不匹配?还是从机没来得及处理?心里就有数了。