嵌入式系统

I2C 面试手册

覆盖 I2C 总线结构、开漏上拉、仲裁、时钟延展与故障排查等嵌入式面试高频考点。

难度:进阶
文章目录

知识地图

graph TD
    A[I2C 总线] --> B[物理层]
    A --> C[协议层]
    A --> D[工程问题]
    B --> B1[开漏输出 + 上拉]
    B --> B2[SDA / SCL 双线]
    B --> B3[线与逻辑]
    C --> C1[START / STOP 条件]
    C --> C2[7bit / 10bit 寻址]
    C --> C3[ACK / NACK]
    C --> C4[时钟延展]
    C --> C5[多主仲裁]
    D --> D1[上拉电阻计算]
    D --> D2[总线卡死排查]
    D --> D3[速率与电容约束]
    D --> D4[SMBus / I3C]

核心速记卡

概念一句话定义关键限制
开漏输出设备只能拉低不能拉高,高电平靠上拉电阻上升沿速度受 Rp × C_b 制约
START 条件SCL 高时 SDA 下降沿总线空闲时(SDA、SCL 均为高)才能发送
ACK第 9 个 SCL 周期接收方拉低 SDANACK 可以表示无设备应答或数据结束
时钟延展从设备拉低 SCL 暂停传输不是所有主设备硬件都支持
仲裁多主场景中,SDA 输出高但读到低的设备主动退出不丢数据,仲裁失败方自动变从

基础问答

Q: 为什么 I2C 总线的 SDA 和 SCL 需要上拉电阻?

答: I2C 设备使用开漏输出驱动总线——它们只能主动将线路拉到 GND,不能主动输出高电平。上拉电阻将线路拉到 VCC 来产生高电平。这种设计使得多个设备可以安全地共享同一线路:任何设备拉低线路时不会与其他试图拉高的设备产生短路(线与逻辑)。

Q: I2C 的 START 和 STOP 条件分别是什么?

答: START:SCL 为高电平时,SDA 从高到低的跳变。STOP:SCL 为高电平时,SDA 从低到高的跳变。START 后总线被认为”忙”,STOP 后总线释放为空闲状态。在 START 和 STOP 之间,SDA 只能在 SCL 为低电平时改变。

Q: 什么情况下从设备会回复 NACK?

答: 常见场景包括:总线上没有匹配地址的设备;从设备正忙无法接收数据;主设备在读取最后一个字节后发送 NACK 通知从设备停止发送;地址或数据格式错误。

Q: 如何估算 I2C 上拉电阻的取值范围?

答: 下限由设备吸电流能力决定:Rp(min) = (VCC - V_OL) / I_OL(通常 ≥ 1~2kΩ)。上限由上升时间和总线电容决定:Rp(max) = t_r / (0.8473 × C_b)。标准模式 t_r ≤ 1000ns,快速模式 t_r ≤ 300ns。实际工程中 2.2kΩ ~ 4.7kΩ 是常用范围。

追问: 如果总线电容 C_b 为 100pF,快速模式(400kHz,t_r ≤ 300ns)下 Rp(max) 是多少?答:约 300ns / (0.8473 × 100pF) ≈ 3.54kΩ

深入追问

Q: 时钟延展(Clock Stretching)的工作原理是什么?为什么有人建议禁用?

答: 主设备释放 SCL 后,从设备可以在主设备再次拉高 SCL 之前将 SCL 保持低电平,阻止主设备产生下一个时钟。工作完成后,从设备释放 SCL,传输继续。

需要谨慎使用的原因:控制器硬件、驱动或超时配置可能无法正确处理长时间的 SCL 低电平。如果从设备会使用时钟延展,应核对主设备数据手册并设置明确超时;软件模拟 I2C 只是可选实现方式,不应被视为通用修复。

Q: 多主仲裁是怎么实现的?为什么不会丢数据?

答: 仲裁基于 SDA 线的线与特性。所有主设备在发送每一位后都会采样 SDA 的实际电平。如果主设备 A 释放 SDA 表示高电平,却检测到总线为低(被主设备 B 拉低),A 就失去仲裁并停止驱动总线。

仲裁不会破坏获胜主设备的报文:失败方在第一次“释放高但读到低”的位上立即停止驱动,在此之前各主设备发送的位完全一致。失败方是否转为从设备取决于控制器能力和当前阶段。

Q: I2C 和 SPI 在嵌入式场景中如何选择?

答: I2C 优势:两根线、多设备共享总线、有内置寻址和应答。劣势:速度较慢(标准 100k/400k/1M/3.4M)、协议开销较大。适合传感器、EEPROM、PMIC 等低速率多节点场景。

SPI 优势:全双工、高速(可达数十 MHz)、协议简单。劣势:每增加一个从设备需要额外 CS 线、无标准寻址机制、无内置应答。适合 Flash、显示屏、ADC/DAC 等高速场景。

工程场景题

场景:MCU 读某 I2C 温度传感器偶尔返回 0xFF 或全 0,但大部分时候正常

分析思路:

  1. 用示波器/逻辑分析仪抓取故障波形,确认是 ACK 后数据异常还是根本没 ACK。
  2. 全 0xFF 通常意味着 SDA 一直被上拉电阻拉高——可能是从设备根本没响应(NACK 后总线被释放),读取到的是浮空电平。
  3. 全 0 可能是 SDA 被意外拉低,检查电源去耦是否充分、有无相邻 GPIO 干扰。
  4. 如果是间歇性的,考虑:总线电容是否临界,上升沿在温度和电压波动下越界;是否存在噪声耦合(旁边的 PWM 线、开关电源纹波)。

场景:总线一直卡在低电平,通信完全失败

分析思路:

  1. 先分别测量 SCL 与 SDA,确认是哪根线被拉低,并排除短路和主设备 GPIO 配置错误。
  2. SCL 被持续拉低时,无法靠主设备输出 9 个时钟恢复;应定位正在时钟延展或故障的设备,必要时复位或断电重启。
  3. SDA 被拉低但 SCL 仍可控时,可以输出最多 9 个 SCL 脉冲,让从设备完成未结束的字节,再发送 STOP。
  4. 驱动中应加入总线忙和时钟延展超时,以及按硬件手册执行的总线恢复流程。

易错点

  • 误区:“I2C 地址就是数据手册上的那个值” → 许多数据手册给出的 8 位地址实际已经包含了读写位,需要右移 1 位得到 7 位地址。
  • 误区:“NACK 就是出错了” → 主设备读取最后一个字节后主动发送 NACK 是正常流程,通知从设备不要再发送数据。
  • 误区:“Repeated START 和 STOP+START 一样” → 不一样。Repeated START 不释放总线,可以防止在多主场景下被其他主设备插入。
  • 误区:“I2C 的上拉电阻用内部上拉就行” → MCU 内部上拉通常为 20~50kΩ,对标准模式 I2C 来说往往偏大,导致上升沿过慢。

一句话总结

I2C 的简洁来自开漏+上拉的线与设计——两根线即可实现多设备双向通信,但上拉电阻的选择、地址格式的确认和对时钟信号的理解是嵌入式工程师必须掌握的三个工程决策点。

官方参考资料