一文搞懂 I2C

阅读导航

  • 为什么 I2C 只有两根线却能连很多设备?
  • 为什么 I2C 一定用“开漏/上拉”,而不是推挽?
  • 为什么起始/停止要在 SCL 高电平期间改变 SDA?
  • 为什么数据采样要在 SCL 高电平?
  • 为什么每 8 位数据后要有第 9 位“应答位”?
  • 如何选上拉电阻、怎么看总线电容、如何识别时钟拉伸?

术语表(名词解释与作用)

  • 开漏(Open-Drain)/ 开集(Open-Collector)

    • 定义(器件级):开漏是 MOSFET 的漏极开路、开集是 BJT 的集电极开路;都只能“拉低”,不能“主动拉高”。释放时由外部上拉电阻将线拉到高电平。
    • 结构示意(文字版):VDD —[Rpullup]— 总线线(SDA/SCL);每个设备的“低侧开关”并联到总线线,谁合上谁把线拉低。
    • 为什么需要:避免推挽并联产生“高对低”的硬冲突;实现“线与”逻辑(只要有人拉低,线就是低),支撑仲裁与 ACK 等共享规则。
    • 工程要点:
      • MCU若不支持开漏输出,可用“输出0=拉低、输入=释放”来模拟(注意不要用推挽的“输出1”去拉高,容易冲突)。
      • 利用外部上拉电压可实现“电平转换”(如 3.3V 控制 5V 设备),但要核对器件的输入容许范围与漏电流。
  • 高阻态(High-Z)

    • 定义:输出级断开、不主动驱动;电平由外部网络(上拉/下拉、电容)决定。
    • 本质区别:高阻态不是“输出高”,而是“不输出”。在 I2C 中“拉高=释放=高阻态”。
    • 为什么重要:
      • 让线在无人拉低时回到高电平(靠上拉)。
      • 在第 9 位应答周期,发送方释放 SDA,让接收方接管驱动;实现“轮流发言”。
      • 避免多个设备同时驱动导致冲突。
    • 工程要点:
      • MCU 引脚模式切换为输入,即进入高阻态;若芯片支持“开漏模式”,优先使用。
      • 谨慎启用内部上拉:与外部上拉并联会改变等效电阻,影响上升沿与功耗。
  • 上拉电阻(Pull-up Resistor)

    • 作用:在无人拉低时提供“逻辑 1”;与总线电容共同决定上升沿速度。
    • 关键公式:上升沿近似 t_r ≈ 0.85 × R_pullup × C_bus(经验公式,取 30%→70% 的上升段)。
    • 选型步骤:
      1. 估算或测量总线电容 C_bus(走线、器件、连接器等的等效和)。
      2. 根据目标速率选择可接受的 t_r(参考器件数据手册/规范)。
      3. 由 R ≤ t_r / (0.85 × C_bus) 粗略反推上拉电阻上限。
      4. 综合功耗与下拉电流,选在 1kΩ~10kΩ 范围内合适值,并用示波器验证。
    • 计算示例:C_bus ≈ 200 pF、目标 t_r ≈ 300 ns(快速模式量级),R ≈ 300e-9/(0.85×200e-12) ≈ 1.76 kΩ,工程上可选 1.8kΩ/2.2kΩ 并验证。
    • 注意事项:
      • R 太大 → 上升沿过慢 → 采样窗口内可能未达 VIH → 误判或“毛刺”。
      • R 太小 → 功耗增大、低电平电流大、器件压力大;多处并联上拉会使等效 R 进一步变小。
  • 线与(Wired-AND)

    • 逻辑规则:
      • 只要有人拉低(输出0),总线就是低(0);
      • 只有所有设备都释放(高阻态),总线才是高(1)。
    • 为什么与 I2C 匹配:
      • 仲裁:若主机“释放=1”却读到“实际=0”,说明别人拉低,自己仲裁失败。
      • ACK:发送方在第 9 位释放 SDA,由接收方拉低形成确认(0)。
    • 小示例(文字):
      • 设备A输出0,设备B释放 → 总线=0;
      • 设备A释放,设备B释放 → 总线=1。
  • VIL/VIH(低/高电平阈值)

    • 定义:输入电路判定“低/高”的门限;常见器件约定 VIH≈(0.60.8)·VDD、VIL≈(0.20.4)·VDD(具体看器件手册)。
    • 为什么关键:
      • 上升沿过慢时,SDA 在采样时刻可能尚未达到 VIH → 被判为“低”,造成位错误。
      • 噪声抖动可能使电平跨越门限,引发误判或“多次起始/停止”的错判。
    • 工程要点:
      • 选用带施密特触发的输入可提升抗抖能力(VIH/VIL 有滞回)。
      • 用示波器在 SCL 高期间观察 SDA 是否稳定并超过 VIH。
  • 总线电容(Bus Capacitance)

    • 来源:PCB 走线、排线/电缆、器件管脚、连接器、探头等的等效电容之和。
    • 影响:与上拉电阻形成 RC 网络,决定上升沿速度;电容越大,越难跑高速。
    • 工程评估:
      • 经验值:较短走线且设备不多时常在 50~200 pF;长线或多设备可能逼近几百 pF。
      • 规范建议:系统总线电容通常限制在“几百 pF”量级(具体请参考所用器件/规范)。
      • 测量:示波器观察上升沿斜率并估算;或通过厂家工具/电容表评估。
  • 仲裁(Arbitration)

    • 过程(文字推演):两个主机同时发送,直到出现位差:主机A欲发送“1”(释放),主机B发送“0”(拉低)。在 SCL 高期间,A 读到总线为“0”,判断有人拉低 → A仲裁失败,停止发送;主机B继续。
    • 本质:“线与”逻辑让低电平占优,因此冲突不会把总线推高;仲裁依赖“边发送边比较实际电平”。
    • 工程要点:
      • 确保主机在发送期间读取 SDA 的实际值;
      • 硬件 I2C 控制器通常内置仲裁检测与错误标志。
  • 时钟拉伸(Clock Stretching)

    • 行为:从设备在需要时间处理数据时,主动将 SCL 拉低,迫使主机等待;释放后主机继续时钟。
    • 作用:允许慢速设备与主机同步,避免数据丢失。
    • 工程要点:
      • 硬件 I2C:通常自动识别拉伸;
      • 软件 I2C:应在“期望SCL高”时检测 SCL 是否被外部保持为低,并加入超时策略(避免无限等待)。
      • 某些兼容协议(如 SMBus)对拉伸/超时有更严格限制,需按规范实现。
  • 重复起始(Repeated Start)

    • 定义:不发停止而再次发起始,保持总线占用并切换方向/设备。
    • 典型用途:寄存器读流程(先写寄存器地址,后立即切读方向);避免 STOP 造成从设备状态重置。
    • 工程要点:
      • 某些器件要求 STOP 后再读,具体以数据手册为准;
      • 驱动中需区分“Start/Repeated Start”的 API 或寄存器位。
  • ACK/NACK(应答/非应答)

    • 规则:每 8 位数据后进入第 9 位应答周期;接收方拉低为 ACK,保持高为 NACK。
    • 语义:
      • 写流程:从设备 ACK 表示地址/数据已接收;NACK 表示拒绝或忙。
      • 读流程:主机在读到最后一字节后发送 NACK,表示“到此为止”,随后 STOP。
    • 工程要点:
      • 必须检查每个字节的 ACK/NACK,作为错误与流控的依据;
      • 若出现 NACK,需判定是地址不匹配、寄存器不存在、器件忙(如 EEPROM 写周期)还是电气问题。
  • 术语之间的关系(一图胜千言的文字版)

    • 开漏 + 高阻态 + 上拉 → 总线“默认高”,有人拉低就“低”。
    • 线与 → 低电平占优 → 仲裁与 ACK 的物理基础。
    • 总线电容 + 上拉电阻 → 决定上升沿时间 → 影响能否在 SCL 高期间稳定采样。
    • VIL/VIH → 判定有效电平 → 上升沿太慢或抖动会跨越门限导致错判。
    • 重复起始/拉伸 → 建立更复杂的时序与流控能力。
  • 观察与验证(教学/调试建议)

    • 用逻辑分析仪抓取“起始/停止、地址字节、应答位”,确认流程是否正确。
    • 用示波器测量 SDA/SCL 的上升沿(t_r)与高期间的稳定度,评估上拉电阻是否合适。
    • 故障定位:连线过长、R 选太大、器件漏电流异常、内部上拉叠加、软件 I2C 时序不严格,都会在波形上留下“证据”。

1. I2C 简介:设计动机与总线模型

I2C(Inter-Integrated Circuit)是“一主多从”的两线制串行总线:

  • SCL(Serial Clock):主机产生时钟;
  • SDA(Serial Data):双向数据线;
  • 物理层采用开漏 + 上拉,逻辑层以“线与”为核心;
  • 速率层级:Standard(100kHz)、Fast(400kHz)、Fast+(1MHz)、High-Speed(3.4MHz,需特殊序列与加速器件)。

为什么只有两根线也能连很多设备?

  • 开漏结构允许所有设备并联在同一根 SDA/SCL 上;
  • 上拉提供“1”,任何设备都可以在需要时拉低为“0”;
  • 地址机制让主机按需与某个“从设备”对话;
  • 仲裁与拉伸保证在复杂场景下仍能稳定协作。

