【I2C 技术系列 05】I2C 多主仲裁与时钟同步

📚 I2C 技术系列 · 持续更新中

本系列从 OD 开漏物理层一路打到 Linux i2c子系统,把"两根线"背后那套电气/协议/仲裁/恢复/驱动全拧成一根线。

👉 想系统追更,戳这里查看完整目录:

【I2C 技术系列 00】总目录


本篇定位 :I2C 能多主多从,靠的是 OD 开漏的线与特性派生出的两套机制------时钟同步 (SCL 线与)和仲裁(SDA 线与)。本篇把这两个机制讲透,再加时钟拉伸(Clock Stretching)。这是 I2C 协议最精妙的部分,也是你做多主系统、调总线卡死问题的核心知识。
多主是 I2C 区别于 SPI 的杀手锏

SPI 严格一主多从,从机不能主动发言。I2C 多主,任何主都能发起通信,靠线与 + 仲裁无冲突共存。这套机制的物理根基就是 【I2C 技术系列 01】I2C 电气特性与物理层的 OD 开漏。理解本篇,你才真正懂"I2C 为什么能多主"。


目录

  • 一、多主总线概述
    • [1.1 多主拓扑](#1.1 多主拓扑)
    • [1.2 多主的核心问题](#1.2 多主的核心问题)
  • [二、SCL 线与与时钟同步](#二、SCL 线与与时钟同步)
    • [2.1 SCL 线与](#2.1 SCL 线与)
    • [2.2 时钟同步过程](#2.2 时钟同步过程)
    • [2.3 同步的本质](#2.3 同步的本质)
  • [三、SDA 线与与仲裁](#三、SDA 线与与仲裁)
    • [3.1 仲裁原理](#3.1 仲裁原理)
    • [3.2 仲裁过程示例](#3.2 仲裁过程示例)
    • [3.3 仲裁的优雅之处](#3.3 仲裁的优雅之处)
    • [3.4 仲裁丢失的场景](#3.4 仲裁丢失的场景)
  • 四、仲裁丢失处理
    • [4.1 控制器硬件行为](#4.1 控制器硬件行为)
    • [4.2 软件处理](#4.2 软件处理)
    • [4.3 仲裁 + 时钟拉伸的交互](#4.3 仲裁 + 时钟拉伸的交互)
  • [五、时钟拉伸(Clock Stretching)](#五、时钟拉伸(Clock Stretching))
    • [5.1 什么是时钟拉伸](#5.1 什么是时钟拉伸)
    • [5.2 拉伸的合法用途](#5.2 拉伸的合法用途)
    • [5.3 拉伸的实现](#5.3 拉伸的实现)
    • [5.4 拉伸的滥用与超时 ⚠️](#5.4 拉伸的滥用与超时 ⚠️)
    • [5.5 不同模式对拉伸的支持](#5.5 不同模式对拉伸的支持)
  • 六、多主系统设计要点
    • [6.1 多主适用场景](#6.1 多主适用场景)
    • [6.2 设计注意事项](#6.2 设计注意事项)
    • [6.3 多主的局限](#6.3 多主的局限)
  • [七、关键问题 Q&A](#七、关键问题 Q&A)
  • 八、实战案例
    • [案例 1:双主冗余系统,偶发数据错乱](#案例 1:双主冗余系统,偶发数据错乱)
    • [案例 2:从机拉伸导致主机超时](#案例 2:从机拉伸导致主机超时)
    • [案例 3:多主系统,一个主死机拖垮总线](#案例 3:多主系统,一个主死机拖垮总线)
    • [案例 4:仲裁丢失未处理,数据错乱](#案例 4:仲裁丢失未处理,数据错乱)
  • 九、本篇小结与速查
  • 延伸阅读
  • 参考资料

一、多主总线概述

1.1 多主拓扑

复制代码
          VDD
           │
          Rp
           │
  ┌────────┼────────┐
  │        │        │
 Master1 Master2  Slaves
  │        │        │
  └────────┼────────┘
           │
       SDA / SCL(共线)
  • 多个 Master + 多个 Slave 挂同一 SDA/SCL
  • 任一 Master 可发起通信
  • 同一时刻只能一个 Master 成功占用总线

1.2 多主的核心问题

问题 解法
多主同时发 SCL,频率不同怎么办? 时钟同步(SCL 线与)
多主同时发 SDA,数据冲突怎么办? 仲裁(SDA 线与)
从机来不及处理怎么办? 时钟拉伸(从机拉低 SCL)

三套机制全靠 OD 开漏的线与。


二、SCL 线与与时钟同步

2.1 SCL 线与

SCL 是 OD 开漏 + 上拉,多个主同时驱动:

复制代码
任一主拉低 SCL → SCL = 0(线与)
所有主都释放(不拉低)→ SCL = 1(上拉拉高)
  • SCL 是线与,任何一个拉低就是低
  • 这天然实现了时钟同步

2.2 时钟同步过程

两个主频率不同(M1 快、M2 慢),SCL 同步到"低电平最长者":

复制代码
M1 想发的 SCL: ‾‾‾‾╲‾‾╲‾‾╲‾‾╲‾‾(快,短周期)
                   ╲  ╲  ╲  ╲
M2 想发的 SCL: ‾‾‾‾‾‾‾╲‾‾‾‾‾‾╲‾‾(慢,长周期)
                      ╲      ╲
实际总线 SCL:   ‾‾‾‾‾‾‾╲‾‾‾‾‾‾╲‾‾(同步:低取最长,高取最短)
                        ╲      ╲
                        ↑      ↑
                    M2拉低(长) M2拉低(长)

M1 想要(快): ‾‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\___/‾‾‾\___
M2 想要(慢): ‾‾‾‾‾‾‾‾\_______/‾‾‾‾‾‾‾\_______/‾‾‾‾‾‾‾\_______/‾‾‾‾‾‾‾\_______
实际总线   : ‾‾‾‾\___________/‾‾‾\___________/‾‾‾\___________/‾‾‾\___________
谁在拉低   :     11112222BBBB    11112222BBBB    11112222BBBB    11112222BBBB
             ↑    ↑            ↑              ↑
             │    │            │              └─ 只有 M1、M2 都释放,线才变高(高取最短)
             │    │            └───── M2 释放后 M1 又拉低,线再次变低
             │    └─────────────── M1 先拉低(4 格),随后 M2 也拉低 → 被拉长到 12 格
             └──────────────────── 起始:两线均高,总线空闲

图例:1 = 仅 M1 拉低   2 = 仅 M2 拉低   B = Both 同时拉低

规则:

  1. SCL 低电平:任一主拉低,SCL 就是低。SCL 低电平时间 = 最长那个主的低电平时间
  2. SCL 高电平:所有主都释放(都到高电平计时),SCL 才高。SCL 高电平时间 = 最短那个主的高电平时间

2.3 同步的本质

  • 每个主内部有"期望 SCL"和"实际 SCL"两个状态

  • 主拉低 SCL 后,等待 SCL 实际变高(读 SCL 输入),才进入高电平计时

  • 这样慢的主拉低时间长,快的主被"拖住",最终同步

    主内部逻辑:
    拉低 SCL(输出0)
    延时 tLOW
    释放 SCL(输出1,OD 模式变高阻)
    等待 SCL 实际读为 1(可能被其他主拉着,要等)
    延时 tHIGH
    拉低 SCL(下一周期)

时钟同步是仲裁的前提

没有时钟同步,两个主频率不同,SDA 采样时刻错位,仲裁无法进行。时钟同步保证所有主在同一 SCL 边沿采样 SDA,仲裁才能逐位比较。这两套机制是配套的。


三、SDA 线与与仲裁

3.1 仲裁原理

多主同时发,SDA 也是线与。仲裁规则:输出 1 但读到 0 的主,丢失仲裁。

复制代码
线与:任一主拉低 SDA → SDA=0
仲裁:主输出 1(释放)却读到 0(被别人拉低),说明别人在发 0,自己"输"了
  • 输出 0 的主不会丢(它自己也拉低,读到 0 正常)
  • 输出 1 的主若读到 0,说明有别的主在发 0 → 自己丢了

3.2 仲裁过程示例

两个主同时发,地址前几位相同,第 4 位不同(M1 发 0,M2 发 1):

复制代码
SCL:  1   2   3   4   5   6 ...
M1发: 1   0   1   0   ... (M1 第4位发0)
M2发: 1   0   1   1   ... (M2 第4位发1)

SDA线:1   0   1   0   ... (线与:M2发1被M1拉低)
                  ↑
           第4拍 SCL 高时:
           M2 输出1,读 SDA=0 → 丢失仲裁!
           M1 输出0,读 SDA=0 → 正常,继续
  • 前 3 位相同(1,0,1),SDA 都一致,无冲突
  • 第 4 位:M1 发 0(拉低),M2 发 1(释放)。线与结果 SDA=0
  • M2 输出 1 却读到 0 → 检测到冲突,丢失仲裁,立即停止输出,转监听模式
  • M1 继续完成传输

3.3 仲裁的优雅之处

  • 无损仲裁:赢的主不知道发生过仲裁,数据完整;输的主主动退出,不破坏总线
  • 逐位比较:类似"地址优先级",地址数值小的(0 多的)优先赢
  • 不会数据损坏:线与保证不会两个推挽打架

3.4 仲裁丢失的场景

复制代码
主 A 发 0x50(0101 0000)
主 B 发 0x52(0101 0010)
        ↑↑↑↑ ↑↑
位:    0101 00 → 前6位相同
第7位: A发0, B发1 → B输

所以 A(0x50)赢,B(0x52)输
  • 谁地址的二进制先出现"0 而别人是 1",谁赢
  • 等价:地址数值小的赢(0x50 < 0x52)

嵌入式视角:地址数值小者赢

仲裁结果是"地址数值小的优先"。设计多主系统时,关键主(高优先级)给小地址。但这不是"优先级机制"的本意,是线与的副产物。别把仲裁当可靠的优先级调度,它只解决"同时发的冲突",不解决"谁先发"。


四、仲裁丢失处理

4.1 控制器硬件行为

当控制器检测到"输出 1 读到 0"(仲裁丢失):

  1. 立即停止 SDA 驱动(转高阻)
  2. 置 ARB_LOST 状态标志
  3. 触发中断(若使能)
  4. SCL 继续跟着同步(不干扰赢方)
  5. 转从机模式监听(可选,看是否自己地址)

4.2 软件处理

c 复制代码
// 典型中断处理
void i2c_irq_handler(void) {
    if (status & ARB_LOST) {
        // 仲裁丢失,清除标志
        clear_arb_lost();
        // 记录,稍后重试
        xfer.aborted = true;
        schedule_retry();
        return;
    }
    if (status & NACK) { ... }
    if (status & TX_EMPTY) { ... }
}
  • 仲裁丢失后,软件应退避 + 重试(类似 CSMA/CD)
  • 不能立即重试(会再次冲突),加随机延时
  • 多次失败上报错误

4.3 仲裁 + 时钟拉伸的交互

  • 从机也可用时钟拉伸暂停,主等
  • 仲裁期间若从机拉伸,所有主都等
  • 仲裁丢失的主不参与后续,直到赢方完成

五、时钟拉伸(Clock Stretching)

5.1 什么是时钟拉伸

从机拉低 SCL,强制主机等待。这是从机"我还没准备好,等等"的机制:

复制代码
主机: SCL ‾‾‾‾╲(释放,期望高)‾‾‾‾╲
               ╲                ╲
从机:            ╲________________╲(拉低,拉伸)
                  ↑               ↑
              主释放SCL       从机准备好,释放
              但从机拉着       SCL变高,主机继续
  • 主机释放 SCL(期望变高),但从机拉低 → SCL 保持低
  • 主机读 SCL,发现还是低 → 等
  • 从机准备好后释放 SCL → SCL 变高 → 主机继续

5.2 拉伸的合法用途

场景 从机为什么拉伸
处理慢 从机 CPU 忙,来不及响应
准备数据 传感器 ADC 转换未完成
EEPROM 写入 写入周期(5ms)未结束
协议要求 SMBus 的 PEC 计算

5.3 拉伸的实现

从机在 ACK 拍之前或之后拉低 SCL:

复制代码
... 主发地址 → ACK 拍前,从机拉低 SCL(拉伸) → 准备好后 ACK + 释放 SCL
  • 从机拉低 SCL 期间,主机干等
  • 拉伸时间不限(规范没规定上限,实际器件几 μs 到几 ms)

5.4 拉伸的滥用与超时 ⚠️

问题 :从机异常(死机/上电未完成)一直拉低 SCL,主机永远等 → 总线挂死

对策:

  1. 主机加超时检测:主机 SCL 释放后等 N ms 还没变高,判超时,报错
  2. 控制器配超时:部分控制器有 stretch timeout 寄存器
  3. 9 时钟脉冲恢复:总线挂死后,主机手动发 9 个 SCL 脉冲,把从机从卡死状态顶出来(详见 \[10-I2C错误处理与总线恢复])
  4. 禁用拉伸:部分主控可配"不允许拉伸",但失去兼容性

5.5 不同模式对拉伸的支持

模式 拉伸支持
标准模式 允许
快速模式 允许
快速+ 允许,但部分主控可禁
高速模式 主机不要求支持拉伸(从机只能有限拉伸)
超快模式 不支持(推挽)

六、多主系统设计要点

6.1 多主适用场景

  • 冗余系统(双主控互备)
  • 多 CPU 协作(应用 CPU + 通信 CPU 共享传感器)
  • 调试主 + 运行主(调试器临时接入)

6.2 设计注意事项

要点 说明
地址分配 各主给不同地址(仲裁有先后)
互斥层 关键操作软件加锁(仲裁只防数据冲突,不防逻辑竞态)
拉伸超时 必配,防从机挂死拖垮所有主
上拉强度 多主多从,电容大,Rp 要重算
仲裁丢失重试 退避 + 随机延时,防活锁
监控 统计仲裁丢失次数,异常告警

6.3 多主的局限

  • 仲裁只到地址位不同处,若两主发同地址(同从机),会一路仲裁到数据位,可能长时间冲突
  • 实际工程多主用得少,多数系统单主 + 多从更简单可靠
  • 多主调试复杂,推荐除非必要不引入

七、关键问题 Q&A

Q1:多主同时发,会不会损坏器件?

A :不会。OD 开漏 + 线与,任一拉低都是低,无 VDD→GND 直通。这是 OD 物理特性保证的。多主仲裁无损,数据不冲突,器件不损坏。

Q2:仲裁谁赢?能预测吗?

A:赢的是"地址二进制先出现 0(别人是 1)的主",等价地址数值小的赢。可预测,但别当优先级机制用------它只解决"同时发的冲突",不保证关键主先发。

Q3:时钟拉伸和仲裁有什么关系?

A:

  • 仲裁:多主之间解决 SDA 冲突(谁发)
  • 时钟拉伸:主从之间解决"从机来不及"(节奏)
  • 两者都靠 SCL/SDA 线与,但方向不同:仲裁是主主,拉伸是从主(从机拉 SCL)
  • 仲裁期间从机也可拉伸,叠加效果

Q4:从机一直拉伸 SCL,主机怎么办?

A:

  1. 主机配拉伸超时(几 ms),超时报错
  2. 触发总线恢复:9 个 SCL 脉冲 + STOP,顶出从机
  3. 软件复位 I2C 控制器
  4. 极端:硬件复位从机(若有复位引脚)
    详见 \[10-I2C错误处理与总线恢复]。

Q5:单主系统需要管时钟同步和仲裁吗?

A:不需要主动管,但:

  • 时钟同步:单主无其他主同步,但从机拉伸仍生效(从机拉低 SCL,主等)------这本质也是 SCL 线与
  • 仲裁:单主无仲裁,但控制器硬件仍可能检测 ARB_LOST(误触发,如噪声)
  • 知识上要懂,单主系统重点是拉伸超时 + 总线恢复

Q6:仲裁丢失后立即重试,会怎样?

A :可能活锁------两主同时重试又同时冲突。正确做法:退避 + 随机延时重试(类似以太网 CSMA/CD)。多次失败(如 10 次)上报错误,别无限重试。


八、实战案例

案例 1:双主冗余系统,偶发数据错乱

现象:双主控共享 I2C 传感器,偶发读到对方请求的数据。

分析:两主同时发,仲裁期间地址相同(都读同传感器),一路仲裁到数据位,从机响应给当前赢方,但两主都以为是自己。

解决:

  1. 软件层加互斥锁(共享信号量),关键读操作前抢锁
  2. 或物理隔离:每个主独立 I2C 接独立传感器(功能安全推荐)

案例 2:从机拉伸导致主机超时

现象:读 EEPROM,主机偶发超时报错。

分析:EEPROM 写入后 5ms 内会拉伸 SCL(写周期未结束),主机若不配拉伸超时,等到 5ms 后才继续;若配了超时(< 5ms),误判为挂死。

解决:拉伸超时设 > EEPROM 最大写周期(10ms),或写后 sleep 5ms 再读,避免拉伸。

案例 3:多主系统,一个主死机拖垮总线

现象:主 B 死机且拉低 SDA(或 SCL),主 A 无法通信。

分析:OD 线与,任一设备拉低,总线就低。死机主拉低不放,整总线瘫痪。

解决:

  1. 主 A 检测总线 BUSY 超时,触发恢复
  2. 9 时钟脉冲 + STOP,尝试顶出死机主
  3. 若死机主硬件拉死,只能硬件复位或断电
  4. 设计上加看门狗,死机主自动复位释放总线

案例 4:仲裁丢失未处理,数据错乱

现象:多主系统,主控代码没处理 ARB_LOST,丢失后继续发,数据错乱。

分析:ARB_LOST 后控制器已停止 SDA 驱动,但软件没查标志,继续写数据寄存器,实际没发出。

解决:中断里查 ARB_LOST 标志,丢失则 abort 本次传输,退避重试。


九、本篇小结与速查

核心要点

  • 多主靠 OD 线与,派生两套机制:时钟同步(SCL)+ 仲裁(SDA)
  • 时钟同步:SCL 线与,低取最长、高取最短,所有主同频
  • 仲裁:SDA 线与,输出 1 读到 0 者丢失,逐位比较,地址数值小者赢
  • 仲裁无损:赢方不知情,输方主动退出
  • 仲裁丢失:清标志 + 退避 + 随机延时重试,防活锁
  • 时钟拉伸:从机拉低 SCL 强制主机等,合法用途(慢处理/写周期)
  • 拉伸超时必配:防从机异常挂死总线
  • 多主设计:地址分配 + 互斥 + 拉伸超时 + 上拉重算 + 监控
  • 单主系统也要懂:拉伸仍生效,ARB_LOST 可能误触发

机制对照表

机制 谁发起 作用 线
时钟同步 多主 同频 SCL SCL 线与
仲裁 多主 解 SDA 冲突 SDA 线与
时钟拉伸 从机 强制主机等 SCL 线与

速查表

现象 机制 对策
多主同发 仲裁 输方退避重试
从机来不及 拉伸 主机等(配超时)
总线 BUSY 卡死 拉伸超时/死机 9 时钟恢复
仲裁频繁 多主竞争 软件互斥/减并发
数据错乱 仲裁未处理 查 ARB_LOST 标志

延伸阅读

  • \[01-I2C电气特性与物理层] ------ 线与的物理根基
  • \[04-I2C协议与时序详解] ------ 仲裁在地址帧进行
  • \[08-I2C控制器寄存器与IP架构] ------ ARB_LOST/拉伸检测寄存器
  • \[10-I2C错误处理与总线恢复] ------ 拉伸超时 + 9 时钟恢复(你专利主场)
  • \[13-I2C调试与排查] ------ 仲裁丢失/拉伸的波形识别

参考资料

  • NXP UM10204 §3.1.7(仲裁)、§3.1.9(时钟同步)、§3.1.10(拉伸)
  • AN282(I2C 多主系统设计)
  • SMBus Spec(拉伸超时规范)

\[00-I2C技术系列总目录] · 上一篇:\[04-I2C协议与时序详解] · 下一篇:\[06-I2C速度模式与时序参数]


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。

如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞 | ⭐ 收藏 | 👀 关注

如果你在调试过程中遇到了其他问题,

欢迎在 评论区 💬 留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


相关推荐
szxinmai主板定制专家1 小时前
面向虚拟电厂协调控制:RK3588+FPGA+AI边缘控制器设计思路与算力评估
人工智能·嵌入式硬件·fpga开发·机器人·jetson·zynq·振动采集
Zw-awa1 小时前
DO 和 AO 到底怎么选:比较器替你决定了什么
stm32
硅基手札1 小时前
【I2C 技术系列 01】I2C 电气特性与物理层
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
comyccccc2 小时前
TB6612电机驱动板原理图与PCB设计学习笔记
单片机·嵌入式硬件
gscsded2 小时前
MCUboot裸机移植实战
单片机·嵌入式硬件·arm
北京盛世宏博3 小时前
多协议以太网温湿度变送器:Modbus TCP/UDP/SNMP 协议应用场景详解
单片机·tcp/ip·udp·传感器·温湿度
HRTOS4 小时前
HRTOS 内核 BUG 修复:任务等待对象未初始化导致的异常唤醒问题
单片机·嵌入式硬件·算法·51单片机
@MMiL4 小时前
SImulink: PowerGui下的信号FFT分析
单片机
List<String> error_P4 小时前
UART串口通信示波器波形测试
嵌入式硬件·示波器