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总线空闲时,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位地址 | 用途 | 备注 |
|---|---|---|---|
| MPU6050 | 0x68 / 0x69 | 六轴IMU | AD0决定地址 |
| BMP280 | 0x76 / 0x77 | 气压高度 | SDO决定地址 |
| MS5611 | 0x76 / 0x77 | 气压高度 | 高分辨率 |
| HMC5883L | 0x1E | 磁力计 | 固定地址 |
| QMC5883L | 0x0D | 磁力计 | 与HMC不兼容 |
| INA219 | 0x40~0x4F | 电流/电压 | 地址引脚可选 |
六、案例:I2C传感器初始化失败排查
某飞控接入BMP280后,驱动初始化返回超时。按以下顺序排查:
- 用示波器观察SCL/SDA空闲电平,若不为高,检查上拉电阻是否缺失或总线被拉死。
- 发送START后观察地址帧,确认从机是否在第9个时钟拉低SDA返回ACK。若NACK,检查地址、供电和CSB/SDO引脚电平。
- 检查SCL高电平期间SDA是否稳定,排除START/STOP误判。
- 确认从机是否需要时钟拉伸,主机是否支持。
- 若总线锁死,尝试发送9个时钟脉冲并补发STOP释放总线。
七、误区与复盘
- 误区一:地址写成8位。很多手册给出的是8位地址(含读写位),驱动中需右移一位得到7位地址。
- 误区二:忽略上拉。以为MCU内部上拉足够,实际内部上拉通常为几十kΩ,400kHz下上升沿过慢。
- 误区三:读多字节时最后一字节仍发ACK。主机应在最后一字节发NACK,否则从机会继续占用SDA。
- 误区四:多个同地址传感器直接并联。应通过地址引脚或I2C多路复用器(如TCA9548A)区分。
掌握I2C的物理层与帧结构后,再看飞控驱动代码中的i2c_transfer、read_reg、write_reg等接口,就能把每一次START、地址、ACK与传感器手册中的时序图对应起来,快速定位通信故障。