第二讲:I2C协议基础——物理层、时序、起始/停止条件、数据帧格式
各位同学,今天我们正式进入I2C协议的世界。说实话,I2C是我在项目里用得最多的串行总线之一,它简单、灵活,两根线就能搞定一堆传感器。但越是简单的东西,坑往往也越隐蔽。我刚开始用I2C时,就被一个时序问题折腾了两天——嗯,今天咱们就把这些基础打扎实。
一、I2C总线物理层:两根线,一个世界
I2C总线只需要两根线:SCL(时钟线)和SDA(数据线)。所有设备都挂在这两根线上,通过唯一地址区分。
物理层有几个关键点,我直接列出来:
- 开漏输出:SCL和SDA都是开漏结构。这意味着它们只能拉低,不能主动拉高。高电平靠上拉电阻实现。
- 上拉电阻:一般选4.7kΩ或10kΩ。总线电容大时用2.2kΩ。我习惯先算一下:
R = t_r / (0.8473 × C_bus),其中t_r是上升时间,C_bus是总线总电容。 - 多主设备:理论上多个主机可以共存,通过仲裁机制决定谁控制总线。不过实际项目中,我很少见到多主场景,基本都是单主多从。
- 设备地址:7位或10位地址。7位地址最多挂127个设备,但算上保留地址,实际可用的大概112个。
重要提醒:上拉电阻不能太小,否则灌电流太大;也不能太大,否则上升沿太慢。标准模式(100kHz)下,我一般用4.7kΩ;快速模式(400kHz)用2.2kΩ。这个经验值,你记一下。
二、时序基础:时钟与数据的关系
I2C的时序,说白了就是SCL和SDA怎么配合。我画个简单的时序图在脑子里:
- 数据有效性:SCL高电平时,SDA上的数据必须稳定。SCL低电平时,SDA可以变化。
- 时钟频率:标准模式100kHz,快速模式400kHz,高速模式3.4MHz。我建议新手先用100kHz,稳定了再提速。
- 时序参数:上升时间、下降时间、保持时间、建立时间。这些在芯片数据手册里都有,一定要看。
我的经验:我曾经在一个项目里,因为SCL上升沿太慢,导致从设备误判数据。后来发现是总线电容太大,上拉电阻没选对。换成2.2kΩ后,问题解决。所以,时序参数不是摆设,真会出问题。
三、起始条件与停止条件
这两个条件是I2C通信的“开关”。没有它们,总线上的设备不知道什么时候开始、什么时候结束。
起始条件(S):SCL高电平时,SDA从高电平跳变到低电平。
停止条件(P):SCL高电平时,SDA从低电平跳变到高电平。
你想想看,这两个条件其实很巧妙——它们利用了SCL高电平时SDA不能变化的规则。起始和停止条件恰恰是在SCL高电平时改变SDA,所以所有设备都能识别出这是“特殊信号”。
避坑指南:我曾经遇到过一个问题:在发送停止条件前,SDA被从设备拉低了。结果停止条件发不出去,总线一直卡住。后来我加了一个超时机制,如果SDA被拉低超过一定时间,就强制复位总线。这个经验,你可能会用上。
四、数据帧格式:一次完整的通信
一次I2C通信,数据帧格式是这样的:
- 起始条件:主机发送S信号。
- 从机地址:7位地址 + 1位读写位(0=写,1=读)。
- 应答位(ACK):从机拉低SDA表示应答。
- 数据字节:8位数据 + 1位应答。可以连续发送多个字节。
- 停止条件:主机发送P信号。
举个例子,读取一个温度传感器:
// 伪代码示例:读取温度传感器
// 第一步:主机发送起始条件 + 从机地址(写)
START
SDA: 0x48 << 1 | 0 // 地址0x48,写操作
ACK from slave
// 第二步:主机发送寄存器地址
SDA: 0x00 // 温度寄存器地址
ACK from slave
// 第三步:主机发送重复起始条件 + 从机地址(读)
REPEATED START
SDA: 0x48 << 1 | 1 // 地址0x48,读操作
ACK from slave
// 第四步:主机读取数据
SDA: 0x1A // 温度高字节
ACK from master
SDA: 0x3C // 温度低字节
NACK from master // 最后一字节不应答
// 第五步:停止条件
STOP
关键点:读操作时,最后一字节主机要发送NACK(不应答),然后发送停止条件。这是告诉从机“我读完了,别发了”。我见过新手在这里踩坑,一直发ACK,结果从机不停发数据,总线乱套。
五、应答机制:通信的握手信号
I2C的应答机制很简单:
- ACK(应答):接收方在第9个时钟周期拉低SDA。
- NACK(非应答):接收方在第9个时钟周期保持SDA高电平。
应答的作用:
- 从机应答:表示从机收到了地址或数据。
- 主机应答:表示主机还想继续接收数据。
- 主机NACK:表示主机不想再接收数据了。
我的习惯:每次写I2C驱动,我都会先检查应答位。如果从机没应答,我会重试三次。如果还不行,就报错。这个机制帮我抓出过不少硬件问题——比如焊接不良、地址写错、从机没上电。
六、实际项目中的常见问题
最后,我总结几个我在项目中遇到的实际问题:
| 问题 | 现象 | 解决方法 |
|---|---|---|
| 总线卡死 | SDA一直被拉低 | 发送9个时钟脉冲,让从机释放总线 |
| 地址冲突 | 多个从机地址相同 | 检查数据手册,用地址引脚区分 |
| 时序不匹配 | 通信偶尔失败 | 降低时钟频率,检查上拉电阻 |
| 噪声干扰 | 数据错误 | 加滤波电容,缩短总线长度 |
嗯,今天的内容就到这里。I2C协议基础其实不复杂,但细节很多。你只要把物理层、时序、起始/停止条件、数据帧格式这四块吃透,后面学高级特性就轻松了。下一讲,我们会深入I2C的时钟拉伸和多主机仲裁,到时候再聊。
课后练习:用逻辑分析仪抓取一个I2C传感器的通信波形,标出起始条件、地址、数据、应答和停止条件。这是理解I2C最好的方式,没有之一。