1.1 地址体系与寻址(7 位 vs 10 位)

  • 7 位地址:地址字节 = 7 位地址 + 1 位 R/W(R=1 读,W=0 写)。这是最常见的寻址方式。
  • 10 位地址:使用两字节地址序列。第一字节以 11110XX 开头(XX 为地址的高 2 位),并以写方向(R/W=0)选中目标;第二字节发送地址的低 8 位完成寻址。若随后进入读流程,需发送重复起始,再发送第一字节并置 R=1 进入读方向。此“组合格式(Combined Format)”是 10 位寻址的典型使用方式。
  • 工程提示:并非所有主机/从设备都支持 10 位地址,选型前需确认芯片手册。

1.2 保留地址与通用呼叫(General Call)

  • 规范中保留了一组特殊地址,例如通用呼叫地址(0x00,General Call)、高速模式主机码(High-Speed Master Code)等。具体列表与语义以最新规范与器件手册为准。
  • General Call 的作用:主机向总线上所有支持通用呼叫的设备广播命令(如复位、写命令等)。并非所有器件默认响应,且响应行为一般可配置。
  • 工程提示:请避免在常规扫描或调试时无意发送会被从设备当作“有效命令”的广播地址,以免造成不可预期的状态改变。

1.3 地址冲突与规避策略

  • 冲突场景:不同器件使用同一固定地址,或器件数量超过其可配置地址空间(例如仅有 A0/A1 地址引脚可选)。
  • 规避办法:
    • 选用带地址可配置引脚的器件,通过硬件引脚(如 A0/A1/A2)设定不同地址;
    • 使用 I2C 分段/多路复用器(Mux/Expander/Buffer)将总线分段,减少单段设备数量与电容;
    • 使用多个 I2C 控制器通道(I2C1/I2C2),或在硬件上进行电气隔离与分层;
    • 若为同类传感器,选用支持更大地址空间或可软件配置地址的型号。

1.4 总线扫描(Bus Scan)与风险

  • 扫描思路:主机遍历候选地址集合,向每个地址发送地址字节(通常以写方向),观察是否获得 ACK,以此判断“设备是否存在”。
  • 风险与建议:
    • 某些器件在接收地址后会立即进入命令接收流程,粗暴扫描可能被当作“写序列”的开端,引发副作用(如 EEPROM 的内部忙周期)。
    • 建议排除保留地址与广播地址(如 0x00 General Call),并在扫描时仅做“地址探测”,不要继续发送寄存器地址/数据字节。
    • 对于可能产生副作用的器件,优先使用厂家提供的安全探测方法或在空闲窗口进行非破坏性扫描;同时结合逻辑分析仪确认波形与应答行为。

2. 物理层原理:从器件电气到总线规律

2.1 为什么 I2C 用开漏 + 上拉(而不是推挽)

推挽输出同时具备“拉低”和“拉高”的能力。两个推挽并联,若一方输出高、另一方输出低,会直接短路,产生巨大电流,烧毁器件。

开漏输出只能拉低,不能拉高:

  • 当无人拉低时,上拉电阻把线拉到高电平;
  • 多设备并联时,“谁拉低谁赢”,不会出现拉高对拉低的硬冲突;
  • 这就是 I2C 的“线与”逻辑。它为多主仲裁、ACK 等机制提供了物理基础。

结论:开漏 + 上拉是“共享总线 + 安全并联”的必要条件。

补充(器件级与等效电路):

  • 推挽输出通常是 CMOS 的“上拉 PMOS + 下拉 NMOS”,两个推挽并联易形成“上拉与下拉短接”。
  • 开漏/开集相当于“只保留下拉器件”,高电平由外部上拉提供,避免硬冲突。
  • 等效图(文字版):VDD —[Rpullup]— 总线(SDA/SCL);各设备的下拉开关并联到总线。谁闭合谁把总线拉低。
  • 软件 I2C 仿真要点:不要用“输出1”拉高,应改为“切换为输入(高阻态)”,让上拉拉到高电平。

2.2 高阻态(High-Z)到底是什么、为什么重要

高阻态不是“高电平”,而是不驱动:

  • 输出级断开,管脚呈高电阻;
  • 此时电平由上拉、寄生电容、其他设备决定;
  • I2C 里,设备释放 SDA/SCL 进入高阻态,等价于“我不干预,由上拉把线拉高”。

重要性:

  • 保证“轮流发言”:当前发送方在采样周期释放 SDA,让接收方驱动 ACK;
  • 保证“安全并联”:避免推挽冲突;
  • 保证“事件检测”:在 SCL 高期间改变 SDA 才会被识别为起始/停止。

工程注意:

  • MCU若支持“开漏模式”优先使用;若不支持,用“输出0=拉低、输入=释放”模拟开漏。
  • 内部上拉(几十kΩ量级)不适合高速 I2C,且与外部上拉并联会改变等效 R,建议关闭或统一评估。

2.3 上拉电阻与总线电容:为何影响最高速率

总线可视为 R(上拉)与 C(走线/器件电容)的 RC 网络:

  • 上升沿近似:t_r ≈ 0.85 × R_pullup × C_bus;
  • R 越大或 C 越大,线从低到高的速度越慢;
  • 当 t_r 接近或超过时钟高电平持续时间时,接收端可能把“斜坡”当作不稳定电平,导致采样错误。

工程选型步骤(实操清单):

  1. 估算/测量总线电容 C_bus(走线、器件、连接器等的等效和)。
  2. 根据目标速率选择可接受的 t_r(参考规范与芯片手册)。
  3. 粗略反推 R 上限:R ≤ t_r / (0.85 × C_bus)。
  4. 综合功耗与下拉电流,初选 1kΩ~10kΩ;线更长/设备更多/速率更高 → R 更小。
  5. 用示波器验证 SDA/SCL 上升沿与高期间稳定度,迭代调优。

计算示例:

  • C_bus ≈ 200 pF、目标 t_r ≈ 300 ns(快速模式量级),R ≈ 300e-9/(0.85×200e-12) ≈ 1.76 kΩ → 可选 1.8kΩ/2.2kΩ 并验证。

注意事项:

  • R 太大 → 上升沿过慢 → 采样窗口内可能未达 VIH → 误判或“毛刺”。
  • R 太小 → 功耗增大、低电平电流大、器件压力大;多个上拉并联会使等效 R 更小。
  • 标准模式下总线电容建议 ≤400 pF;更大电容应考虑分段/缓冲。

2.4 输入阈值与抗抖:为何“在 SCL 高期间保持 SDA 稳定”

数字输入以 VIH/VIL 判定高/低。若在采样窗口(SCL 高期间)SDA 发生抖动/过渡:

  • 可能跨越阈值而被误判;
  • 也可能被识别为“起始/停止”(SCL 高期间对 SDA 的边沿定义了事件)。
    因此,I2C 规定:
  • 数据只能在 SCL 低期间改变;
  • 在 SCL 高期间必须保持稳定,接收端才可靠采样。

进一步(与规范参数关联):

  • tSU;DAT(数据建立时间):数据在 SCL 变高前必须稳定的最短时间,保障采样可靠。
  • tHD;DAT(数据保持时间):数据在 SCL 高期间需保持的最短时间,避免采样窗内翻转。
  • 选用带施密特触发的输入可提升抗抖能力(VIH/VIL有滞回)。

2.5 为什么“起始/停止”定义为“在 SCL 高期间改变 SDA”

  • 起始(Start):SCL 高期间,SDA 从高到低;
  • 停止(Stop):SCL 高期间,SDA 从低到高。
    物理依据:
  • 当 SCL 高且 SDA 产生边沿,控制器可在任何时刻识别“总线被占用或释放”的事件,无需等待时钟沿;
  • 与数据位(必须在 SCL 低期间改变)形成明确区分,避免歧义。

实现提示:

  • 许多控制器内置“起始/停止检测电路”,基于“SCL高期间的SDA边沿”触发状态机;
  • 软件 I2C 必须严格遵守“在 SCL 高期间制造起始/停止边沿”,否则会被当作数据变化而非事件。

2.6 为什么需要第 9 位应答(ACK/NACK)

应答位由接收方驱动,核心是“拉低=确认”:

  • 发送方在第 9 个时钟释放 SDA(高阻态);
  • 接收方拉低=ACK,表示成功接收且愿意继续;
  • 接收方保持高= NACK,表示无法接收或要求停止(读流程的最后一字节由主机发送 NACK)。
    物理意义:
  • 仍基于“线与”:谁拉低谁占优;
  • 用“0(拉低)”做确认,减少对上拉速度的依赖,可靠性更高。

语义补充(工程侧):

  • 写流程:从设备对地址与数据字节拉低 ACK 表示“已接收”;若NACK,多为地址不匹配、寄存器不存在或器件忙(如EEPROM页写)。
  • 读流程:主机在最后一字节发送 NACK 表示“到此为止”,再发 Stop 释放总线。
  • 驱动层应对每个字节检查 ACK/NACK 并做错误与流控处理。

