1. I2C协议基础:物理层、协议层、时序图解析

各位同学,咱们今天正式开讲I2C。说实话,I2C这协议看起来简单,两根线嘛,但真要在项目里把它调稳、调快,坑还真不少。我最早接触I2C是在一个温湿度传感器项目上,当时觉得“这玩意儿不就两根线嘛,能有多难?”结果被从机无应答折腾了一整个通宵。嗯,从那以后,我再也不敢小看这两根线了。

咱们这一章,先把I2C的底裤扒干净——物理层怎么接、协议层怎么传、时序图怎么看。基础打牢了,后面写驱动、调任务才不会翻车。

1.1 物理层:就两根线,但规矩不少

I2C总线物理上只需要两根线:SCL(串行时钟线)和SDA(串行数据线)。所有设备都挂在这两根线上,主从之间通过地址区分。

关键特征:

  • 半双工通信:同一时刻只能一个方向传数据
  • 多主多从:理论上多个主机可以共存,但实际项目中我很少这么干
  • 7位/10位地址:7位地址最多挂127个设备,10位地址可以挂更多
  • 上拉电阻:SCL和SDA必须接上拉电阻到VCC,阻值通常4.7kΩ~10kΩ

这里有个容易踩的坑——上拉电阻的取值。我曾在一次项目里用了1kΩ的上拉电阻,结果总线负载太重,SCL波形直接变成了三角波。你想想看,时钟都变形了,数据还能对么?

总线速率 推荐上拉电阻 总线电容 我踩过的坑
标准模式(100kHz) 4.7kΩ ~ 10kΩ < 400pF 电阻太大,上升沿太慢
快速模式(400kHz) 2.2kΩ ~ 4.7kΩ < 200pF 电阻太小,功耗偏高
高速模式(3.4MHz) 1kΩ ~ 2.2kΩ < 100pF 布线要短,否则反射严重

⚠️ 我曾经犯过的错:在快速模式下用了10kΩ上拉电阻,结果SCL上升时间超过300ns,从机直接不认时钟。后来换成3.3kΩ,问题解决。记住:上拉电阻不是随便选的,要根据总线电容和速率算一下。

1.2 协议层:起始、停止、应答,三板斧

I2C的协议层其实不复杂,核心就三个动作:起始条件、停止条件、应答机制。掌握了这三个,你就掌握了I2C通信的命门。

1.2.1 起始条件与停止条件

这两个条件由主机控制,从机只能被动响应。说白了,就是主机说“开始干活”和“收工下班”。

  • 起始条件(S):SCL高电平时,SDA从高电平切换到低电平
  • 停止条件(P):SCL高电平时,SDA从低电平切换到高电平

我个人习惯在写驱动时,把起始和停止条件封装成两个独立的函数。这样代码清晰,调试也方便。有一次我在一个项目中,起始条件没处理好,SDA电平切换时序差了那么几十纳秒,结果从机一直不响应。查了三个小时才发现是GPIO翻转顺序写反了。

1.2.2 数据传输与应答

数据在SCL的时钟配合下,一位一位地在SDA上传输。每个字节8位,高位在前。每传完一个字节,接收方必须回一个应答位(ACK)或非应答位(NACK)。

应答规则:

  • ACK(0):接收方在第9个时钟周期将SDA拉低,表示“收到,继续发”
  • NACK(1):接收方在第9个时钟周期释放SDA(由上拉电阻拉高),表示“收完了,别发了”

你想想看,如果从机没拉低SDA,主机怎么知道它收到了?这就是应答存在的意义。我在调试一个EEPROM驱动时,发现写操作总是失败,后来用逻辑分析仪一看,从机回了NACK。查手册才发现,EEPROM在内部擦写期间不会应答——这就是典型的“忙状态”。

1.2.3 完整的数据帧格式

一个典型的I2C写操作帧长这样:

| S | 从机地址(7bit) | R/W(1bit) | ACK | 寄存器地址(8bit) | ACK | 数据(8bit) | ACK | P |

读操作稍微复杂一点,需要先写寄存器地址,再重新发起始条件切换方向:

| S | 从机地址 | W(0) | ACK | 寄存器地址 | ACK | Sr | 从机地址 | R(1) | ACK | 数据 | NACK | P |

💡 我的经验:写驱动时,把“写寄存器地址”和“读数据”拆成两个步骤,中间用重复起始条件(Sr)连接。这样代码可读性强,也方便加超时处理。我曾经见过有人把整个流程写在一个大循环里,出了bug根本没法调试。

1.3 时序图解析:别怕,其实就几个关键参数

很多同学看到时序图就头大,密密麻麻的波形和参数。其实I2C的时序图,你只需要关注几个关键时间参数:

参数 符号 标准模式 快速模式 说明
SCL时钟频率 fSCL 100kHz 400kHz 别超上限,否则从机跟不上
保持时间(起始条件) tHD;STA 4.0μs 0.6μs SDA拉低后要等一会再拉低SCL
建立时间(停止条件) tSU;STO 4.0μs 0.6μs SCL拉高后要等一会再拉高SDA
数据建立时间 tSU;DAT 250ns 100ns SDA数据要在SCL上升沿前稳定
数据保持时间 tHD;DAT 0ns 0ns SCL下降沿后SDA要保持一段时间

说白了,时序图就是告诉你:什么时候该拉高、什么时候该拉低、要等多久。我刚开始学的时候,把这些参数打印出来贴在工位上,写驱动时对照着看,慢慢就熟了。

⚠️ 特别注意:tHD;DAT在标准模式下最小值是0,但实际项目中我建议至少留100ns的余量。有些从机芯片的时序窗口很窄,不留余量就容易出现“偶发通信失败”——这种bug最难查。

1.4 知识体系总览:一张图看懂I2C

下面这张SVG图,把I2C的核心知识点串起来了。从物理层到协议层,再到时序参数,你看完应该能有个整体印象。

I2C总线知识体系总览 物理层 SCL时钟线 SDA数据线 上拉电阻(4.7kΩ~10kΩ) 协议层 起始条件(S) 停止条件(P) 7位地址 10位地址 应答机制(ACK/NACK) 时序参数 SCL时钟频率 t_HD;STA / t_SU;STO t_SU;DAT / t_HD;DAT 核心:两根线(SCL+SDA)+ 上拉电阻 + 起始/停止条件 + 应答机制 + 时序约束

1.5 写在后面:基础决定上限

I2C协议看起来简单,但真要在项目里用好它,物理层的上拉电阻、协议层的应答处理、时序参数的余量,每一个细节都不能马虎。我见过太多人上来就调驱动,结果波形不对、从机不响应,最后回头补基础——白白浪费几天时间。

这一章的内容,我希望你能做到:看到时序图能说出每个参数的含义,写驱动时能主动考虑上拉电阻和时序余量。做到这两点,后面的章节你学起来会轻松很多。

💡 课后小练习:拿逻辑分析仪抓一下你手头开发板的I2C波形,对照本章的时序参数表,看看实际波形是否满足规范。我当年就是这么练出来的——看波形、对参数、调代码,循环几次就熟了。