4. I2C协议详解

I2C(Inter-Integrated Circuit)是飞控系统中最常见的板载低速总线之一,用于连接气压计、磁力计、IMU、电流计等传感器。它只用两根信号线——SCL(时钟)和SDA(数据),就能挂载多个从设备。理解I2C的物理层与协议帧结构,是排查“传感器不识别”“总线锁死”“读数偶发错误”等问题的前提。

一、物理层:开漏输出与上拉电阻

I2C的SCL和SDA都是开漏(Open-Drain)结构。器件只能把线拉低,不能主动拉高;高电平由外部上拉电阻提供。这种设计带来两个关键特性:

  • 线与逻辑:只要有一个器件拉低,总线就是低电平。这为多主机的时钟同步和总线仲裁提供了物理基础。
  • 电平兼容:不同供电电压的器件只要上拉到同一电平,就能共享总线,便于3.3V飞控与1.8V传感器共存。

上拉电阻的取值需要权衡:阻值太小,低电平灌电流大、功耗高;阻值太大,上升沿变缓,高速率下波形失真。飞控上常见取值为2.2kΩ~10kΩ,400kHz Fast Mode下多取2.2kΩ~4.7kΩ。总线电容(PCB走线、引脚、连接器)越大,上拉应越小。

飞控上I2C总线通常已有板载上拉电阻。外接传感器模块若自带上拉,会与板载上拉并联,导致等效阻值过小、低电平电流过大。接入前应确认模块是否已含上拉。

二、起始条件、停止条件与重复起始

I2C总线空闲时,SCL和SDA都为高。通信的开始与结束由特定电平跳变定义:

  • 起始条件(START):SCL为高时,SDA由高变低。
  • 停止条件(STOP):SCL为高时,SDA由低变高。
  • 重复起始(Repeated START):不发送STOP,再次发送START,用于在一次传输中切换读写方向或访问不同寄存器,避免释放总线被其他主机抢占。

数据位传输时,SDA必须在SCL低电平期间变化,在SCL高电平期间保持稳定。若在SCL高电平期间SDA跳变,会被误判为START或STOP,这是总线异常最常见的原因之一。

三、地址帧、读写位与ACK/NACK

每次START后,主机发送7位从机地址,紧跟1位读写位:0表示写(主机→从机),1表示读(从机→主机)。第9个时钟周期,接收方拉低SDA表示ACK,保持高表示NACK。

典型写寄存器时序:START → 地址+W → ACK → 寄存器地址 → ACK → 数据 → ACK → STOP。典型读寄存器时序:START → 地址+W → ACK → 寄存器地址 → ACK → Repeated START → 地址+R → ACK → 数据 → NACK → STOP。主机在读取最后一个字节后发送NACK,通知从机停止发送。

// 以MPU6050读取WHO_AM_I(0x75)为例
// 7位地址0x68,写地址0xD0,读地址0xD1
START
  0xD0  -> ACK
  0x75  -> ACK
Repeated START
  0xD1  -> ACK
  data  -> NACK
STOP

四、时钟同步与总线仲裁

多主机场景下,I2C依靠开漏的线与特性实现两个机制:

  • 时钟同步:多个主机同时发送时钟时,SCL低电平由所有主机中低电平最长者决定,高电平由最短者决定,最终形成统一的时钟。
  • 总线仲裁:主机在发送数据的同时回读SDA。若自己发送高电平却读到低电平,说明被其他主机拉低,立即退出并转为从机接收。仲裁不会破坏获胜主机的数据。

飞控中通常只有一个主机(MCU),仲裁较少发生,但时钟同步在时钟拉伸(Clock Stretching)时仍然重要:从机可拉低SCL延长低电平,迫使主机等待,适用于传感器内部转换未完成时。

五、常见I2C传感器与地址

传感器典型7位地址用途备注
MPU60500x68 / 0x69六轴IMUAD0决定地址
BMP2800x76 / 0x77气压高度SDO决定地址
MS56110x76 / 0x77气压高度高分辨率
HMC5883L0x1E磁力计固定地址
QMC5883L0x0D磁力计与HMC不兼容
INA2190x40~0x4F电流/电压地址引脚可选

六、案例:I2C传感器初始化失败排查

某飞控接入BMP280后,驱动初始化返回超时。按以下顺序排查:

  1. 用示波器观察SCL/SDA空闲电平,若不为高,检查上拉电阻是否缺失或总线被拉死。
  2. 发送START后观察地址帧,确认从机是否在第9个时钟拉低SDA返回ACK。若NACK,检查地址、供电和CSB/SDO引脚电平。
  3. 检查SCL高电平期间SDA是否稳定,排除START/STOP误判。
  4. 确认从机是否需要时钟拉伸,主机是否支持。
  5. 若总线锁死,尝试发送9个时钟脉冲并补发STOP释放总线。
I2C总线锁死常见于从机在传输中被复位或掉电,导致SDA被持续拉低。此时主机无法产生START。解决方法是把SCL作为GPIO手动翻转9个周期,再发送STOP。

七、误区与复盘

  • 误区一:地址写成8位。很多手册给出的是8位地址(含读写位),驱动中需右移一位得到7位地址。
  • 误区二:忽略上拉。以为MCU内部上拉足够,实际内部上拉通常为几十kΩ,400kHz下上升沿过慢。
  • 误区三:读多字节时最后一字节仍发ACK。主机应在最后一字节发NACK,否则从机会继续占用SDA。
  • 误区四:多个同地址传感器直接并联。应通过地址引脚或I2C多路复用器(如TCA9548A)区分。

掌握I2C的物理层与帧结构后,再看飞控驱动代码中的i2c_transfer、read_reg、write_reg等接口,就能把每一次START、地址、ACK与传感器手册中的时序图对应起来,快速定位通信故障。

I2C 物理层与帧结构示意 VCC Rp VCC Rp SCL SDA MCU 主机 开漏输出 BMP280 0x76 / 0x77 MPU6050 0x68 / 0x69 QMC5883L 0x0D 一次写寄存器传输帧 START 7位地址 + W 0x76<<1 | 0 ACK 寄存器地址 ACK 数据 ACK STOP 开漏 + 上拉:任一器件拉低即为低电平;SCL高时SDA跳变定义START/STOP