2.7 电平转换(双向 MOS 管与外部上拉)

  • 原理简述:利用双向MOS管(如BSS138)与两侧上拉电阻实现自适应电平转换;当低电平出现在任一侧,MOS管导通把另一侧也拉低;高电平由各自上拉提供。
  • 注意事项:
    • 适用速度:轻负载与中速I2C表现良好;高速/大电容场景可能失真。
    • 两侧上拉电阻需分别选型,避免一侧过小导致另一侧受限。
    • 评估漏电与阈值,确保低电平能可靠传播且高电平能达 VIH。

2.8 总线拓扑与分段(Buffer/Mux/Expander)

  • 拓扑建议:避免“星形”与长支线(stub),尽量走直线/树形,缩短支线长度,减小等效电容。
  • 分段手段:
    • 使用 I2C Buffer/Repeater 将总线分段,降低每段电容、提升上升沿速度;
    • 使用多路复用器(Mux)按需选择设备支路,减少同段并联设备数;
    • 对长距离或不同电压域,结合电平转换与隔离器件。

2.9 内部上拉 vs 外部上拉(配置策略)

  • 内部上拉一般在几十kΩ:不适合高速;与外部上拉并联会改小等效R。
  • 策略:
    • 高速/重负载场景优先使用精确的外部上拉并关闭内部上拉;
    • 若板级已有多个上拉,计算并联等效,避免过小导致功耗与器件压力过大。

2.10 EMC/布局(实用建议)

  • 布线:SDA/SCL靠近地参考面、短且直;避免环路与不必要的过孔;支线尽量靠近主干。
  • 去耦:相关器件电源去耦充足,降低供电噪声耦合到总线。
  • 测试:示波器与逻辑分析仪联合使用,分别观察电气波形与协议序列。

3. 协议层与时序:规则与“为什么”

核心规则(再次强调):

  • SCL 高期间,SDA 必须稳定;
  • 起始/停止在 SCL 高期间对 SDA 做边沿;
  • 每 8 位数据后进入第 9 位应答周期;
  • 支持重复起始(不释放总线,切换方向或设备)。

ASCII 时序示意(文本版):
空闲:

SCL: ──────────────────(高)
SDA: ──────────────────(高)

起始:

SCL: ──────‾‾‾‾‾‾‾‾
SDA: ‾‾‾‾\__________

停止:

SCL: ──────‾‾‾‾‾‾‾‾
SDA: ________/‾‾‾‾‾‾‾

数据有效(仅在 SCL 低改变):

SCL: ___|‾|___|‾|___|‾|___
SDA: __0____1____0____1____

应答(第 9 位接收方驱动):

SCL: ___|‾|___|‾|___|‾|___|‾|
SDA: __d7___d6___...__d0___0   (0=ACK)

读写流程背后的“为什么”:

  • 写数据:发送地址+W → 寄存器地址 → 数据字节(每字节后看 ACK)。理由:寄存器型外设需要先设置写指针。
  • 读数据:地址+W 写寄存器地址 → 重复起始 → 地址+R → 逐字节读(最后一字节主机发 NACK)。理由:通知从设备读指针后切读方向,并用 NACK 告知“到此为止”。

3.1 帧结构与语义(每一位为何这样设计)

  • 起始(S):在 SCL 高期间,SDA 高→低;“告诉所有设备:总线被占用,开始传输”。
  • 地址字节:7 位地址 + 1 位 R/W;“谁匹配谁在第 9 位拉低 ACK”,未匹配者保持高。
    • R/W 位的意义:写方向(W=0)用于“设指针/写寄存器”;读方向(R=1)用于“读取数据”。
    • 10 位寻址(见 1.1):采用两字节地址前缀(11110xx)+低 8 位,仍遵循“每字节后 ACK”。
  • 数据字节:发送方在 SCL 低期间摆数(SDA 变化),接收方在 SCL 高期间采样。
  • 应答位(ACK/NACK):第 9 个时钟由接收方驱动(拉低=ACK,保持高=NACK)。语义是“继续/停止”的流控信号。
  • 停止(P):在 SCL 高期间,SDA 低→高;“释放总线,所有设备回到空闲”。
  • 重复起始(Sr):不发停止直接发起始,用于“切换方向/设备但保持总线占用”,避免其他主机插入。

设计动机:把“事件”(S/P/Sr)与“数据”严格分离到不同的边沿约束上,既易于检出,又可抗抖和避免歧义。

3.2 时序参数与可靠性(如何用示波器验证)

关键参数(名称取自规范,数值以具体模式与芯片手册为准):

  • tLOW/tHIGH:SCL 的低/高持续时间,决定比特周期的下/上半拍;慢设备可通过拉低 SCL 进行“拉伸”延长 tLOW。
  • t_r/t_f:SDA/SCL 上升/下降时间,受 R·C 影响(见第 2 章)。t_r 过大将影响 VIH 采样、导致毛刺或误判。
  • tSU;DAT/tHD;DAT:数据建立/保持时间——“在 SCL 变高前先稳定”“在 SCL 高期间不要变”。
  • tSU;STA/tHD;STA:起始建立/保持时间——“先满足稳定,再产生 SCL 高期间的 SDA 下降沿,并保持一段最小时间”。
  • tSU;STO:停止建立时间——“在 SCL 高期间,SDA 上升前的最小准备时间”。
  • tBUF:总线空闲时间——“一次传输结束到下一次起始之间的最小空窗”,给设备恢复状态机。

测量方法(实操):

  • 用示波器同时捕获 SCL/SDA,打开游标测 tLOW/tHIGH 与 t_r;观察 SCL 高期间 SDA 的稳定度和事件边沿清晰度。
  • 用逻辑分析仪验证协议序列,对齐“电气波形”与“协议解码”,检查是否存在拉伸、重复起始、意外起停。
  • 若出现“在 SCL 高期间 SDA 抖动”,优先排查上拉选型、噪声源与布线;必要时采用施密特输入或降低速率。

3.3 重复起始(Repeated START)的“必要性”

  • 寄存器型读必须“先写寄存器地址再读数据”。若发了 Stop,总线释放,其他主机可能插入设备访问,且部分设备会重置指针。
  • 用 Sr 在同一会话内“切换方向”(W→R),保证“指针设置”与“读出”紧密相连、不中断。
  • 风险与规避:部分旧设备对 Sr 边界敏感,应验证 tSU;STA/tHD;STA 是否满足其手册;驱动需在 Sr 后及时切换方向位。

3.4 多主仲裁与时钟同步(线与如何让规则成立)

  • 仲裁规则:在发送阶段,主机每一位都比较“自己期望的 SDA”与“实际 SDA”。若期望 1(释放)而实际 0(他人拉低),判负,立即退出传输。
  • 时钟同步:SCL 也是开漏线与——多主并行驱动时,“低电平由任何一方维持,高电平只有在所有人都释放后才成立”,于是有效高期间由最慢一方决定。
  • 结论:开漏 + 上拉天然提供“无需额外仲裁总线”的并行安全性;协议只需规定“何时比较”与“什么时候退出”。

3.5 协议级故障与恢复(总线被拉死怎么办)

  • 典型场景:从设备在 ACK 后异常,SDA 一直保持低,或在读流程中未按期释放;主机看见总线非空闲。
  • 复位策略:
    • 由主机在 SCL 上输出 9 个脉冲(模拟时钟),促使从设备把内部位/字节移出并释放 SDA;随后发送 Stop 形成总线复位。
    • 若仍被拉低,逐个断开分段(Buffer/Mux),定位故障支路;必要时上电重启设备。
  • 诊断要点:观察“谁在拉低”(拔掉上拉看是否回高、逐个断线)与“是否响应 SCL 脉冲”。

4. 总线事件与硬件响应:设备在“看到某种电平/边沿”时做什么

本节用“内部状态机 + 事件检测”的视角解释硬件为何如此响应,并给出驱动设计的工程建议。

4.1 内部状态机与事件检测电路(为何“看见某种边沿/电平”就触发)

  • 状态基本序列(抽象):Idle → Start_Detected → Address_Recv → Ack_Phase → Data_Transfer → Ack_Phase (循环) → Stop_Detected → Idle。
  • 事件检测原理:硬件持续监控“SCL 高期间的 SDA 边沿”。当检测到下降沿=Start,上升沿=Stop;若在 SCL 低期间的 SDA 边沿则视为“数据变化”,不触发事件。
  • 去毛刺与滤波:不少控制器在输入端有数字滤波(如采样窗口内多点一致才判定),降低毛刺导致的误触发。速率越高,上拉越小、布线越好,毛刺越少。
  • 事件标志与中断:常见有 Start/Stop/Addr/NACK/Arbitration_Lost/Overrun 等标志位,驱动可选择中断或轮询。不同芯片命名与清除方法各异,需按手册实现。

4.2 地址接收与 ACK 生成(“谁应该拉低第 9 位”)

  • 地址接收:收满 8 位(7 位地址 + R/W)后,从设备比较自身地址。匹配者在第 9 位拉低 ACK;不匹配者保持高(不驱动)。
  • ACK 的物理过程:接收方在第 9 个时钟把 SDA 拉低(开漏),发送方在此位释放 SDA(高阻态)。“谁拉低谁赢”的线与逻辑确保低电平成立。
  • NACK 触发常见原因:地址不匹配、寄存器不存在、设备忙(如 EEPROM 页写进行中)、读流程最后一字节由主机主动 NACK。
  • 工程建议:驱动对每字节检查 ACK/NACK,遇 NACK 立即进入错误/恢复路径(重试或 Stop)。

