第4章 I2C通信协议:时序、读写、多设备与上拉电阻

各位同学,今天我们来聊聊I2C。这个协议在飞控传感器里太常见了——气压计、磁力计、部分IMU,基本都是走I2C。说白了,它就是一根时钟线(SCL)加一根数据线(SDA),两根线就能挂一堆设备。

嗯,我刚开始做飞控时,觉得I2C挺简单的。直到有一次,磁力计数据老是跳变,查了三天才发现是上拉电阻选错了。从那以后,我对I2C就多了一份敬畏。今天我把这些坑都给你们摆出来。

4.1 I2C时序:你得看懂波形

I2C的时序其实不复杂。核心就几个动作:起始信号、停止信号、数据位、应答位。

  • 起始信号:SCL高电平时,SDA从高变低。这个叫Start Condition。
  • 停止信号:SCL高电平时,SDA从低变高。这个叫Stop Condition。
  • 数据位:SCL低电平时改变SDA,SCL高电平时采样SDA。每个字节8位,高位在前。
  • 应答位:第9个时钟周期,接收方拉低SDA表示ACK,不拉低表示NACK。

关键点:I2C是同步协议,时钟由主机产生。从机只能拉低SCL做时钟拉伸(Clock Stretching),但飞控传感器很少用这个功能。

我在调试MPU6050时,用逻辑分析仪抓过波形。你想想看,如果起始信号没抓到,整个通信就乱了。所以我建议,第一次调I2C时,一定用逻辑分析仪看一眼波形。

4.2 读写操作:主机说了算

I2C的读写操作,主机是老大。从机只能被动响应。标准格式是这样的:

// 写操作:主机发送
起始信号 + 7位地址 + 写位(0) + 从机ACK + 寄存器地址 + 从机ACK + 数据字节 + 从机ACK + 停止信号

// 读操作:主机先写后读
起始信号 + 7位地址 + 写位(0) + 从机ACK + 寄存器地址 + 从机ACK + 
重复起始信号 + 7位地址 + 读位(1) + 从机ACK + 数据字节 + 主机NACK + 停止信号

注意看,读操作有个重复起始信号。这是I2C的精髓——主机不释放总线,直接切换读写方向。我曾经见过有人在这里写错,导致从机一直不响应。

我的习惯:写操作时,先发设备地址,再发寄存器地址,最后发数据。读操作时,先发设备地址+寄存器地址,然后发重复起始信号,再发设备地址+读位。顺序错了,数据就乱了。

4.3 多设备挂载:地址别冲突

I2C支持多设备,靠的是7位地址。理论上可以挂128个设备,但实际受总线电容限制,一般挂3-5个就差不多了。

每个I2C设备都有一个固定地址,通常有几位是可配置的。比如MPU6050的地址是0x68,如果AD0引脚拉高,就变成0x69。BMP280的地址是0x76或0x77,取决于SDO引脚。

传感器 默认地址 可选地址 配置引脚
MPU6050 0x68 0x69 AD0
BMP280 0x76 0x77 SDO
HMC5883L 0x1E

注意:如果两个设备地址相同,它们会打架。我遇到过有人把两个MPU6050都设成0x68,结果数据全乱了。解决办法:要么改地址,要么用I2C多路复用器(如TCA9548A)。

4.4 上拉电阻选择:别小看这个电阻

I2C总线是开漏输出,必须加上拉电阻才能输出高电平。电阻选大了,信号上升沿变慢;选小了,功耗增加,还可能拉不动。

标准I2C规范里,上拉电阻的推荐值跟总线电容和通信速率有关:

  • 标准模式(100kHz):4.7kΩ 典型值
  • 快速模式(400kHz):2.2kΩ 典型值
  • 高速模式(3.4MHz):1kΩ 左右

计算公式很简单:R = t_r / (0.8473 × C_bus)。其中t_r是上升时间,C_bus是总线总电容(包括引脚电容和走线电容)。

嗯,这里有个坑。我曾经在一个项目里,用了10kΩ的上拉电阻,总线长度30cm,结果400kHz下波形完全变形。后来换成2.2kΩ,问题就解决了。你想想看,飞控内部走线短,电容小,但传感器模块的线可能很长,这时候就得算一算。

我的建议:飞控板上I2C总线,如果只挂1-2个传感器,用4.7kΩ。如果挂3个以上,或者线长超过10cm,用2.2kΩ。实在拿不准,用示波器看上升沿,保证上升时间不超过300ns(400kHz下)。

4.5 知识体系总览

下面这张图,把I2C的核心逻辑串起来了。你看一遍,应该能有个整体印象。

I2C通信协议知识体系 I2C协议核心 时序 读写操作 多设备挂载 上拉电阻 起始/停止 数据位 应答位 写操作 读操作 重复起始 地址配置 地址冲突 多路复用 阻值计算 上升时间 功耗考虑 核心:两根线 + 地址 + 时序 = 可靠通信

4.6 避坑指南

最后,我把自己踩过的坑总结一下,你们遇到类似问题可以少走弯路。

  • 上拉电阻选错:信号上升沿变慢,导致通信失败。用示波器看波形,上升沿太缓就换小电阻。
  • 地址冲突:两个设备地址一样,数据互相干扰。用I2C扫描程序先扫一遍,确认每个地址都有唯一设备。
  • 总线电容过大:挂太多设备或线太长,信号失真。飞控上建议不超过5个设备,线长不超过20cm。
  • 漏掉重复起始信号:读操作时,没有发重复起始,从机不响应。检查代码,确保读操作有Restart。
  • 时钟拉伸没处理:部分从机需要时钟拉伸,但飞控传感器很少用。如果遇到,检查MCU的I2C外设是否支持。

一句话总结:I2C不难,但细节决定成败。上拉电阻选对,地址别冲突,时序别搞错,你的传感器就能稳稳地跑起来。


公众号:蓝海资料掘金营,微信deep3321