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的核心知识点串起来了。从物理层到协议层,再到时序参数,你看完应该能有个整体印象。
1.5 写在后面:基础决定上限
I2C协议看起来简单,但真要在项目里用好它,物理层的上拉电阻、协议层的应答处理、时序参数的余量,每一个细节都不能马虎。我见过太多人上来就调驱动,结果波形不对、从机不响应,最后回头补基础——白白浪费几天时间。
这一章的内容,我希望你能做到:看到时序图能说出每个参数的含义,写驱动时能主动考虑上拉电阻和时序余量。做到这两点,后面的章节你学起来会轻松很多。
💡 课后小练习:拿逻辑分析仪抓一下你手头开发板的I2C波形,对照本章的时序参数表,看看实际波形是否满足规范。我当年就是这么练出来的——看波形、对参数、调代码,循环几次就熟了。