4.3 数据发送与采样策略(为何“低期间摆,高期间采”)

  • 发送方职责:只在 SCL 低期间改变 SDA(摆数);确保在 SCL 高期间稳定不变。
  • 接收方职责:在 SCL 高期间采样 SDA;常在高期间中点或近似中点采样(视实现)。
  • 读位细节:主机读时需释放 SDA(高阻态),让从设备在高期间驱动数据;在第 9 位应答周期,主机决定 ACK/NACK 并拉低或保持高阻态。
  • 内部移位寄存器:硬件常用移位寄存器在每个 SCL 周期移入/移出比特,结合事件标志触发下一个阶段。

4.4 时钟拉伸的交互与超时策略(“慢设备如何让快主机等我”)

  • 从设备拉伸:需要时间处理数据时,主动把 SCL 拉低保持。主机检测到“SCL 未回高”即进入等待(拉伸)状态。
  • 释放与恢复:从设备准备好后释放 SCL,上拉把其拉到高电平,传输继续。
  • 超时与容错:部分主机在硬件/驱动层设置拉伸等待上限(超时),超时后发 Stop 并上报错误,避免“死等”。
  • 风险:并非所有主机都完全支持拉伸(尤其软件 I2C 或简化外设),工程上需验证并在必要时降低速率或更换器件。

4.5 仲裁的硬件实现(何时判负、如何退出)

  • 判负条件:主机期望发送“1”(释放)而实际看到 SDA=0(他人拉低)时,判定仲裁失败(Arbitration Lost)。
  • 退出策略:硬件置位“仲裁失败”标志,停止当前发送,等待总线空闲或协议允许的恢复路径(如重新发起 Start)。
  • 同步副作用:SCL 的线与特性带来“时钟同步”,高期间由最慢一方决定,低期间任何一方拉低都成立,保障并行安全。

4.6 标志、中断与错误处理(驱动层如何“与硬件对话”)

  • 常见标志(抽象):Start、Stop、Addr_Matched、TX_Buffer_Empty、RX_Buffer_NotEmpty、Byte_Transfer_Finished、NACK_Flag、Arbitration_Lost、Overrun、Bus_Error 等。
  • 清除与顺序:不同芯片对标志的清除顺序严格(如“读状态寄存器后写控制寄存器”才清除)。务必按手册的时序操作,否则会出现“标志清不掉”或“误清”。
  • 中断 vs 轮询:低速/教学可轮询;量产建议使用中断(或 DMA)降低 CPU 占用、提升时序稳定性。
  • 错误上报:驱动应统一上报接口(错误码/事件),在应用层可统一处理(重试、降速、复位、日志)。

4.7 典型事件序列(写与读的硬件视角)

  • 写事务(寄存器型,从设备地址 A,寄存器 R,数据 D):
    1. Start → Addr(A|W) → ACK
    2. Data® → ACK
    3. Data(D0) → ACK → [可继续更多字节]
    4. Stop
  • 读事务(寄存器型,从设备地址 A,寄存器 R,读 N 字节):
    1. Start → Addr(A|W) → ACK
    2. Data® → ACK
    3. Repeated Start → Addr(A|R) → ACK
    4. 读数据:每字节后主机发 ACK,最后一字节发 NACK → Stop
  • 设备忙/不支持场景:在步骤 2/3 的 ACK 可能变为 NACK(寄存器不存在、写周期进行中等),驱动需走“错误路径”。

4.8 可靠性与调试(从“看到波形”到“修正策略”)

  • 看波形:示波器对齐 SCL/SDA,检查 SCL 高期间是否有 SDA 边沿(非预期事件)、t_r 是否过大、是否出现拉伸与仲裁失败瞬间。
  • 看协议:逻辑分析仪解码,与电气波形对齐,定位 NACK 发生位置与原因(地址错、寄存器错、设备忙)。
  • 修正策略:优化上拉与布线、降低速率、打开施密特输入、加入超时与总线复位(9 脉冲 + Stop)、分段定位故障器件。

这些响应都根植于物理层:

  • 开漏支撑“谁拉低谁赢”;
  • 上拉提供“默认高电平”;
  • 在 SCL 高期间对 SDA 的边沿被定义为“事件”,因此硬件可随时检测;
  • 数据必须在 SCL 低期间改变,避免与事件冲突。

5. I2C 数据传送:格式与流程(含推导理由)

5.1 7 位地址帧格式

  • 起始 + 地址字节(7 位地址 + 1 位 R/W)+ ACK;
  • 之后进入读/写数据阶段(可连续字节传输,每字节后应答)。

补充说明:

  • 10 位地址(参见 1.1)采用两字节前缀(11110xx)+低 8 位,同样遵循“每字节后 ACK”的基本节奏。
  • 保留地址与通用呼叫(参见 1.2)不用于普通设备寻址,驱动需避免误用。

5.2 写流程(示例与解释)

示例:向设备 0x48 的寄存器 0x01 写入 0xAB。

  • Start;
  • 发送 0x48 + W(R/W=0),等待从设备 ACK(确认地址);
  • 发送寄存器地址 0x01,等待 ACK(设置写指针);
  • 发送数据字节 0xAB,等待 ACK(确认接收);
  • Stop(释放总线)。
    推导理由:寄存器型器件需要先指定目标寄存器,再写入数据。

时序与判定(工程要点):

  • 每字节结束进入第 9 位应答周期:主机释放 SDA(高阻态),从设备拉低表示 ACK;保持高表示 NACK。
  • 若收到 NACK:可能为寄存器不存在、写保护、设备忙(如 EEPROM 页写中)。驱动应进入错误路径(重试或停止)。
  • 页写场景:EEPROM 等写入后会占用内部写周期,期间可能对后续访问 NACK 或拉伸。建议 ACK 轮询或等待规定写周期。

5.3 读流程(示例与解释)

示例:读取设备 0x48 的寄存器 0x00 的 2 字节。

  • Start;
  • 发送 0x48 + W,ACK;
  • 发送寄存器地址 0x00,ACK(设置读指针);
  • Repeated Start;
  • 发送 0x48 + R,ACK;
  • 读第 1 字节后主机发送 ACK(还要继续);
  • 读第 2 字节后主机发送 NACK(到此为止);
  • Stop。
    推导理由:读指针设置必须用写方向,随后切读方向;最后一字节用 NACK 告知从设备停止发送。

关键语义(避免误解):

  • 主机在“最后一字节”发送 NACK,是对“从设备:请停止继续发送”的明确信号;随后通过 Stop 释放总线。
  • 若主机误发 ACK,部分设备会继续输出下一字节数据(如内部指针递增),导致读超量或指针错位。

5.4 位级时序对齐与采样点(文字波形)

写一个比特(发送方摆数,接收方采样):

SCL: ___|‾‾‾|___            (低→高→低)
SDA: __b________             (b 在 SCL 低期间放好;高期间保持不变)
采样:接收方在 SCL 高期间中点采样 SDA。

读一个比特(主机释放,设备驱动):

SCL: ___|‾‾‾|___
SDA: ‾‾x________             (主机在 SCL 低期间释放SDA=高阻态;从设备在高期间驱动为0/1)
采样:主机在 SCL 高期间中点读取 SDA。

应答周期(第 9 位):

SCL: ___|‾‾‾|___
SDA: ‾‾0/‾‾‾‾‾‾             (接收方拉低=ACK;发送方释放)
规则:发送方必须释放SDA,让接收方拥有第9位的驱动权。

5.5 伪状态机(轮询/中断版的工程骨架)

抽象事件(来自状态寄存器或中断):Start, AddrMatched, ByteTXDone, ByteRXReady, NACK, ArbitrationLost, Stop。

轮询版(文字逻辑):

  • 等待 Start → 发送 地址字节 → 等 ByteTXDone;
  • 检查 NACK?是→错误路径;否→进入数据阶段;
  • 写数据:每字节“发送→等完成→查 NACK”,出错则 Stop/重试;
  • 读数据:每字节“等 RXReady→读出→发 ACK/NACK(最后一字节NACK)”,结束后 Stop;
  • 任何时刻若 ArbitrationLost → 释放并等待空闲后重启事务。

中断版(事件驱动):

  • ISR 根据标志分发:AddrMatched → 配置方向;ByteTXDone → 发送下一字节或发 Sr/Stop;ByteRXReady → 读取并决定 ACK/NACK;NACK/ArbLost → 进入容错路径;Stop → 通知上层完成。
  • DMA 可用于连续写/读字节,仍需在边界位(如最后一字节)精确处理 ACK/NACK 与 Stop。

5.6 错误分支与退避/重试策略

  • ACK 失败:检查地址/寄存器/设备忙;可设定最大重试次数(如 3~5 次),失败后上报并进入“总线复位策略”。
  • 拉伸超时:主机等待 SCL 释放超过阈值→发 Stop,上报错误;可选择降低速率或延长超时,再次尝试。
  • 仲裁失败:立即停止当前事务,随机退避(延时)后重试,避免与其他主机持续冲突。
  • 总线被拉死:执行 9 脉冲复位(见 3.5),或分段排查故障支路;必要时重启设备电源。

