教程 · 入门 · 30 分钟
I2C 总线工作原理:地址与上拉电阻
两根线,一条共享总线:上拉电阻到底是干什么用的,为什么叠加多块转接板会把总线弄坏,以及地址冲突该怎么解决。
引言
本站每一个传感器页面都告诉你把 SDA 和 SCL 接上,却没有一页告诉你这两根线在干什么。大多数时候你不需要知道——接上,库能用,传感器就读出数了。
然后你加上第二块板子,第一块就不应答了。或者库返回零,而且不报错。又或者两台设备被发现想要同一个地址,而文档里一个字都没说该怎么办。
这些全都是关于 I²C 的同一小组事实,而它们比症状所暗示的还要少。
两根线,多台设备
I²C 把所有设备放在同一对共享的线上:
- SDA——数据线。
- SCL——时钟,永远由控制器驱动。
在 Arduino Uno 上它们就是 A4 和 A5(以及 AREF 旁边那对重复引脚——就是同样这两根,不是额外的)。每台设备都并联接到同样这两根线上。没有片选线,也没有环形结构:加一台设备就是把它焊到同一对线上。
这就是它在机器人上扩展得如此好的原因。一颗 MPU-6050、一颗 VL53L0X 和一块 PCA9685 共享这两根线,靠地址而不是接线来区分。
没有人把线驱动到高电平
这是其他所有事实的源头。
I²C 设备只能把线拉低。它没法把线驱动到高——输出是开漏的,意思是一个通往地的开关,仅此而已。放着不管,那条线会浮在一个说不上是多少的电压上。
上拉电阻就是让线变成高电平的东西。它们不是一个提高健壮性的措施,也不是可有可无的配件;它们是电路的一半,没有它们总线就没有空闲态,什么都不工作。
开漏有一个真实优势,这也正是它被这么做的原因:如果两台设备同时说话,一台拉低、一台释放,结果就是低。没有谁在对抗,没有谁被短路,而输的那台能检测到。在任何设备都可能发言的总线上,这一点很要紧。
上拉必须待在这个窗口里
两侧都有极限,而且都能算出来。
太小,就没有设备能把线拉下来。规范要求设备在线低于 0.4 V 时至少灌入 3 mA,于是:
R_min = (Vcc − V_OL) / I_sink = (5 − 0.4) / 0.003 = 1533 Ω
太大,上升沿就太慢。线会按一条 RC 曲线针对总线电容上升,而时钟不会等它:
t_rise ≈ 0.85 × R × C ≤ 1000 ns at 100 kHz, ≤ 300 ns at 400 kHz
现在说到坑人的部分。几乎每块业余级转接板上都已经自带上拉,通常是 4.7 kΩ。把几块接到同一总线上,这些电阻就是并联的:
| 总线上的板数 | 有效上拉 | 结论 |
|---|---|---|
| 1 | 4700 Ω | 没问题 |
| 2 | 2350 Ω | 没问题 |
| 3 | 1567 Ω | 勉强——高出下限 34 Ω |
| 4 | 1175 Ω | 低于下限 |
于是故障有了一个确定的形状:一颗传感器能用,两颗能用,三颗在好日子里能,第四块板子让整条总线停摆。什么都没坏。只是设备再也没法把线拉得足够低,低到能被读成低电平。
修法是把除一块之外所有板子上的上拉电阻拆掉——通常是两颗标着 472 的小贴片电阻,板子的文档会指出是哪两颗。第一次拆时会觉得不对劲,而那恰恰是正确的答案。
在动手调试任何东西之前先扫描总线
在碰库之前,先问问总线上有什么。这段程序逐个尝试地址,并报告哪些有应答:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(115200);
while (!Serial) {}
Serial.println(F("Scanning I2C..."));
uint8_t found = 0;
for (uint8_t addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print(F(" device at 0x"));
Serial.println(addr, HEX);
found++;
}
}
Serial.println(found ? F("done") : F("nothing answered — check wiring and pull-ups"));
}
void loop() {}
结果会告诉你是哪一类问题:
- 什么都没有应答→接线、电源,或者缺上拉。不是库的问题。
- 设备在一个意料之外的地址上应答→某个地址 strap 脚的设置和你以为的不一样。
- 设备有应答,但库返回零→总线没问题,问题在它上面。
最后这一条值得整段程序。它把“线错了”和“代码错了”干净地分开,而这原本占掉了大部分调试时间。
地址,以及 0xD0 那个困惑
I²C 地址是 7 位。有 16 个值被保留,剩 112 个可用。
陷阱在这里。在线上,地址是以七位加上一个读写位发送的,所以它看起来像一个八位值。而数据手册经常引用那个八位形式。Arduino 的 Wire 库要的是七位形式。
MPU-6050 的手册说它的写地址是 0xD0、读地址是 0xD1。而 Wire 库要的是 0x68:
0xD0 = 1101 0000
0x68 = 0110 1000 the same bits, shifted right by one
如果一台设备在手册印出的地址上不应答,在断定板子坏了之前,先试试把那个值右移一位。这是 I²C 最常见的错误,没有之一。
两台设备想要同一个地址时
地址是厂家定的,所以冲突是家常便饭。三种修法,按你该有多喜欢它们来排序:
**一、地址 strap 脚。**零件有这个脚时,这是最便宜的修法。一颗 MPU-6050 坐在 0x68,把 AD0 脚拉高就是 0x69——于是两颗用一根线就能共存。一块 PCA9685 有六个焊接跳线给出 64 个地址,这就是为什么一条总线上能串 62 块板子。
**二、重定址一个的时候把别的按在复位。**有些零件地址固定,但可以在运行时重新编程。一颗 VL53L0X 出厂永远是 0x29,而跑多颗的标准做法就是这样:把每颗传感器的 XSHUT 脚拉低,放起一颗,告诉它换到一个新地址,然后重复。新地址住在 RAM 里,所以每次开机都要重跑一遍这个序列。
**三、多路器。**一颗 TCA9548A 是住在 0x70 的一台设备,后面带八路切换总线。选一个通道,和上面那台设备说话,再选下一个。它是那种既没有 strap 脚又不能重定址的八颗相同传感器的答案——代价是多一个零件,以及总线变成一次只能伺候一个。
速度、长度,以及真正会断的是什么
I²C 是为同一块板上的芯片之间设计的。机器人把它拉横跨整个底盘,而极限就是从那里开始咬人的。
- **真正的极限是电容,不是长度。**规范允许总共 400 pF。排线每米大约加 50–100 pF,每台设备大约加 10 pF。跑长了失败,是因为上升沿变得太慢——上面那张图里表现为天花板下降。
- 降速比放弃更有用。
Wire.setClock(50000)把标准速率减半,在一条勉强的总线上能买到实打实的余量。一台以 100 Hz 轮询传感器的机器人不需要 400 kHz。 - **让 SDA 和 SCL 离远一点。**在排线里把它们作为相邻的一对走,会把时钟边沿耦合进数据。在它们之间加一根地线会有帮助。
- **时钟拉伸。**需要时间的设备会把 SCL 拉低保持,控制器必须等。真正的硬件 I²C 处理得了;有些位操作实现处理不了,而症状是这台设备单独工作正常、一忙起来就损坏数据。
- **总线卡死。**如果一台设备在传输中途被复位,它可能一直把 SDA 按在低电平,于是每次扫描都什么都返回。往 SCL 上打九个脉冲,能让它把以为自己正在发的那个字节发完、释放线路——好的库里的恢复例程做的正是这件事。
混用 3.3 V 和 5 V
ESP32 和树莓派 的 I²C 跑在 3.3 V;Uno 跑在 5 V。因为总线是开漏的,**上拉电压替整条总线决定高电平。**挂着一颗只能用 3.3 V 的零件却把上拉接到 5 V,就是把 5 V 灌进一个额定 3.6 V 的引脚。
有两样东西大多数时候救你,而知道自己依赖的是哪一样很值得:
- 很多标“5 V 兼容”的转接板,板上已经有一颗 3.3 V 芯片加上稳压器和电平转换。假设之前先查——只看到有稳压器并不意味着信号脚受保护。
- 没有的话,双向 MOSFET 电平转换器是标准答案。电阻分压不行:它会在要紧的那个方向上破坏开漏行为。
出问题时
| 现象 | 通常原因 |
|---|---|
| 扫描器什么都没找到 | 缺上拉、SDA/SCL 接反,或者没有共地 |
| 三块板能用,第四块就死 | 并联上拉落到了灌电流下限之下 |
| 设备在手册地址上找不到 | 引用的是八位地址;右移一位 |
| 两颗相同传感器只有一颗应答 | 地址冲突——strap 脚、XSHUT,或者多路器 |
| 短线读着好,长线缆上跪 | 总线电容;降时钟频率或缩短走线 |
| 电机运行时数据间歇损坏 | 电源噪声——见电源与欠压 |
| 崩溃之后扫描器突然全空 | 一台设备卡在低电平按着 SDA;往 SCL 打九个时钟 |
| 单独工作正常,总线一忙就损坏数据 | 控制器不遵守时钟拉伸 |
有用的习惯是每次都先跑扫描器,在打开任何库的源码之前。它花三十秒,却把问题劈成两半——而所有 I²C 调试里有一半,是不知道故障在线的哪一边。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读