2、I2C通信协议:从原理到实战

各位同学,今天我们来聊聊嵌入式系统里最常用的通信协议之一——I2C。说实话,我最早接触I2C是在做一款加速度传感器驱动的时候,那时候被时序图折腾得够呛。但搞懂了之后你会发现,I2C其实是个很优雅的协议。

2.1 I2C总线原理

I2C总线,全称Inter-Integrated Circuit,是飞利浦公司(现在的NXP)在80年代发明的。它只需要两根线:SDA(数据线)和SCL(时钟线)。嗯,就这么简单。

这两根线都是开漏输出,所以必须接上拉电阻。我习惯用4.7kΩ的电阻,但具体阻值要看总线电容和通信速率。你想想看,如果总线太长,电容大了,上拉电阻就得小一点,不然信号上升沿会变缓。

I2C总线上可以挂多个设备,每个设备都有唯一的地址。主机(通常是MCU)负责产生时钟,从机响应。说白了,就是主机喊话,从机听。

关键点:

  • SDA和SCL都是双向的
  • 空闲时,两根线都是高电平
  • 每个从机有7位或10位地址
  • 标准模式100kHz,快速模式400kHz

2.2 I2C时序

时序这东西,光看文字很难理解。我建议你配合逻辑分析仪来看,效果最好。这里我讲几个关键时序点。

起始条件:SCL高电平时,SDA从高变低。这个动作告诉所有从机:主机要开始通信了。

停止条件:SCL高电平时,SDA从低变高。告诉从机:通信结束。

数据传输:每个时钟周期传输1位数据。SCL低电平时,SDA可以变化;SCL高电平时,SDA必须稳定。我曾经在项目里犯过一个低级错误——在SCL高电平时改变了SDA,结果从机完全不响应,查了半天才发现是时序问题。

应答位:每传输完8位数据,接收方会在第9个时钟周期拉低SDA,表示「收到了」。如果没收到应答,主机可以发送停止条件或者重复起始条件。

我的小技巧:调试I2C时序时,先用示波器或逻辑分析仪抓一下波形。看看起始条件、停止条件、数据位和应答位是否都符合规范。很多时候,问题就出在这些细节上。

2.3 I2C读写操作

I2C的读写操作其实就两种:写和读。但组合起来有好几种模式。

写操作:

  1. 主机发送起始条件
  2. 主机发送从机地址 + 写位(0)
  3. 等待从机应答
  4. 主机发送寄存器地址
  5. 等待从机应答
  6. 主机发送数据字节
  7. 等待从机应答
  8. 重复第6-7步,直到数据发完
  9. 主机发送停止条件

读操作:

  1. 主机发送起始条件
  2. 主机发送从机地址 + 写位(0)
  3. 等待从机应答
  4. 主机发送要读取的寄存器地址
  5. 等待从机应答
  6. 主机发送重复起始条件
  7. 主机发送从机地址 + 读位(1)
  8. 等待从机应答
  9. 主机读取数据字节,并发送应答
  10. 重复第9步,直到数据读完
  11. 主机发送非应答,然后发送停止条件

为什么会这样?因为读操作需要先告诉从机你要读哪个寄存器,然后再切换成读模式。说白了就是「先写后读」。

注意:有些传感器支持连续读写,但有些不行。我遇到过一款气压传感器,连续读超过4个字节就会出错。所以,一定要看数据手册!

2.4 I2C在传感器中的应用

现在很多传感器都采用I2C接口,比如温度传感器、加速度计、陀螺仪、磁力计等。为什么?因为I2C只需要两根线,而且可以挂多个传感器。

以MPU6050六轴传感器为例,它的I2C地址是0x68(AD0引脚接地)或0x69(AD0引脚接VCC)。初始化流程一般是:

// 伪代码示例
i2c_start();
i2c_write(0x68 << 1 | 0);  // 地址+写位
i2c_write(0x6B);             // 电源管理寄存器
i2c_write(0x00);             // 唤醒传感器
i2c_stop();

// 读取加速度数据
i2c_start();
i2c_write(0x68 << 1 | 0);  // 地址+写位
i2c_write(0x3B);             // 加速度数据起始地址
i2c_start();                 // 重复起始条件
i2c_write(0x68 << 1 | 1);  // 地址+读位
accel_x_h = i2c_read(1);     // 读取并发送应答
accel_x_l = i2c_read(1);
accel_y_h = i2c_read(1);
accel_y_l = i2c_read(1);
accel_z_h = i2c_read(1);
accel_z_l = i2c_read(0);     // 最后一个字节发送非应答
i2c_stop();

这段代码看起来简单,但实际调试时可能会遇到各种问题。比如从机不应答、数据错位、时序不对等。我曾经在一个项目里,因为上拉电阻选得太小,导致SDA信号失真,读出来的数据全是0xFF。后来换成4.7kΩ就好了。

避坑指南:

  • 上拉电阻一般选2.2kΩ~10kΩ,具体看总线长度和速率
  • 总线电容不要太大,否则信号会变形
  • 多个从机地址不能冲突
  • 从机地址通常是7位,左移1位后加上读写位
  • 调试时先用逻辑分析仪抓波形,确认时序正确

好了,I2C协议就讲到这里。下一章我们会讲SPI协议,到时候对比着看,你会发现每种协议都有自己的特点。记住,没有最好的协议,只有最合适的协议。

本章要点回顾:

  • I2C总线只需要SDA和SCL两根线
  • 起始条件、停止条件、数据位、应答位是基本时序
  • 读写操作遵循「先写地址,再读写数据」的流程
  • 传感器应用中,注意从机地址、寄存器地址和连续读写限制