5.7 重复起始与最后一字节 NACK 的细节

  • Sr 的生成:在不发 Stop 的情况下,于 SCL 高期间制造 SDA 的下降沿;确保满足 tSU;STA/tHD;STA。
  • 最后一字节 NACK:在第 9 位,主机保持 SDA 高阻态(不拉低),随后生成 Stop;从设备据此停止继续输出。
  • 边界风险:未能及时释放/拉低会导致设备误解流控信号;驱动需在 ISR/DMA 边界精确时序处理。

5.8 与 SMBus 的接口差异(简述)

  • SMBus 引入超时、PEC(包错误校验)等约束,部分设备的读写命令语义与 I2C 略异;
  • 在混用场景需参照数据手册实施正确的命令格式与时序限制(详细差异可在进阶章节展开)。

6. 设计与选型:上拉电阻、总线电容与速率

本节把“R·C 决定上升沿速度”的原理落到工程可操作的选型流程与验证闭环。

6.1 速率模式与上升沿目标(以规范/芯片手册为准)

  • Standard-mode(SM,100kHz):常见上升沿目标 t_r ≤ 1000ns、下降沿 t_f ≤ 300ns(取决于规范与芯片手册)。
  • Fast-mode(FM,400kHz):常见 t_r ≤ 300ns、t_f ≤ 300ns。
  • Fast-mode Plus(FM+,1MHz):常见 t_r ≤ 120ns、t_f ≤ 120ns。
  • High-speed(HS,3.4MHz,需特殊握手与链路约束):t_r 要求更严,且拓扑/器件能力约束更强,工程上需谨慎评估。
    结论:目标速率越高,对 t_r 的要求越严;因此 R 越可能需要更小,布线与电容更需优化。

6.2 上拉电阻选型流程(从计算到验证)

  1. 估算/测量总线电容 C_bus(器件引脚、走线、连接器、跨板线缆等的等效和)。
  2. 依据速率选择目标 t_r(参考规范与芯片手册)。
  3. 计算上限:R ≤ t_r / (0.85 × C_bus)。该近似来自 RC 上升边沿到达阈值的经验系数。
  4. 约束校核:检查器件的灌电流能力 I_OL 与低电平输出电压 VOL 限值;R 越小,低电平灌电流 I_pullup = VDD/R 越大,需确保不超规格。
  5. 位置与并联:确认板级是否已有上拉,计算并联等效 R;拉得太小会增大功耗与噪声敏感度。
  6. 实测与迭代:用示波器测 SCL/SDA 的 t_r/t_f、VIH/VIL 达标性、是否有过冲/振铃;按需微调 R 或进行分段/布线优化。

6.3 计算示例(仅作工程起点,最终以实测为准)

  • 假设 C_bus ≈ 100 pF:
    • 100kHz(t_r≈1000ns)→ R ≈ 1000e-9/(0.85×100e-12) ≈ 11.8kΩ → 可选 10kΩ~12kΩ 并验证。
    • 400kHz(t_r≈300ns)→ R ≈ 300e-9/(0.85×100e-12) ≈ 3.5kΩ → 可选 3.3kΩ/3.9kΩ。
    • 1MHz(t_r≈120ns)→ R ≈ 120e-9/(0.85×100e-12) ≈ 1.4kΩ → 可选 1.5kΩ/1.8kΩ。
  • 假设 C_bus ≈ 200 pF:
    • 100kHz(t_r≈1000ns)→ R ≈ 5.9kΩ → 可选 5.6kΩ/6.8kΩ。
    • 400kHz(t_r≈300ns)→ R ≈ 1.76kΩ → 可选 1.8kΩ/2.2kΩ。
      提示:示例仅提供初值,真实项目必须以波形测量与规范阈值(VIH/VIL、VOL等)校核为准。

6.4 总线电容的测量与估算

  • 直接测量:LCR 表/阻抗分析仪测量总线电容(断电且断开器件主动驱动),为最佳但成本高。
  • 波形反推:在已知 R 的前提下,用示波器测得 t_r,反推 C_bus ≈ t_r/(0.85×R)。
  • 工程估算:汇总器件输入电容(datasheet给出)、连接器与线缆的分布电容、PCB走线的等效电容。若不便精算,可粗估“每段线缆/走线贡献若干 pF”,并以实测修正。

6.5 分段/Buffer/Mux 的应用场景与影响

  • 分段动机:当 C_bus 较大或设备过多时,单段难以满足 t_r;用 I2C Buffer/Repeater 把总线分成多个轻负载段。
  • Mux 选择:用 I2C 多路复用器按需连接支路,减少同段并联设备数,提升上升沿速度与稳定性。
  • 影响评估:Buffer/Mux 引入传播延迟与电气特性差异(如门限/方向性),需验证其在目标速率下的鲁棒性。

6.6 长距离、跨板与电平转换(EMC 配合)

  • 走线:主干短且直、靠近地参考面,支线(stub)尽量短,减少反射与等效电容。
  • 线缆:尽量使用屏蔽或绞合方案,并提供良好接地与防静电路径;注意共模噪声耦合。
  • 串联阻尼:在 SDA/SCL 近源端串 22Ω~100Ω 级小电阻可抑制过冲/振铃(需实测优化)。
  • 电平转换:跨电压域用双向 MOS 管/专用电平转换芯片;两侧上拉分别选型,验证低电平传播与高电平 VIH 达标。

6.7 上拉电阻的放置与多点并联

  • 放置位置:上拉一般放在主干或段端,减少被动支线造成的额外 RC 影响。
  • 多点并联:多个上拉并联会降低等效 R(R_eq = 1 / Σ(1/R_i)),导致灌电流增大与功耗上升;建议统一规划,仅保留必要的上拉。

6.8 选型闭环(计算 → 实装 → 测量 → 修正)

  • 计算:按目标速率与 C_bus 计算 R 初值;校核器件 I_OL/VOL 能力。
  • 实装:合理放置上拉与布线,清点并联拉阻;必要时引入分段或 Mux。
  • 测量:示波器测 t_r/t_f 与事件边沿清晰度;逻辑分析仪校验协议稳定性与 NACK 分布。
  • 修正:微调 R、优化布线、降低速率或分段;必要时引入施密特输入与串联阻尼,直至满足规范与可靠性。

6.9 典型 R 参考区间(经验值,最终以实测为准)

  • C_bus ≤ 100 pF:
    • 100kHz:4.7kΩ~10kΩ;
    • 400kHz:2.2kΩ~4.7kΩ;
    • 1MHz:1.0kΩ~2.2kΩ。
  • C_bus ≈ 200~400 pF:
    • 100kHz:3.3kΩ~6.8kΩ;
    • 400kHz:1.5kΩ~3.3kΩ;
    • 1MHz:需分段或更小 R(功耗与器件能力约束加剧)。
      说明:这些区间是经验初值,务必结合 I_OL/VOL 能力与波形测量校核。

6.10 约束与陷阱(再次提醒)

  • R 过大 → 上升沿慢 → 采样窗内未达 VIH → 误判/毛刺/协议错误。
  • R 过小 → 灌电流大(I=VDD/R)→ 噪声敏感度高、功耗上升、器件压力增大(检查 I_OL 与 VOL)。
  • 跨域/长距 → 优先分段与电平转换,辅以屏蔽/接地与串联阻尼;必要时降低速率。

7. 故障与避坑清单(对应“为什么会错”)

本节把“常见异常现象”与“根因路径”结构化,给出可执行的排查与恢复清单。

7.1 典型症状总览(先看现象,再走路径)

  • 上升沿过慢、采样误判、随机 NACK;
  • 过冲/振铃、毛刺触发错误的起始/停止;
  • SCL 长时间低保持(拉伸卡死)、SDA 始终低(总线卡死);
  • 多主场景下的仲裁丢失、事务被中断;
  • 热插拔/ESD 后设备不响应、地址 NACK;
  • 读流程最后一字节 NACK被误判为错误(实际是正常终止语义)。

7.2 波形异常与根因(示波器优先)

  • 上升沿过慢:R 过大、C_bus 过大、并联上拉过少、布线/线缆过长;→ 降低 R、分段/缓冲、优化布线、降速。
  • 过冲/振铃:走线过长、阻抗不匹配、源端驱动过硬;→ 源端串 22Ω~100Ω 阻尼、靠地参考面走线、缩短 stub、适度降低速率。
  • 毛刺/抖动:电磁干扰、地弹、跨域转换不透明;→ 屏蔽/滤波、改善接地、选择更稳健电平转换、必要时启用施密特输入。
  • 误触发起始/停止:SCL 高期间 SDA 产生不期望边沿;→ 优化滤波/阻尼、提高抗干扰、在软件侧严格遵守“低时改、高时采”。
  • SCL 拉伸超长:从设备内部处理耗时或故障;→ 主机设置超时门限,进入恢复流程(见 7.4/7.6)。

7.3 ACK/NACK 异常定位清单

  • 地址字节 NACK:设备未上电/复位中、地址不匹配、线缆/焊点接触不良、上拉/VIH/VIL 不达标、拉伸导致主机时序乱。
  • 数据字节 NACK:写保护(如 EEPROM)、页写尚在进行、寄存器不可写/越界、命令长度/校验不符、设备忙。
  • 读流程最后一字节主机发送 NACK:属于“正常终止”语义,用于告诉从设备“读到此为止”。误把此 NACK 当错误会导致错误路径误触发。

