1. I2C 总线协议概述
聊 I2C 之前,我先问你一个问题:你手头同时有好几个芯片要通信,板子上线又少,你会选什么总线?对我而言,I2C 就是那个最顺手的选择。两根线,多设备,还带应答——简直是为嵌入式量身定做的。别小看它,很多工程师栽的坑,根源恰恰是从这“两根线”开始的。
1.1 什么是 I2C
I2C(Inter-Integrated Circuit)是 Philips 在 80 年代发明的同步串行总线,现在满大街都是:传感器、EEPROM、ADC、RTC……几乎每块板上都能找到它。它只有两根信号线:SDA(数据线)和 SCL(时钟线),采用主从通信结构。一个主设备发起传输,一个或多个从设备响应。我见过不少新人以为主设备只能有一个,其实多主设备也能共存,靠的是仲裁机制。
1.2 物理层:两根线与上拉电阻
SDA 和 SCL 都是开漏(Open-Drain)输出。什么叫开漏?就是芯片只能把线拉到低电平,拉高要靠外部的上拉电阻。所以你看到 I2C 总线上挂一个 4.7kΩ 或 10kΩ 的电阻,就是这个用处。
为什么用开漏?好处是多个设备可以共线——任意一个设备都能把总线拉低,不会短路。坏处?信号上升沿靠电阻充电,速度上不去。尤其是总线电容大或者上拉电阻选高了,波形就歪了,时钟拉伸故障就容易冒出来。
简化的参数参考:
| 速度模式 | 最大频率 | 典型上拉电阻 | 总线电容上限 |
|---|---|---|---|
| 标准模式 | 100 kHz | 4.7kΩ – 10kΩ | ~200 pF |
| 快速模式 | 400 kHz | 1.5kΩ – 4.7kΩ | ~200 pF |
| 快速模式+ | 1 MHz | 1kΩ – 2kΩ | ~100 pF |
| 高速模式 | 3.4 MHz | 固定电流源 | ~100 pF |
1.3 通信协议:起始、停止、数据与应答
I2C 的时序看着简单,但每个细节都精打细算。我们先来过一次完整的数据搬砖过程:
- 起始条件(START):SCL 高电平时,SDA 由高变低。这叫“总线不再空,我要说话”。
- 发送从设备地址 + 读写位:通常是 7 位地址(也有 10 位),后面跟 1 位 R/W。这 8 位数据在 SCL 的每个高电平期间采样。
- 等待应答(ACK/NACK):第 9 个 SCL 时钟,从设备如果拉低 SDA 表示“收到”,不拉低就是“没空(NACK)”。我曾遇到过从设备上电慢了,主设备发地址后一直等不到 ACK,总线就卡在那里——这就是时钟拉伸故障的一种表现。
- 数据传输:数据字节也是 8 位 + 1 ACK。主设备发送时,SCL 由主设备驱动;主设备接收时,SCL 仍然由主设备产生。
- 停止条件(STOP):SCL 高电平时,SDA 由低变高。总线释放,给下一个设备用。
// 伪代码模拟 I2C 起始 + 地址发送
void i2c_start() {
SDA_HIGH();
SCL_HIGH();
delay(); // 确保稳定
SDA_LOW(); // 产生 START
delay();
SCL_LOW(); // 准备传数据
}
uint8_t i2c_send_byte(uint8_t data) {
for (int i = 7; i >= 0; i--) {
if (data & (1 << i)) SDA_HIGH();
else SDA_LOW();
SCL_HIGH(); delay(); // 采样
SCL_LOW(); delay(); // 准备下一位
}
// 释放 SDA,等待 ACK
SDA_HIGH();
SCL_HIGH(); delay();
uint8_t ack = (SDA_READ() == 0) ? 1 : 0;
SCL_LOW();
return ack;
}
1.4 地址与读写方向
每个 I2C 从设备都有一个唯一的地址(7 位,共 128 个,但保留 16 个用于特殊用途)。你发完地址,第 8 位决定后续方向:0 = 主写从读,1 = 主读从写。注意!方向只能在一次传输中切换,如果想改方向,得发一个重复起始条件(Repeated START),再发一次地址。
- 7位地址示例:0x48(常用温度传感器)
- 加上写位:0x90(0x48 << 1 | 0)
- 加上读位:0x91(0x48 << 1 | 1)
实际编程中,大部分驱动库的地址都是左移后的 8 位值,写代码时千万别搞混。我最初就是因为这个左移没对齐,调试了一整个下午。
1.5 时钟同步与总线仲裁
多主设备同时发起传输时怎么办?I2C 用了线与(Wire-AND)逻辑来解决。
- 时钟同步:每个主设备都会产生自己的 SCL。因为开漏,谁拉的 SCL 周期最短,谁就胜出——实际上所有主设备被同步到最慢的时钟。
- 仲裁(Arbitration):数据线上,多个主设备同时拉低时,谁能保持低电平谁输(失去总线控制权)。赢的那个继续传输。
时钟拉伸跟这个有关系吗?当然有。如果一个从设备需要更多时间处理数据,它会主动拉低 SCL(把这个时钟周期伸长)。主设备检测到 SCL 被拉低,就会等待直到 SCL 变高再继续。这是正常功能,但一旦从设备拉低时间过长或主设备没有超时处理,总线就“卡死”了——这是我们这门课的重点。
1.6 速度模式与标准
I2C 目前有几种主流速度模式:
| 模式 | 时钟频率 | 主要特点 |
|---|---|---|
| 标准模式 (Sm) | 100 kHz | 兼容性最广,几乎所有设备都支持 |
| 快速模式 (Fm) | 400 kHz | 主流,速度快 4 倍 |
| 快速模式+ (Fm+) | 1 MHz | 需要更小的上拉电阻 |
| 高速模式 (Hs) | 3.4 MHz | 专用电流源驱动,需特殊握手 |
| 超快模式 (UFm) | 5 MHz | 单向传输,常用于显示 |
实际项目中,大多数场景用 Fm 就够。如果超过 400 kHz,波形整形、上拉电阻、走线长度都得仔细算,否则时钟拉伸问题会成倍放大。
下面这张图展示 I2C 协议核心元素之间的关系,方便你快速建立全局印象。
好,I2C 基础就到这儿。别看今天讲的都是老生常谈,后面的时钟拉伸故障全靠这几点知识去拆。掌握了这些,你才能精准定位:到底是哪个家伙把时钟拽住了,拽了多久,为什么拽。