3、多从机拓扑设计:地址分配、总线仲裁、上拉电阻计算

好,咱们进入第三章。多从机拓扑设计,说白了就是解决三个问题:谁是谁(地址分配)、谁先说话(总线仲裁)、信号能不能传到位(上拉电阻)。这三个点任何一个出问题,你的 I2C 总线就别想正常工作。

我记得刚入行那会儿,总觉得 I2C 简单,两根线嘛。结果第一次做多从机项目,三个传感器挂一条总线上,死活只认出一个。查了两天,最后发现是地址冲突。嗯,从那以后我再也不敢小看这个环节了。

3.1 地址分配:别让两个从机抢同一个名字

I2C 的地址是 7 位(也有 10 位模式,但 STM32 默认用 7 位)。7 位能表示 0x00 ~ 0x7F,共 128 个地址。但注意,0x00 是广播地址,0x7F 保留,实际可用的是 0x01 ~ 0x7E,共 126 个。

听起来够用对吧?但问题在于,很多传感器出厂时只给你一个固定地址,或者只有两三个可选引脚。比如常见的 MPU6050,地址只有 0x68 和 0x69 两个选择。你挂三个 MPU6050 上去,必然冲突。

核心原则:一条 I2C 总线上,每个从机必须有唯一的 7 位地址。地址冲突时,两个从机都会响应,数据直接乱掉。

3.1.1 地址分配策略

我一般按以下优先级来分配地址:

  1. 查数据手册:先看每个器件的默认地址和可选地址范围。
  2. 硬件引脚配置:很多芯片有 ADDR、A0/A1/A2 引脚,通过拉高或拉低来改变地址。
  3. 软件可编程地址:部分芯片支持通过命令修改地址(如 EEPROM 的 24 系列)。
  4. 分时复用:实在冲突了,用 GPIO 控制从机的电源或使能脚,分时访问。
器件类型 典型地址范围 地址配置方式 注意事项
MPU6050(陀螺仪) 0x68 / 0x69 AD0 引脚电平 只有两个地址,多片需分时
AT24C02(EEPROM) 0x50 ~ 0x57 A0/A1/A2 引脚 最多 8 片同总线
BMP280(气压计) 0x76 / 0x77 SDO 引脚电平 注意默认值
SSD1306(OLED) 0x3C / 0x3D DC 引脚或跳线 常见冲突源
我的习惯:画原理图时,就把每个从机的地址标注在原理图上。调试时直接对照,省得回头查数据手册。

3.1.2 地址冲突的排查方法

我曾经遇到一个项目,挂了两片 AT24C02,地址设成 0x50 和 0x51,结果读出来数据全是乱的。用逻辑分析仪一看,主机发送 0x50 时,两个从机都回了 ACK。为什么?因为 AT24C02 的地址是 0x50 + (A2<<2 | A1<<1 | A0),我 A0 引脚忘记接上拉电阻了,浮空状态导致两个芯片都读到了低电平。

排查步骤:

  • 用逻辑分析仪抓取主机发送的地址字节。
  • 检查每个从机的 ACK 响应,看是否有多个从机同时应答。
  • 用万用表测量地址配置引脚的电压,确认硬件连接正确。
  • 写一个扫描程序,遍历 0x01 ~ 0x7E,看哪些地址有应答。
// I2C 地址扫描示例(STM32 HAL 库)
for (uint8_t addr = 0x01; addr < 0x7F; addr++) {
    HAL_StatusTypeDef ret = HAL_I2C_IsDeviceReady(&hi2c1, (addr << 1), 1, 10);
    if (ret == HAL_OK) {
        printf("Device found at 0x%02X\n", addr);
    }
}

3.2 总线仲裁:谁先抢到谁说话

I2C 总线仲裁,很多人觉得跟自己没关系。其实关系大了。仲裁机制决定了多个主机同时发起通信时,谁获得总线控制权。但即使你只有一个主机,仲裁机制也会影响从机的响应行为。

说白了,I2C 的仲裁是基于线与逻辑的。谁先输出低电平,谁就赢。因为 SDA 和 SCL 都是开漏输出,低电平会覆盖高电平。

注意:仲裁只发生在主机之间。从机不会主动发起仲裁,但从机在响应时如果拉低 SDA 保持总线,也会影响仲裁结果。

3.2.1 仲裁过程

假设两个主机同时想发送数据:

  1. 两个主机都拉低 SCL,开始时钟。
  2. 第一个数据位,主机 A 输出高电平,主机 B 输出低电平。
  3. SDA 线上实际是低电平(因为低覆盖高)。
  4. 主机 A 检测到 SDA 与自己输出的不一致,知道自己仲裁失败,立即释放总线。
  5. 主机 B 继续通信,不受影响。

这个过程完全由硬件自动完成,你不需要写代码处理。但你要知道:仲裁失败的主机不会产生错误中断,它只是默默退出。如果你的程序依赖“发送完成”标志来判断通信成功,仲裁失败时可能误判。