7.4 拉伸与超时(主机侧策略)

  • 检测:在 SCL 释放为高后,持续读 SCL 直到真的变高;若持续低保持,视为拉伸。
  • 超时:设置不同事务的最大拉伸时间(如寄存器读/写、页写轮询),超时后进入恢复/重试。
  • 恢复策略:
    • 轻故障 → 再次尝试当前字节或重复起始重新发起;
    • 中故障 → 发 STOP、等待总线空闲,退避(指数回退 + 随机抖动);
    • 重故障 → 进入 7.6 的“总线卡死”恢复流程(SCL 脉冲 + 设备复位)。

7.5 仲裁丢失与多主冲突

  • 检测:发送者在期望释放为“1”时读到“0”,判定仲裁失败;硬件外设通常有仲裁丢失标志位。
  • 响应:立即停止本事务(硬件自动),等待总线空闲;软件侧加入退避窗口,避免持续冲突。
  • 设计:明确多主需求、速率与优先级;在协议层为重要事务预留重试与确认机制。

7.6 总线卡死(stuck bus)与恢复流程

  • 常见场景:
    • SDA 被设备拉低不释放(设备内部状态机异常、ESD 后锁死、热插拔瞬态未恢复);
    • SCL 长期低保持(设备持续拉伸或硬件短路)。
  • 恢复步骤(主机侧):
    1. 生成 9~16 个 SCL 脉冲尝试把设备内部位移出应答窗口;
    2. 在 SCL 高期间强制产生 STOP(SDA 低→高);
    3. 软重置 I2C 外设;必要时对相关设备拉复位脚;
    4. 若仍失败,执行上电序列复位(断电重启)、降低速率后重试。
  • 预防:电源时序稳定、ESD 路径与 TVS、防热插拔瞬态;页写/忙轮询用超时与 NACK 检测。

7.7 上拉、电平转换与并联陷阱

  • 多处上拉并联 → 等效 R 过小 → 灌电流大、功耗高、噪声敏感度上升;统一规划,仅保留必要上拉。
  • 跨电压域电平转换不透明 → 边沿畸变、阈值不匹配;选择专用 I2C 双向转换或合适 MOS 方案,并两侧独立校核上拉。
  • 内部上拉 + 外部上拉叠加 → 等效 R 被拉小;核对芯片内部上拉状态,按需关闭或重新选型。

7.8 布线与 EMC 隐患

  • 主干短直、靠地参考;支线(stub)尽量短,减少等效电容与反射。
  • 线缆优先屏蔽/绞合,良好接地与防静电路径,降低共模耦合。
  • 近源端串联小电阻(22Ω~100Ω)作阻尼,实测优化过冲/振铃。
  • 在接口侧布置 TVS/滤波器件,提升 ESD/浪涌鲁棒性。

7.9 软件/驱动侧典型坑

  • 位级规则违背:在 SCL 高期间改变数据、起始/停止边沿不在高期间;
  • 拉伸未处理:未轮询 SCL 真实为高就继续推进状态;
  • 忽略 NACK:地址/数据 NACK 未进入错误路径,导致指针错位或事务悬挂;
  • 复用引脚未配置开漏/上拉,导致推挽冲突或 VIH/VIL 不达标;
  • 中断/DMA 同步问题:状态位未清、竞争条件导致字节丢失;
  • 读流程最后一字节未发 NACK,导致从设备等待继续输出、总线语义混乱。

7.10 调试方法与测量技巧

  • 示波器触发:以“起始/停止”“SCL 高期间的 SDA 边沿”“过冲/毛刺阈值”触发,测 t_r/t_f、VIH/VIL 达标;
  • 逻辑分析仪:抓帧结构与事件序列,标注 NACK 分布与拉伸位置;
  • 步进实验:更换不同 R、增减线缆长度、切换速率,观察异常是否随 RC 改变而收敛;
  • 故障重现:构造温度/供电抖动/ESD 等极端条件,验证鲁棒性与恢复策略有效。

7.11 验收与回归测试清单

  • 上电/下电序列(含热插拔);
  • 全速率档位(SM/FM/FM+)在不同温度与电压下的稳定性;
  • 拉伸/超时阈值与恢复流程;
  • 多主冲突与仲裁丢失的退避策略;
  • ESD/浪涌与电源瞬态下的可恢复性;
  • 批量差异:不同供应商/料批的输入电容与门限差异验证。

7.12 速查表:症状 → 动作

  • “随机 NACK、多在高温/长线缆” → 降速 + 降 R + 分段;
  • “起始/停止误触发” → 加阻尼 + 缩短支线 + 优化接地/屏蔽;
  • “SCL 一直低” → 判断拉伸 vs 短路,设超时,执行 SCL 脉冲 + STOP 恢复;
  • “SDA 一直低” → 设备锁死,按 7.6 流程恢复;
  • “多主仲裁丢失频繁” → 增加退避随机窗,调整优先级、降低速率或分段;
  • “读流程最后字节被误判错误” → 确认主机发送 NACK 属于正常终止语义。

8. 软件 I2C 与 硬件 I2C(工程侧的“取舍与原理”)

本节从取舍、架构与鲁棒性角度展开,给出两种实现的工程要点与常见坑位。

8.1 何时选软件 I2C / 硬件 I2C

  • 软件 I2C(bit-banging):资源紧张、简单器件数量少、速率要求不高、教学/快速验证场景;
  • 硬件 I2C(控制器外设):量产、速率较高、拉伸/仲裁/中断/DMA等复杂需求、功耗与稳定性优先。

8.2 软件 I2C 的工程骨架

  • 时序驱动:严格“低时改、高时采”;所有边沿均在 SCL 高期间定义事件(起始/停止)。
  • 拉伸检测:在 SCL 释放为高后循环读 SCL,直到真正变高;设定超时门槛。
  • 仲裁检测(多主场景):当期望释放为“1”却读到“0”时,判定仲裁失败,撤销事务并退避。
  • 错误路径:地址/数据 NACK、拉伸超时、仲裁丢失、总线卡死(见第 7 章)均需明确处理分支与重试策略。
  • 速率校准:通过延时与测量实波形的方式闭环校准位级时序。

8.3 硬件 I2C 的寄存器/中断要点

  • 控制寄存器:使能、速率选择、起始/停止触发、ACK/NACK 配置、拉伸支持。
  • 状态/标志位:起始/停止检测、TXE/RXNE(缓冲空/数据就绪)、NACK、仲裁丢失、总线错误、拉伸。
  • 中断流:基于事件驱动的字节级/帧级状态机;错误中断进入恢复路径,数据中断推进事务。
  • DMA:大块数据读写(如摄像头/触控子系统不常见但某些芯片支持),要处理边界与最后一字节 NACK 的语义。

8.4 OS/调度对软件 I2C 的影响

  • 抢占与时钟抖动会破坏位级时序;尽量在临界区或高优先级任务中执行关键段。
  • 定时器分辨率有限导致 SCL 周期不稳定;实际测量并适配延时参数以达到目标速率。

8.5 复用/开漏与引脚配置

  • 开漏输出 + 上拉是 I2C 的物理基础;推挽误用会与其他设备冲突(见第 2 章)。
  • 某些芯片提供“施密特输入”、滤波、弱/强上拉选项;结合 RC 和 EMI 做实测优化。

8.6 自检与自修(上电与异常场景)

  • 上电自检:扫描目标地址、统计 NACK,检查 SCL/SDA 是否被常低;必要时执行“9~16 个 SCL 脉冲 + 强制 STOP”。
  • 异常自修:拉伸超时、仲裁失败、页写忙等进入重试/退避策略;长期失败时降速/分段或提示硬件检查。

8.7 代码骨架(在原简例基础上补全关键点)

逻辑要点:

  • 等待 SCL 真正变高(拉伸检测);
  • 在读写字节后检查 ACK,并根据错误策略重试或退出;
  • 提供重复起始与强制 STOP 的通用帮助函数。
// 仅示意:在原简例基础上加入拉伸检测与错误路径钩子
static int scl_wait_high(uint32_t timeout_us){
    uint32_t t0 = micros();
    while(GPIO_Read(SCL)==0){ if(micros()-t0>timeout_us) return -1; }
    return 0;
}

typeof void i2c_start(void){
    sda_release(); scl_release(); dly();
    sda_low(); dly();
    scl_low();
}

typeof void i2c_stop(void){
    sda_low(); dly();
    scl_release(); if(scl_wait_high(1000)<0){ /* 拉伸过长,进入恢复 */ }
    dly(); sda_release(); dly();
}

typeof int i2c_write_bit(int b){
    if(b) sda_release(); else sda_low(); dly();
    scl_release(); if(scl_wait_high(1000)<0) return -1; // 拉伸
    dly();
    scl_low(); dly();
    return 0;
}

typeof int i2c_read_bit(void){
    int b; sda_release(); dly();
    scl_release(); if(scl_wait_high(1000)<0) return -1;
    dly(); b = GPIO_Read(SDA);
    scl_low(); dly();
    return b;
}

