2. I2C通信协议详解:总线结构、起始/停止条件、数据帧格式、地址与读写位

好,咱们进入正题。I2C协议,说白了就是一套“谁说话、谁听话、什么时候说”的规矩。你想想看,一条总线上挂着好几个设备,如果没有规矩,大家一起抢着发数据,那不乱套了?

我个人习惯把I2C比作一个会议室。主设备是会议主持人,从设备是参会者。主持人控制会议节奏,点名谁发言,谁才能说话。这个比喻虽然简单,但能帮你快速理解I2C的核心逻辑。

2.1 I2C总线结构:两条线,多个设备

I2C总线物理上只需要两根线:

  • SDA(串行数据线):用来传输地址和数据。
  • SCL(串行时钟线):用来同步数据传输的时钟信号。

这两根线都是开漏输出结构。什么叫开漏?就是芯片只能把线拉低,不能主动拉高。所以总线上必须接上拉电阻(通常4.7kΩ或10kΩ)。空闲时,SDA和SCL都被上拉电阻拉到高电平。

我的经验:上拉电阻的取值很关键。电阻太大,信号上升沿变慢,高速通信容易出错;电阻太小,功耗增加,驱动能力可能不够。我一般100kHz用10kΩ,400kHz用4.7kΩ,基本不会出问题。

总线上可以挂多个主设备和多个从设备。但同一时刻只能有一个主设备控制总线。每个从设备都有一个唯一的7位或10位地址。

I2C总线结构示意图 Rₚ Rₚ SCL(时钟线) SDA(数据线) 主设备 (如STM32/ESP32) 从设备1 (LCD 0x27) 从设备2 (传感器 0x76) 从设备3 (EEPROM 0x50) 所有设备通过SDA和SCL并联在总线上 每个从设备有唯一地址,主设备通过地址选择通信对象

2.2 起始条件与停止条件

I2C通信的开始和结束,由SDA和SCL的特定电平变化来标志。这就像会议主持人说“会议开始”和“会议结束”。

条件 SDA状态 SCL状态 说明
起始条件(S) 高→低 高电平 SCL为高时,SDA从高跳变到低
停止条件(P) 低→高 高电平 SCL为高时,SDA从低跳变到高

关键点:起始和停止条件都是SCL为高电平时,SDA发生跳变。而在数据传输过程中,SDA只能在SCL为低电平时变化。这个区别非常重要,我当年调试时就是因为没注意这个细节,逻辑分析仪抓出来的波形怎么看怎么不对。

我曾经在一个项目中,因为代码里起始条件之后忘了加一个小延时,导致从设备没识别到起始信号,整个通信都乱了。后来用示波器一看,SDA的下降沿和SCL的上升沿几乎重叠了,从设备根本分不清是起始条件还是数据位。

2.3 数据帧格式:一个字节一个字节地传

I2C的数据传输是以字节为单位的。每传输一个字节(8位),后面紧跟着一个应答位(ACK)。所以实际上每帧是9位。

数据传输的规则很简单:

  • 高位先行:先传最高位(MSB),再传低位。
  • SCL低电平时,发送方改变SDA数据。
  • SCL高电平时,接收方读取SDA数据。
  • 每字节后,接收方在第9个SCL周期拉低SDA表示应答(ACK),或保持高表示非应答(NACK)。
我的习惯:写驱动时,我总会在发送每个字节后检查ACK位。如果收到NACK,说明从设备可能没准备好或者地址错了,这时候及时停止重试,比傻等强得多。

2.4 地址与读写位:点名与指令

主设备发起通信时,发送的第一个字节就是从设备地址 + 读写位。格式如下:

位 7 6 5 4 3 2 1 0
含义 7位从设备地址 R/W
说明 高7位是设备地址 0=写,1=读

举个例子:我们用的LCD模块,I2C地址通常是0x27(7位地址)。如果要写数据,发送的字节就是 0x27 << 1 | 0 = 0x4E;如果要读数据,就是 0x27 << 1 | 1 = 0x4F。

注意:很多数据手册上给出的地址是7位地址,但实际发送时要左移一位,最低位补上读写位。我见过不少新手直接把0x27发出去,结果通信失败,折腾半天才发现是地址没移位。

2.5 完整通信流程:一次典型的写操作

咱们以向LCD写一个字节数据为例,看看完整的I2C通信流程:

  1. 主设备发送起始条件:SDA在SCL高电平时拉低。
  2. 主设备发送地址字节:0x4E(0x27左移1位 + 写位0)。
  3. 从设备应答:LCD模块拉低SDA表示“我在”。
  4. 主设备发送控制字节:比如0x08(背光开)或0x00(背光关)。
  5. 从设备应答:再次拉低SDA。
  6. 主设备发送数据字节:比如要显示的字符' A '的ASCII码0x41。
  7. 从设备应答:拉低SDA。
  8. 主设备发送停止条件:SDA在SCL高电平时拉高。

你看,整个过程就像一场有来有回的对话。主设备说一句,从设备应一声,确保信息传到了。

I2C写操作时序图(向LCD写数据) SCL SDA 起始 地址(7位) R/W ACK 数据(8位) ACK 数据(8位) ACK 停止 每传输一个字节(8位),接收方在第9个SCL周期发送ACK/NACK

2.6 常见问题与避坑指南

做I2C驱动,有几个坑我踩过,分享给你:

  • 总线锁死:从设备异常时可能一直拉低SDA不放。主设备发停止条件也无效。解决办法是发送9个以上的SCL时钟脉冲,让从设备复位状态机。我一般写一个“总线恢复”函数,专门处理这种情况。
  • 地址冲突:两个从设备用了相同地址。这时候要么换硬件地址(如果芯片有地址选择引脚),要么用I2C多路复用器。我在一个项目里同时用了两个相同型号的传感器,就是靠一个PCA9548A解决的。
  • 速率不匹配:主设备跑400kHz,从设备只支持100kHz。通信会间歇性失败。解决办法是统一降到100kHz,或者用时钟拉伸(如果从设备支持)。

重要提醒:调试I2C时,逻辑分析仪是你的好朋友。别光靠眼睛看代码,抓一下波形,什么问题都一目了然。我每次调I2C,第一件事就是挂上逻辑分析仪,看起始条件、地址、ACK,三步走完基本就能定位问题。