1. I2C协议基础:从一根线上看通信本质
做嵌入式这些年,我调试过的接口少说也有十几种。但要说哪个协议最「优雅」,我个人会投I2C一票。两根线,多个设备,就能把数据传得明明白白。不过,优雅归优雅,坑也不少。咱们先从根儿上把I2C扒清楚。
1.1 I2C总线起源与特点
I2C是Philips(现在的NXP)在1982年搞出来的。初衷很简单——让芯片之间少走几根线。你想,那时候的电路板,每加一个外设就得拉一堆并行线,布线布到想哭。I2C一出,两根线搞定所有。
它的核心特点,我总结成四个字:简单、灵活。
- 两线制:SDA(数据线)和SCL(时钟线),所有设备挂在这两根线上。
- 多主多从:总线上可以有多个主机,谁先拉低时钟谁说了算。
- 地址寻址:每个从设备有唯一7位或10位地址,主机点名通信。
- 同步时钟:时钟由主机产生,从机可以拉低时钟做「时钟拉伸」——这个后面细说。
我个人的经验:I2C最适合板内短距离通信,一米以内最稳。超过这个距离,寄生电容一上来,波形就变形了。我曾经在一个项目里硬拉了1.5米的I2C线,结果数据时不时错一位——后来加了电平转换芯片才搞定。
1.2 物理层:SDA/SCL线与上拉电阻
物理层说白了就是两根线怎么接、怎么传。咱们先看结构。
SDA和SCL都是开漏输出。什么意思?就是芯片只能把线拉低,不能主动拉高。拉高的工作交给上拉电阻。这样做的好处是——多个设备可以同时拉低总线,不会短路。
上拉电阻的取值很关键。我见过不少新手在这上面栽跟头。阻值太大,上升沿变缓,信号畸变;阻值太小,电流过大,芯片可能扛不住。
| 工作模式 | 最大频率 | 推荐上拉电阻 | 总线电容上限 |
|---|---|---|---|
| 标准模式 (Standard) | 100 kHz | 4.7 kΩ ~ 10 kΩ | 400 pF |
| 快速模式 (Fast) | 400 kHz | 1.5 kΩ ~ 4.7 kΩ | 400 pF |
| 快速+模式 (Fast+) | 1 MHz | 1 kΩ ~ 2.2 kΩ | 550 pF |
| 高速模式 (Hs) | 3.4 MHz | 0.5 kΩ ~ 1 kΩ | 100 pF |
我曾经踩过的坑:有一次用100kHz标准模式,接了10kΩ上拉,总线长度不到10cm,按理说没问题。但示波器一看,上升沿快2μs了。查了半天,发现从机芯片的输入引脚寄生电容有15pF,加上PCB走线电容,总电容超了。换成4.7kΩ,波形立马干净了。所以,上拉电阻别死记公式,拿示波器看波形最靠谱。
1.3 协议层:起始条件、停止条件、数据有效性、应答机制
物理层搞定了,咱们看看数据怎么在线上跑。I2C的协议层,说白了就是一套「握手规则」。规则不多,但每条都很关键。
1.3.1 起始条件 (Start Condition)
SCL为高电平时,SDA从高电平跳变到低电平。这就是起始信号。记住一个口诀:「时钟高,数据跳」。
为什么要在SCL高时跳变?因为正常数据传输时,SCL高电平期间SDA要保持稳定。起始条件是个「异常」状态,用来告诉所有从机:注意,主机要开始说话了。
1.3.2 停止条件 (Stop Condition)
SCL为高电平时,SDA从低电平跳变到高电平。和起始条件正好相反。停止信号一出现,总线就释放了,其他主机可以竞争总线控制权。
我的小技巧:调试时,用逻辑分析仪抓起始和停止条件。如果看到异常多的起始/停止,多半是主机程序跑飞了,或者中断里误操作了I2C外设。我遇到过一例,中断服务函数里不小心调了I2C发送函数,结果每个中断都产生一个起始条件——总线直接瘫痪。
1.3.3 数据有效性
数据传输时,SCL低电平期间SDA可以变化,SCL高电平期间SDA必须保持稳定。每个时钟脉冲传输1位数据,先传高位(MSB),再传低位(LSB)。
举个例子,你要发送0xA5(二进制1010_0101):
- 第1个时钟:SDA=1(高位)
- 第2个时钟:SDA=0
- 第3个时钟:SDA=1
- 第4个时钟:SDA=0
- 第5个时钟:SDA=0
- 第6个时钟:SDA=1
- 第7个时钟:SDA=0
- 第8个时钟:SDA=1(低位)
你看,8个时钟传一个字节。简单吧?
1.3.4 应答机制 (ACK/NACK)
每传完一个字节(8位),接收方要在第9个时钟周期拉低SDA,表示「收到了」。这就是应答位(ACK)。如果接收方不拉低(SDA保持高),就是非应答(NACK)。
应答机制是I2C最巧妙的设计之一。它让发送方实时知道数据有没有被正确接收。我见过不少工程师忽略检查应答位,结果从机没响应,主机还在傻乎乎地发数据——总线就这么卡死了。
| 场景 | 应答位状态 | 含义 |
|---|---|---|
| 主机发送地址后 | ACK (SDA低) | 对应地址的从机存在且就绪 |
| 主机发送地址后 | NACK (SDA高) | 无此地址的从机,或从机忙 |
| 主机发送数据后 | ACK (SDA低) | 从机成功接收数据 |
| 主机发送数据后 | NACK (SDA高) | 从机无法接收(如缓冲区满) |
| 主机接收数据后 | ACK (SDA低) | 主机还要继续接收 |
| 主机接收数据后 | NACK (SDA高) | 主机不再接收,准备发停止条件 |
我个人的习惯:写I2C驱动时,每发一个字节都检查ACK。如果连续3次收到NACK,我就认为从机挂了,主动复位总线(发9个时钟脉冲让从机恢复)。这个「三次NACK复位」策略帮我省了不少排查时间。
1.4 完整的数据传输流程
把上面这些拼起来,一次完整的I2C传输长这样:
- 起始条件:主机拉低SDA(SCL高时)
- 发送从机地址:7位地址 + 1位读写位(0=写,1=读)
- 等待应答:从机拉低SDA表示确认
- 发送/接收数据:每字节后跟一个应答位
- 停止条件:主机释放SDA(SCL高时)
嗯,流程就这么清晰。但实际项目中,你可能会遇到各种「不按套路出牌」的情况。比如从机需要时间处理数据,就会在应答位之后拉低SCL——这叫时钟拉伸。标准I2C主机必须支持这个特性,但有些MCU的硬件I2C外设不支持,这时候就得用软件模拟I2C。
注意:软件模拟I2C时,一定要把SCL和SDA配置为开漏输出,别配成推挽输出。推挽输出会导致总线冲突,轻则数据错乱,重则烧芯片。我刚开始学I2C时就犯过这个错,一个STM32的IO口直接冒烟了——嗯,那味道至今难忘。