typeof int i2c_write_byte(uint8_t x){
    for(int i=7;i>=0;--i){ if(i2c_write_bit((x>>i)&1)<0) return -1; }
    sda_release(); dly();
    scl_release(); if(scl_wait_high(1000)<0) return -1;
    int ack = GPIO_Read(SDA); // 0=ACK, 1=NACK
    scl_low(); dly();
    return ack; // 上层根据 ack 决定重试/退出
}

typeof int i2c_read_byte(uint8_t* px, int send_ack){
    uint8_t x=0; int bv;
    for(int i=7;i>=0;--i){ bv=i2c_read_bit(); if(bv<0) return -1; x|=(bv&1)<<i; }
    if(send_ack==0) sda_low(); else sda_release(); dly();
    scl_release(); if(scl_wait_high(1000)<0) return -1;
    scl_low(); dly(); sda_release();
    *px = x; return 0;
}

注:骨架仅示意“拉伸检测/ACK 分支/错误路径钩子”的位置,真实项目需结合芯片与 OS 调度做更细化的容错。


9. I2C 进阶:多主仲裁与拉伸的“为什么”

本节把“谁拉低谁赢”的物理事实在多主/拉伸场景下展开为可实现、可测量、可恢复的工程策略。

9.1 多主仲裁的位级机制(发生在 SCL 高期间)

  • 开漏 + 线与:多个主机同时发送时,线被任何一个拉低即为“0”;所有主机都释放时才为“1”。
  • 判定规则:发送者在 SCL 高期间比较“期望=1(释放发送‘1’)”与“实际=0(总线被他人拉低)”,判定“仲裁丢失”。
  • 发生位置:可发生在地址/数据位、读/写位甚至重复起始期间(关键在 SCL 高期间的 SDA 观测)。
  • 硬件标志:多数 I2C 外设提供“Arbitration Lost”标志;硬件通常会自动停止当前事务或禁止继续驱动。

9.2 时钟同步(Clock Synchronization)

  • 低电平保持:谁拉低更久,SCL 低就持续更久(总线低电平持续时间由“最慢/最长低保持”的主机决定)。
  • 高电平长度:任何主机提前拉低都会缩短高电平长度(总线高电平由“最早拉低”的主机决定)。
  • 结论:总线节拍会“自动同步到最慢的主机”,以避免更快主机破坏时序稳定。

9.3 仲裁失败后的策略(公平与退避)

  • 立即让路:检测到仲裁丢失后,停止当前事务,等待总线空闲。
  • 退避与随机窗:引入指数退避 + 随机抖动,避免两主机“同时再次冲突”。
  • 公平性:为关键事务设置优先级或限流策略;必要时用上层协议(令牌/队列)协调访问。
  • 总线空闲判定:SCL、SDA 都为高且持续≥ tBUF(参见第 3 章时序参数),再重新发起起始。

9.4 拉伸(Clock Stretching)与交互

  • 语义:从设备拉低 SCL 表示“我还在忙,请等我”;主机必须在 SCL 真正变高后才推进下一位(见第 8 章代码骨架的 scl_wait_high)。
  • 约束:I2C 允许拉伸但未强制超时;SMBus 对拉伸/超时有明确上限,工程上建议设置事务级超时,避免无限等待。
  • 多主交互:拉伸与仲裁同时存在时,总线低保持会由“拉伸方/最慢主机”决定,其他主机在高期间同样要观测仲裁。
  • 设备差异:部分器件(尤其低端或老旧)拉伸行为不稳定,需用示波器与逻辑分析仪结合验证。

9.5 超时与恢复(与第 7 章联动)

  • 超时门槛:按事务类型设上限(如寄存器读写、EEPROM 页写),超时进入恢复分支。
  • 恢复动作:
    1. 尝试重复起始重新发起;
    2. 发 STOP 并等待总线空闲,进入退避窗口;
    3. 若总线卡死(SDA/SCL 常低),执行“9~16 个 SCL 脉冲 + 强制 STOP”,软重置外设或对设备拉复位脚(详见 7.6)。
  • 降速与分段:当拉伸频繁且影响整体吞吐时,考虑降低速率或用 Buffer/Mux 分段(见第 6 章)。

9.6 测试与验证建议

  • 冲突重现:让两个主机在同一时刻发起事务,观察仲裁丢失标志与退避策略是否有效。
  • 边界测试:在不同温度/电压与线缆长度下,测量有效 SCL 周期是否随最慢主机变化而稳定。
  • 拉伸场景:针对可能拉伸的器件(如 EEPROM 页写、复杂传感器)测量拉伸分布与超时恢复闭环。

9.7 常见误区与陷阱

  • 误把读最后字节的主机 NACK 当错误,导致错误路径误触发(见第 5 章的读流程语义)。
  • 只在软件层“延时等待”而不检测 SCL 真正为高,导致拉伸下时序破坏。
  • 忽略仲裁标志或无退避,导致两主机长期冲突并降低吞吐。
  • 把 I2C 与 SMBus 规则混用(超时/电压/命令语义差异),导致在某些设备上不兼容(见第 5 章 SMBus 简述)。

10. 常见外设交互例子(工程模板与注意事项)

本节给出“寄存器读/写”的通用模板,并以温度传感器、EEPROM、ADC/DAC为例,强调重复起始、最后字节 NACK、页写轮询与拉伸等要点。

10.1 通用模板(7 位地址,Reg 寄存器地址)

  • 写寄存器:
    1. START → Address(Write) → ACK → Reg → ACK → Data… → ACK → STOP;
    2. 若 NACK:进入错误路径(退避/重试/降速)。
  • 读寄存器(最常见的“写指针后读”):
    1. START → Address(Write) → ACK → Reg → ACK;
    2. Repeated START → Address(Read) → ACK → Data… → 主机对最后一字节发送 NACK → STOP。
  • 注意:主机对“最后一字节”发送 NACK 是“正常终止”语义(见第 5 章)。

10.2 温度传感器(如 LM75/TMP102 等寄存器型)

  • 典型流程:写寄存器指针 → 重复起始 → 读两个字节(高字节先)→ 最后一字节主机 NACK → STOP。
  • 要点:
    • 寄存器指针常有“配置/温度/阈值”不同地址;
    • 连续读可能需要内部指针自动增加;
    • 拉伸:部分器件在转换更新期间可能拉伸或暂时 NACK,应做重试/轮询。
  • 伪代码(简化):
int tmp_read16(uint8_t addr7, uint8_t reg, uint16_t* pv){
    if(i2c_write_byte((addr7<<1)|0)) return -1; // W
    if(i2c_write_byte(reg)) return -1;
    i2c_start();
    if(i2c_write_byte((addr7<<1)|1)) return -1; // R
    uint8_t hi, lo; if(i2c_read_byte(&hi, 0)<0) return -1; // ACK 中间字节
    if(i2c_read_byte(&lo, 1)<0) return -1;                 // NACK 最后一字节
    *pv = (hi<<8)|lo; i2c_stop(); return 0;
}

10.3 EEPROM(24Cxx 系列)

  • 写单字节:START → Addr(W) → ACK → MemAddr → ACK → Data → ACK → STOP → 进入“页写内部周期”,此时设备可能 NACK/拉伸。
  • 页写:
    • 页大小常见 8/16/32 字节,跨页会自动滚动或截断;
    • 写后需“ACK Polling”:循环发送 Addr(W),若返回 ACK 表示内部写完成;
    • 忽略 ACK Polling 会导致后续读写失败或数据不一致。
  • 读:常用“写指针后读”,大量读可用多字节持续读(指针自动递增);最后一字节主机 NACK。
  • 伪代码(页写轮询示意):
int eep_page_write(uint8_t addr7, uint16_t mem, const uint8_t* buf, int n){
    if(i2c_write_byte((addr7<<1)|0)) return -1;
    if(i2c_write_byte(mem>>8)) return -1;     // 高地址(具体器件可能只有8位)
    if(i2c_write_byte(mem&0xFF)) return -1;   // 低地址
    for(int i=0;i<n;i++) if(i2c_write_byte(buf[i])) return -1;
    i2c_stop();
    // ACK Polling:设备忙时会 NACK
    for(int retry=0; retry<100; ++retry){
        i2c_start();
        int ack = i2c_write_byte((addr7<<1)|0);
        if(ack==0){ i2c_stop(); return 0; }  // 设备就绪
        i2c_stop(); delay_ms(1);
    }
    return -2; // 超时
}

10.4 ADC/DAC(寄存器配置 + 数据通路)

  • ADC(如配置采样率/通道):写配置寄存器 → 重复起始 → 读转换结果(可能两字节或更多);留意拉伸与数据就绪指示。
  • DAC:写输出寄存器(可能包含电源/缓冲控制位);检查 ACK 与错误路径,避免速率太高导致设备忙。
  • 要点:
    • 对齐大小端与符号位;
    • 如果支持“数据就绪”位或中断,优先用事件触发而非盲读;
    • 在高采样率下,保证上拉与时序达标(见第 6 章)。

10.5 一般化读写模板(可抽象为驱动 API)

  • 写寄存器:
int i2c_write_reg(uint8_t addr7, uint8_t reg, const uint8_t* d, int n){
    if(i2c_write_byte((addr7<<1)|0)) return -1;
    if(i2c_write_byte(reg)) return -1;
    for(int i=0;i<n;i++) if(i2c_write_byte(d[i])) return -1;
    i2c_stop(); return 0;
}
  • 读寄存器:
