两根线,为什么能接多个传感器?|单片机动画课 08

课程插图

上一课的 UART 像两台设备互相说话。现在想接多个传感器,如果每台都独占一组信号线,引脚很快就不够用了。

I²C 提供了另一种组织方法:让多个设备共享 SDA 数据线与 SCL 时钟线,由控制器用地址指出本次要交流的对象。两根是信号线,供电与共同参考仍然需要安排。

本文采用单控制器、7 位地址的常见 I²C 模型。动画非仿真、非实测,时间放慢;设备地址和寄存器数据为教学设定,不冒充真实传感器读数。

大家共用线路,先听清在叫谁

把 SDA 和 SCL 想成共享的通道。控制器开始一次传输,发出目标地址和读写方向;地址匹配的设备参与应答,其余设备通常不参与这次数据交换。

同一组总线上,控制器依地址选择目标

图中设备 A 的地址设为 0x48,设备 B 为 0x50。高亮表示本次寻址的对象,并不是给那根线切换了一条独占物理通路。线上信号仍会到达连接在总线上的其他设备。

设备地址并不保证天然互不相同。同型号模块可能出厂地址相同,有的允许通过引脚或寄存器修改,有的需要其他方式隔离选择。同一总线上出现相同地址时,不能期待控制器自动知道你在叫哪一个。

谁负责把线拉高?

在常见 I²C 电气连接中,设备通过开漏等方式把线路拉低,松手后的高电平则依靠上拉电阻形成。这与主动推高、拉低的普通推挽 GPIO 有区别。

第二课讲过上拉提供默认高电平。在这里,两条总线也各需要合适的上拉。实际模块可能已带电阻,但并联数量、阻值和总线电容都影响通信,不能只看到有电阻就忽略电气条件。

八位地址与方向之后,第九拍留给接收方应答

控制器发出七位地址,再发一位读写方向。例子选择地址 0x48、写方向 0,于是线上这八位组成 0x90。I²C 字节按高位先发,与上一课 UART 常见的低位先发不同。

接下来第九个时钟留给 ACK:接收方拉低 SDA,表示应答;不拉低则是 NACK。发送方在应答位释放 SDA,不能一边强行维持自己的数据,一边期待对方接管。

SCL 提供时序参照。正常数据位在 SCL 高电平期间应保持稳定;起始和停止条件是专门定义的例外。动画只展开一个地址字节与 ACK,完整传输前后还要有相应控制条件。

读寄存器,常常要先告诉它读哪里

许多传感器把配置和结果安排在编号不同的寄存器里。“找到设备地址”只是找到设备,寄存器地址才进一步指出设备内部的位置。

一次典型寄存器读取中的方向切换

第三张图采用一种常见流程:开始,发设备地址并选择写;对方 ACK;写入寄存器编号并收到 ACK;重复起始,再发同一设备地址并选择读;对方 ACK 后返回数据。控制器在最后一个字节后发 NACK,再停止。

图中虚构寄存器 0x01 返回 0x2A,只用于说明流程。真实器件可能用不同寄存器宽度、不同字节顺序或不同事务要求,必须查它自己的数据手册。

在 Arduino Wire 中,beginTransmission() 传入的是 7 位地址;不要因为线上写方向字节是 0x90,就把 0x90 当作这个 API 的设备地址。库通常会负责组合方向位。本文先不放完整读寄存器代码,以免一个虚构设备例子被当成可直接驱动真实传感器的程序。

一个常见误区

“地址有七位,所以可以随意接满 128 台设备。”七位有 128 种编码,但其中包含保留用途;实际还受地址冲突、上拉、电容、布线和器件能力等约束。编码数量不等于一根总线保证能挂载的设备数量。

ACK 也只表明相应阶段收到应答,不保证最终测量值一定正确、传感器已经完成采样,或应用逻辑已经成功。

看完再猜

如果读完最后一个字节,控制器发出 NACK,是不是必然说明通信失败?

不一定。在这里,它是在告诉发送数据的设备:这次不再继续接收下一个字节。下一课,我们把视线转回 CPU:外部事件突然发生时,程序能不能先去处理一下再回来?

资料:NXP I²C 总线规范 UM10204、Arduino Wire。

发现更多好玩的开源项目

专注 Arduino、开源硬件、端侧 AI 和值得收藏的开发者工具。

课程插图

长按二维码关注「GitStar」

所有动画为教学原理示意,非芯片仿真、非硬件实测。网页发布:2026-09-21。