避坑指南:我曾经在一个多主机项目中,两个 STM32 共用一个 I2C 总线。其中一个主机发送数据时,另一个主机刚好也启动了发送。结果数据错位,两个主机都以为自己成功了。后来我加了一个软件握手信号,用 GPIO 做总线请求,才解决。

3.2.2 多主机拓扑的注意事项

  • 每个主机必须能检测仲裁失败(硬件支持)。
  • 仲裁失败后,主机必须释放总线并重试。
  • 从机地址不能冲突,否则仲裁机制无法区分。
  • 多主机场景下,上拉电阻要更保守计算(见下一节)。

3.3 上拉电阻计算:信号质量的命门

上拉电阻选不对,I2C 总线要么信号爬不上去,要么功耗太大。我见过太多人随便焊个 10kΩ 上去,结果 400kHz 模式下波形惨不忍睹。

上拉电阻的取值受三个因素约束:

  • 上升时间:RC 常数决定,R 越大,上升越慢。
  • 总线电容:包括引脚电容、走线电容、过孔电容。
  • 驱动能力:从机拉低 SDA 时,需要能克服上拉电阻的电流。

3.3.1 计算公式

I2C 规范给出了上拉电阻的最小值和最大值:

参数 标准模式(100kHz) 快速模式(400kHz) 快速+模式(1MHz)
最大上升时间 tr 1000 ns 300 ns 120 ns
最小上拉电阻 Rp(min) ≈ 1.5 kΩ ≈ 1.5 kΩ ≈ 1.5 kΩ
最大上拉电阻 Rp(max) ≈ 10 kΩ ≈ 4.7 kΩ ≈ 2.2 kΩ

具体计算:

  • Rp(min) = Vcc / Iol(max),其中 Iol(max) 是从机最大灌电流(通常 3mA)。
  • Rp(max) = tr / (0.8473 × Cb),其中 Cb 是总线总电容。

举个例子:3.3V 供电,总线电容 100pF,400kHz 模式:

  • Rp(min) = 3.3V / 3mA = 1.1kΩ,取标称值 1.5kΩ。
  • Rp(max) = 300ns / (0.8473 × 100pF) ≈ 3.54kΩ。
  • 所以上拉电阻选 2.2kΩ ~ 3.3kΩ 比较合适。
经验值:3.3V 系统、100kHz、总线电容 < 200pF,我一般用 4.7kΩ。400kHz 时降到 2.2kΩ。如果总线很长(超过 20cm),我会用 1.5kΩ 并加 TVS 管。

3.3.2 多从机时的电容估算

每个从机引脚大约有 5~10pF 的输入电容。走线电容约 1pF/cm。过孔电容约 0.5pF。所以:

C_b = 主机引脚电容 + 所有从机引脚电容 + 走线电容 + 过孔电容

假设 5 个从机,每个 10pF,走线 30cm,过孔 10 个:

  • 从机电容:5 × 10pF = 50pF
  • 主机电容:约 10pF
  • 走线电容:30cm × 1pF/cm = 30pF
  • 过孔电容:10 × 0.5pF = 5pF
  • 总电容 ≈ 95pF

这个值代入上面的公式,就能算出合适的上拉电阻。

注意:上拉电阻太小,会导致低电平电压升高(Vol 超标),从机可能无法正确识别低电平。上拉电阻太大,上升时间过长,高速模式下信号畸变。

3.4 拓扑结构图

下面这张图展示了多从机 I2C 总线的典型拓扑,包括地址分配、上拉电阻位置和仲裁逻辑。

I2C 多从机拓扑结构图 I2C 多从机拓扑结构 主机 STM32 SDA SCL Rₚ Rₚ 从机 1 地址: 0x50 从机 2 地址: 0x51 从机 3 地址: 0x52 总线仲裁机制 基于线与逻辑:谁先输出低电平,谁获得总线控制权 上拉电阻 Rₚ 位于 SDA 和 SCL 线上,靠近主机或总线中间位置 每个从机必须分配唯一地址,避免地址冲突导致通信失败

3.5 实战建议

最后,给几个我踩过坑之后总结的实战建议:

  • 地址规划表:项目初期就做好地址分配表,贴在工位上。
  • 上拉电阻选型:不要盲目用 10kΩ。先估算总线电容,再按频率计算。
  • 逻辑分析仪:调试多从机系统时,逻辑分析仪是必备工具。看波形比看代码快得多。
  • 预留测试点:PCB 上给 SDA 和 SCL 预留测试点,方便抓波形。
  • 分步上电:第一次调试时,只挂一个从机,确认通信正常后再逐个增加。
我的习惯:每个从机地址都用一个宏定义,写在头文件里。万一要改地址,只改一处,不会漏。

嗯,多从机拓扑设计就讲到这里。地址分配、总线仲裁、上拉电阻,这三个点看似独立,其实环环相扣。地址冲突会导致仲裁异常,上拉电阻不对会影响地址识别。你想想看,是不是这个道理?