3. I2C总线协议:物理层、起始/停止条件、数据有效性、地址与读写位、应答机制
各位同学,今天我们来聊聊I2C总线。说实话,I2C是我个人非常喜欢的一种接口协议。为什么?因为它简单、优雅,两根线就能搞定一堆设备的通信。我在做嵌入式项目的那些年,几乎每个板子上都能看到它的身影。
但别被它的简单外表骗了。越简单的协议,坑往往越深。我曾经在一个量产项目上,因为I2C时序差了那么零点几微秒,导致整批产品在高温下随机死机。嗯,那滋味可不好受。所以今天咱们把I2C的每个细节都掰开揉碎了讲清楚。
3.1 物理层:两根线,一个故事
I2C总线物理上只需要两根线:
- SCL(串行时钟线):由主设备驱动,控制通信节奏
- SDA(串行数据线):双向数据线,主从设备都能拉低
这两根线都是开漏输出结构。什么叫开漏?说白了,就是芯片只能把线拉低到GND,不能主动拉高。那高电平谁来给?靠外部的上拉电阻。
关键参数:
- 标准模式:100 kHz
- 快速模式:400 kHz
- 高速模式:3.4 MHz
- 上拉电阻:通常4.7kΩ(100kHz时),400kHz时可能需要2.2kΩ
我个人的习惯是,如果总线上挂的设备超过4个,或者线长超过10cm,我会把上拉电阻换小一点。比如从4.7k换成2.2k。为什么?因为总线电容大了,上升沿会变缓,容易导致时序违规。你想想看,SCL的上升沿太缓,从设备可能误判时钟边沿。
避坑指南:我曾经在一个项目里,为了省电把上拉电阻换成了100kΩ。结果呢?总线根本跑不起来,波形像正弦波一样圆润。从那以后,我选上拉电阻再也不敢凭感觉了,都是先算一下RC时间常数。
3.2 起始条件和停止条件
I2C总线的通信,以起始条件开始,以停止条件结束。这两个条件非常特殊——它们发生在SCL为高电平时,SDA发生跳变。
| 条件 | SDA行为 | SCL状态 |
|---|---|---|
| 起始条件(S) | 从高电平跳变到低电平 | 高电平 |
| 停止条件(P) | 从低电平跳变到高电平 | 高电平 |
为什么要在SCL高电平时跳变?因为正常数据传输时,SDA只能在SCL低电平时变化。SCL高电平时SDA必须保持稳定。所以,在SCL高电平时改变SDA,就成了一个「特殊信号」。
我记得刚学I2C时,总觉得这个设计很巧妙。用两根线的电平组合,就能定义出起始和停止,省掉了一根额外的控制线。
3.3 数据有效性:时钟的节奏
数据传输的规则其实很简单:
- SCL低电平:SDA可以变化(发送方准备下一位数据)
- SCL高电平:SDA必须稳定(接收方采样数据)
每个时钟脉冲传输1位数据。一个字节(8位)需要8个时钟脉冲。但注意,I2C是MSB优先,也就是先传最高位。
小技巧:调试时,我习惯用逻辑分析仪抓SCL和SDA的波形。先看SCL高电平时SDA是否稳定。如果看到SDA在SCL高电平时有毛刺或抖动,那基本可以断定是时序问题或者上拉电阻不合适。
3.4 地址与读写位:找到对的人
起始条件之后,主设备要发送的第一个字节是从设备地址+读写位。格式如下:
bit 7 - bit 1: 7位从设备地址
bit 0: 读写位(0=写,1=读)
举个例子,假设一个温度传感器的地址是0x48(二进制1001000),你要读它:
发送字节: 1001000 1 = 0x91
↑地址 ↑读位
如果是写操作:
发送字节: 1001000 0 = 0x90
↑地址 ↑写位
这里有个容易混淆的地方。很多数据手册上写的设备地址是7位值,比如0x48。但你在代码里发送时,要左移一位再加上读写位。我刚开始做的时候,就因为这个吃了亏——直接发了0x48,结果设备死活没反应。
10位地址模式:有些设备地址不够用,I2C还支持10位地址。第一个字节的前5位是11110,后2位是地址的高2位。第二个字节是剩下的8位。不过说实话,我做了这么多年,用到10位地址的场景屈指可数。大部分时候7位地址(最多128个设备)完全够用。
3.5 应答机制:握手的艺术
I2C的每个字节后面,都要跟一个应答位。这是协议里我最欣赏的设计——它让通信变得可靠。
应答机制是这样的:
- 发送方发出8位数据后,释放SDA线(拉高)
- 发送方在第9个时钟脉冲时,产生一个SCL时钟
- 接收方如果收到数据,就把SDA拉低(应答ACK)
- 如果接收方不拉低SDA(保持高电平),就是非应答NACK
什么时候会出现NACK?
- 从设备忙,无法处理数据
- 从设备没收到完整数据
- 主设备读数据时,读完最后一个字节后发送NACK,告诉从设备「别再发了」
我曾经踩过的坑:有一次,我写的I2C驱动在读取传感器数据时,总是卡在第一个字节。用示波器一看,从设备确实发出了ACK,但我的代码没检测到。后来发现,是中断服务程序占用了太多时间,导致检测ACK时SCL已经过去了。从那以后,我写I2C驱动都会用状态机,确保每个时序步骤都不被中断打断。
3.6 完整通信流程
把上面这些拼起来,一次完整的I2C写操作是这样的:
主设备: [起始条件] [地址+写位] [等待ACK] [数据字节1] [等待ACK] ... [停止条件]
从设备: [拉低ACK] [拉低ACK]
读操作稍微不同:
主设备: [起始条件] [地址+读位] [等待ACK] [接收数据] [发送NACK] [停止条件]
从设备: [拉低ACK] [发送数据]
注意读操作的最后,主设备要发送NACK。这是告诉从设备:「我读完了,别再发了」。如果主设备发了ACK,从设备会以为你还要继续读,就会继续发下一个字节。
3.7 知识体系总览
下面这张图,是我自己总结的I2C协议知识结构。每次做项目前,我都会对照着检查一遍,确保没有遗漏。
好了,I2C协议的核心内容就这些。你想想看,其实I2C的设计哲学就是「简单可靠」。两根线,一个时钟,一个数据,再加上应答机制,就能让多个设备有序通信。下次你在板子上看到I2C总线时,不妨想想今天讲的这些细节——特别是那个上拉电阻,别像我一样踩坑。