1. I2C协议基础:物理层特性与协议层规范
各位同学,咱们今天聊聊I2C。这个协议在嵌入式开发里太常见了——传感器、EEPROM、ADC、DAC,几乎每个板子上都能见到它的身影。我刚开始做MCU开发那会儿,总觉得I2C就是两根线的事儿,没啥好研究的。结果呢?一次项目里EEPROM死活读不对数据,折腾了两天才发现是上拉电阻选错了。从那以后,我再也不敢小看这两根线了。
说白了,I2C的成功秘诀就两个:物理层简单,协议层灵活。咱们一个一个拆开讲。
1.1 物理层特性:SDA/SCL开漏输出
I2C总线只有两根线:SDA(串行数据线)和SCL(串行时钟线)。但这两根线的驱动方式很特别——开漏输出。
什么叫开漏?你可以理解为:MOS管的漏极是“开”的,没有内部上拉。芯片只能把引脚拉低到GND,但拉不上去。要让它变成高电平,必须靠外部上拉电阻。
开漏输出的核心特点:
- 芯片只能输出低电平(0)或高阻态(Z)
- 高电平由外部上拉电阻提供
- 多个设备可以“线与”——谁拉低谁说了算
为什么I2C要用开漏?我个人的理解是:为了多设备共用总线时不打架。你想啊,如果两个设备同时往一根线上输出,一个输出1一个输出0,那就短路了。开漏模式下,谁想拉低就拉低,谁想发高电平就释放总线,靠上拉电阻自动拉高。这就是“线与”逻辑——只要有一个设备拉低,总线就是低电平。
1.2 上拉电阻选择:不是随便焊一个就行
上拉电阻选多大?这个问题我面试过不少人,十个里有八个答不上来。其实就两个约束:
| 约束条件 | 说明 | 典型值 |
|---|---|---|
| 最小电阻 | 由灌电流能力决定。电阻太小,拉低时电流过大,会烧坏IO口 | ≥ 1.6kΩ(VCC=3.3V,IO灌电流≤2mA) |
| 最大电阻 | 由总线电容和上升时间决定。电阻太大,上升沿太慢,时序不满足 | ≤ 10kΩ(100kHz,总线电容≤200pF) |
实际项目中,我一般这么选:
- 100kHz标准模式:4.7kΩ 或 10kΩ,通用
- 400kHz快速模式:2.2kΩ 或 3.3kΩ,保证上升沿够陡
- 1MHz快速+模式:1kΩ 左右,但要注意灌电流是否超限
我的经验:如果总线上挂的设备多(超过4个),或者走线比较长(超过10cm),建议用示波器实测一下上升时间。我曾经在一个项目里用了10kΩ电阻,总线电容太大,SCL上升沿拖到了1μs,100kHz都跑不稳。换成4.7kΩ后问题解决。
1.3 协议层规范:起始条件与停止条件
I2C的通信由主设备发起,以起始条件开始,以停止条件结束。这两个条件是总线上最关键的信号——所有设备都在盯着它们。
起始条件(S):
- SCL为高电平时,SDA从高电平跳变到低电平
- 只有主设备可以发出起始条件
- 通信开始前,总线必须处于空闲状态(SDA和SCL都是高电平)
停止条件(P):
- SCL为高电平时,SDA从低电平跳变到高电平
- 停止条件发出后,总线释放,回到空闲状态
注意:起始条件和停止条件都是SCL高电平时SDA的跳变。而在数据传输过程中,SCL高电平时SDA必须保持稳定。这个区别很重要——我曾经见过有人把数据线上的跳变误判成起始条件,导致整个通信错乱。
1.4 数据有效性:SCL高电平时采样
I2C的数据传输规则很简单:
- SCL低电平时:SDA可以变化(数据准备阶段)
- SCL高电平时:SDA必须稳定(数据采样阶段)
每个时钟脉冲传输1位数据。先传高位(MSB),再传低位(LSB)。8个时钟脉冲传完一个字节后,第9个时钟脉冲用于应答。
你想想看,这个设计其实很巧妙。SCL低电平时改变数据,高电平时采样——这样只要保证SCL高电平期间SDA不变,就不会采错。抗干扰能力比UART那种异步采样强多了。
1.5 应答机制:ACK与NACK
应答是I2C协议里最容易被忽视、但又最容易出问题的地方。每个字节传输完成后,接收方必须在第9个时钟周期拉低SDA,表示“我收到了”。
应答规则:
- ACK(应答):接收方在第9个时钟拉低SDA
- NACK(非应答):接收方在第9个时钟释放SDA(保持高电平)
什么情况下会出现NACK?
- 从设备地址不匹配
- 从设备正忙,无法接收数据
- 主设备读数据时,读完最后一个字节后发送NACK,通知从设备停止发送
避坑指南:我曾经在一个项目里发现,某个传感器在掉电重启后,第一次通信总是NACK。后来查手册才发现,这个传感器上电后需要5ms的稳定时间。如果在稳定时间内访问它,它根本不会应答。解决方案很简单——在每次通信前加一个10ms的延时。
1.6 知识结构总览
下面这张图把I2C协议基础的核心知识点串起来了。你可以把它当作本章的“思维导图”。
1.7 本章小结
I2C协议基础,说白了就是两件事:物理层怎么接线,协议层怎么握手。物理层搞不定,上拉电阻选不对,通信根本起不来。协议层搞不清楚,起始条件、停止条件、应答机制弄混了,数据传过去也是错的。
我个人建议,初学者先别急着写代码。拿示波器或者逻辑分析仪,看看实际波形——起始条件长什么样?停止条件怎么跳变的?ACK时SDA是不是被拉低了?亲眼看过一次,比读十遍手册都管用。