教程 · 入门 · 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
一张以总线电容为横轴、允许上拉电阻为纵轴的对数刻度图。两条下降曲线标出 100 千赫和 400 千赫的上升时间天花板,1533 欧处的水平红线标出灌电流下限,低于它就没有设备能把线拉低。四条水平虚线标出一、二、三、四块叠放转接板的有效电阻,分别是 4700、2350、1567、1175 欧;四块板那条落到了下限之下。
可用区间在灌电流下限和上升时间天花板之间。注意重负载总线上 400 千赫的位置:电容超过大约 250 pF 之后,它的天花板掉到下限之下,那里用普通电阻根本做不到快速模式。 下载 SVG

现在说到坑人的部分。几乎每块业余级转接板上都已经自带上拉,通常是 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 调试里有一半,是不知道故障在线的哪一边。

探索关联图谱

本教程所属的搭建

包含本教程的项目和学习路线。

延伸阅读

参考资料