int i2c_read_reg(uint8_t addr7, uint8_t reg, uint8_t* d, int n){
    if(i2c_write_byte((addr7<<1)|0)) return -1;
    if(i2c_write_byte(reg)) return -1;
    i2c_start();
    if(i2c_write_byte((addr7<<1)|1)) return -1;
    for(int i=0;i<n;i++){
        int ack = (i<(n-1))?0:1;           // 最后字节 NACK
        if(i2c_read_byte(&d[i], ack)<0) return -1;
    }
    i2c_stop(); return 0;
}

10.6 常见注意事项

  • 重复起始(Sr)不可省略:避免 STOP 后设备指针/状态机被重置。
  • 最后一字节主机 NACK 是“正常终止”,不可误判为错误。
  • EEPROM 页写忙:用 ACK Polling 轮询完成;读写前后校验数据一致性。
  • 拉伸/超时:在读写函数里实现拉伸检测与超时恢复(见第 7、8、9 章)。
  • 10 位地址与保留地址:参考第 5 章的地址语义与注意事项。

11. 总结:把“为什么”记住,I2C 就稳了

  • 开漏 + 上拉是共享总线的电气前提;
  • 高阻态是“让位与回到高”的方式;
  • SCL 高期间的 SDA 边沿是事件(起始/停止);
  • 数据在 SCL 低期间改变、在高期间采样是为了抗抖与避免歧义;
  • 第 9 位应答用“拉低=确认”落地在线与逻辑;
  • 速率由 RC 决定,上拉与电容需要实测优化;
  • 多主与拉伸都源自“谁拉低谁赢”的物理事实。

当你把这些“为什么”真正理解,I2C 的所有规则都会变得“理所当然”。之后的寄存器、驱动与代码,只是在具体芯片上把这些物理与协议规则机械地实现出来而已。


附录 A:术语统一与规范参考

  • 术语统一:本文统一使用“I2C(俗称 IIC)”。
  • 规范参考:
    • NXP “I2C-bus specification and user manual”(UM10204),包含速率档位、时序参数(t_r/t_f、tLOW/tHIGH、tSU;DAT/tHD;DAT、tBUF等)。
    • SMBus 规范(System Management Bus):定义更严格的超时与电压范围、命令语义差异。
  • 参数映射与交叉引用:
    • 起始/停止事件 → 第 3 章;
    • ACK/NACK 与读最后字节 NACK → 第 5 章;
    • 上拉选型与 RC → 第 6 章;
    • 事件与状态机 → 第 4 章;
    • 拉伸与仲裁 → 第 7/9 章;
    • 软件/硬件实现 → 第 8 章;
    • 外设交互模板 → 第 10 章。

附录 B:I2C 速查卡(现场排障与实现要点)

  • 事件规则:
    • 起始/停止必须在 SCL 高期间产生 SDA 边沿;
    • 数据在 SCL 低期间改变、在高期间采样;
    • 第 9 位应答:接收方拉低为 ACK,主机在读最后字节发送 NACK 表示终止。
  • 上拉与 RC:R ≤ t_r/(0.85×C_bus) 为工程初值;以实测波形校核并考虑 I_OL/VOL;必要时分段/Mux。
  • 拉伸与超时:在 SCL 释放为高后“读到真高”再推进;事务级超时 + 恢复(SCL 脉冲/STOP/重试)。
  • 仲裁与公平:标志位检测、立即让路、指数退避 + 随机窗;总线空闲判定包含 tBUF。
  • 模板与语义:重复起始不可省、最后字节 NACK 是正常终止;EEPROM 用 ACK Polling。
  • EMC 与布线:主干短直、靠地参考;近源端串阻尼;屏蔽/接地与 TVS/滤波。

附录 C:逻辑分析仪触发条件模板、示波器测量方案与阈值建议

C.1 逻辑分析仪触发条件模板(通用思路,兼容大多数解码器)

  • 采样率与探头配置:
    • 采样率建议 ≥ I2C 位速率的 10×(如 400kHz 建议 ≥4MS/s),避免边沿被过度抽样。
    • 使用低电容数字探头或短地线夹,减少对总线的附加电容与噪声耦合。
  • 基础触发(事件级):
    1. Start 触发:SCL 高期间 SDA 下降沿(验证序列起点)。
    2. Stop 触发:SCL 高期间 SDA 上升沿(验证序列终点)。
    3. Repeated Start 触发:在一次会话中出现第二个 Start 事件(用于“写指针后读”)。
  • 字节与应答触发:
    4) 地址字节后 NACK:触发条件=第 9 位保持高(NACK),用于定位“设备不存在/忙/地址错”。
    5) 数据字节后 NACK:第 9 位保持高(NACK),用于定位“写保护/页写忙/长度不符”。
    6) 读事务最后一字节的主机 NACK:应识别为“正常终止”,不应触发为错误;建议单独标注但不计入故障。
  • 拉伸与时序异常触发:
    7) SCL 低保持超过阈值(Clock Stretching):阈值建议按事务类型设置,如 1ms/5ms/10ms;超过则触发“拉伸过长”。
    8) SCL 高期间出现 SDA 边沿(非预期数据变化):标记为“潜在起停误触发/抗抖不足”。
    9) tBUF 违例:Stop 后到下一个 Start 的间隔 < 规范最小值,触发为“总线空窗不足”。
  • 仲裁与多主:
    10) 仲裁丢失标志:当期望发送 1(释放)却采到 0(被他人拉低),触发“Arb Lost”;可结合地址/数据位位置记录发生点。
  • 卡死与毛刺:
    11) SDA 常低超过阈值:如 >10ms(可按项目设定),触发“总线卡死(SDA)”。
    12) SCL 常低超过阈值:如 >10ms,触发“总线卡死(SCL/拉伸异常)”。
    13) 毛刺宽度过滤:把 < t_glitch(如 <50ns/<100ns)脉冲标记为毛刺,结合示波器确认物理来源。
  • 组合脚本/自动化建议:
    • 对解码器事件(Start/Stop/Addr/NACK)做标注并导出 CSV,结合测试脚本自动统计 NACK 分布、拉伸时长直方图。
    • 对“读最后字节 NACK”单独标签,避免被错误地归类为失败。

C.2 示波器测量方案与阈值建议(电气侧闭环验证)

  • 测量准备:
    • 双通道同时接入 SDA/SCL;使用 ×10 低电容探头、短地弹簧;带宽建议 ≥100MHz(FM+/HS 更高)。
    • 触发选择:以 Start 或地址字节边界触发;必要时对“SCL 高期间的 SDA 边沿”设为触发条件。
  • 上升/下降时间(t_r/t_f):
    • t_r 用 30%→70% 阈值测量(或规范指定方法);对照目标速率档位的上限:
      • SM(100kHz):t_r ≤ 1000ns;
      • FM(400kHz):t_r ≤ 300ns;
      • FM+(1MHz):t_r ≤ 120ns;
      • HS(3.4MHz):更严格,依芯片/拓扑评估。
    • 若 t_r 超标:降低 R_pullup、分段/缓冲、优化布线(缩短支线/靠地参考)。
  • 电压阈值(VIH/VIL)与稳定度:
    • 经验阈值:VIH ≈ 0.7·VDD、VIL ≈ 0.3·VDD(具体以器件手册为准,部分器件内置施密特门限有滞回)。
    • 在 SCL 高期间检查 SDA 是否稳定越过 VIH 并保持;采样点前后观测是否存在抖动或回滞。
  • 过冲/振铃与阻尼:
    • 过冲 >10%VDD 或振铃持续多个周期:在近源端串 22Ω~100Ω 阻尼;优化走线与接地;必要时降低速率。
  • 拉伸/超时与总线空窗(tBUF):
    • 以游标统计 SCL 低保持时长分布;设置事务级超时门限(如读写 5ms/页写 10ms);Stop→Start 间隔满足 tBUF。
  • 组合判定(症状→动作):
    • t_r 超标 → 降 R + 分段 + 布线优化;
    • VIH 未达或采样窗内抖动 → 降速 + 施密特输入 + 上拉重选;
    • 过冲/振铃显著 → 串阻尼 + 接地优化 + 探头短地;
    • SCL 低保持长尾 → 设超时 + 错误路径(Stop/重试/9 脉冲恢复);
    • tBUF 违例 → 控制事务间距或在驱动中强制空窗。

C.3 触发与测量的联动流程(建议放入回归测试)

  • 步骤:
    1. 逻辑分析仪以“Start→Addr→ACK/NACK”序列触发并记录事件;
    2. 示波器同时测 t_r/t_f 与高期间稳定度;
    3. 当出现 NACK/拉伸过长/仲裁丢失事件时,回看同一时间窗的电气波形是否存在 RC/EMI 迹象;
    4. 形成“事件→电气→动作”的闭环记录(模板可导出到测试报告)。
  • 输出:
    • NACK 直方图、拉伸时长分布、t_r 越界次数;
    • 修复动作与结果对比(降速/换 R/分段前后波形截图与统计)。

提示:以上阈值均为工程建议,最终以所选芯片的规范/数据手册为准。在不同电压域(3.3V/5V)与不同输入结构(标准门/施密特)下,VIH/VIL 与 t_r/t_f 的合格范围可能不同,请以实测与器件手册闭环确认。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