1. I2C协议基础:物理层特性与协议层帧格式
各位同学,咱们今天正式开讲I2C协议。说实话,I2C这玩意儿看着简单,但坑是真不少。我当年刚入行时,就被一个上拉电阻的问题折腾了两天——板子调不通,示波器一抓,波形跟鬼画符似的。后来才发现,电阻值选错了。
好,咱们从头捋一遍。I2C,全称Inter-Integrated Circuit,是飞利浦(现NXP)在80年代搞出来的。它只用了两根线:SDA(数据线)和SCL(时钟线)。两根线挂一堆设备,想想就挺省引脚的对吧?
1.1 物理层特性:开漏输出与上拉电阻
I2C物理层最核心的一个概念——开漏输出。说白了,就是SDA和SCL这两根线,芯片内部只能把它们拉到低电平(GND),但拉不上去。那高电平谁来拉?靠外部上拉电阻。
核心要点:I2C总线上的所有设备,SDA和SCL引脚都是开漏结构。总线空闲时,两根线都被上拉电阻拉到高电平。任何设备想发信号,就把线拉低。
为什么要这么设计?你想想看,如果多个设备共用一根线,一个想拉高、一个想拉低,那不就短路了吗?开漏加外部上拉,本质上实现了“线与”逻辑——只要有一个设备拉低,整条线就是低电平。这样多主设备仲裁才能实现。
上拉电阻的取值,我建议你记住这个范围:1kΩ ~ 10kΩ。具体选多少,看总线电容和通信速率。
| 总线速率模式 | 标准时钟频率 | 推荐上拉电阻范围 | 典型总线电容 |
|---|---|---|---|
| 标准模式(Sm) | 100 kHz | 4.7kΩ ~ 10kΩ | ≤ 200 pF |
| 快速模式(Fm) | 400 kHz | 2.2kΩ ~ 4.7kΩ | ≤ 200 pF |
| 快速+模式(Fm+) | 1 MHz | 1kΩ ~ 2.2kΩ | ≤ 100 pF |
| 高速模式(Hs) | 3.4 MHz | 1kΩ 以下(需电流源辅助) | ≤ 100 pF |
避坑指南:我曾经在一个项目里,为了省电把上拉电阻选到了47kΩ。结果400kHz通信时,上升沿慢得像蜗牛爬,数据全错。后来用示波器一看,上升时间都快赶上半个时钟周期了。记住:电阻太大,上升沿变缓;电阻太小,功耗增加,且可能超出引脚的灌电流能力。
1.2 协议层帧格式:一次完整的数据传输
好,物理层讲完了,咱们看看数据是怎么在线上跑的。I2C的帧格式其实很规整,我习惯把它拆成六个步骤来记:
- 起始条件(START):SCL高电平时,SDA从高变低。
- 从机地址(7位或10位):主机发送要通信的从机地址。
- 读写位(R/W):0表示写,1表示读。
- 应答位(ACK/NACK):从机收到地址后,拉低SDA表示应答。
- 数据字节(8位):每次传一个字节,后面紧跟应答位。
- 停止条件(STOP):SCL高电平时,SDA从低变高。
个人习惯:我调试I2C时,最喜欢用逻辑分析仪抓起始条件和停止条件。只要这两个信号对了,后面数据基本不会跑偏。如果抓不到起始条件,先查上拉电阻和电源。
咱们用一张SVG图把整个帧结构画出来,这样更直观:
1.3 起始条件与停止条件
这两个条件,是I2C通信的“开始”和“结束”信号。我当年刚学的时候,总觉得跟UART的起始位停止位差不多。其实完全不是一回事。
- 起始条件(S):SCL为高电平时,SDA由高电平切换到低电平。这个切换必须由主机完成。
- 停止条件(P):SCL为高电平时,SDA由低电平切换到高电平。同样由主机发起。
注意:起始条件和停止条件之间,可以插入一个重复起始条件(Sr)。什么意思呢?就是主机不想释放总线,直接再发一个起始条件,切换读写方向。比如先写寄存器地址,再读数据——这个场景在传感器驱动里太常见了。我调试MPU6050时,几乎每次都用重复起始条件。
1.4 从机地址与读写位
起始条件之后,主机发送的第一个字节,高7位是从机地址,最低位是读写位。
7位地址模式下,理论上可以挂127个设备(地址0x00保留)。但实际中,很多芯片只支持几个可配置的地址引脚,比如A0、A1、A2。我常用的AT24C02 EEPROM,三个地址引脚,最多挂8片。
| 地址模式 | 地址位宽 | 可用地址数 | 典型应用 |
|---|---|---|---|
| 7位地址 | 7 bit | 112(0x08 ~ 0x77) | 大多数传感器、EEPROM、ADC/DAC |
| 10位地址 | 10 bit | 1024 | 复杂系统、多设备级联 |
读写位:0表示主机写数据到从机,1表示主机从从机读数据。注意,这个位是跟地址一起发的,所以地址字节 = (地址 << 1) | R/W。
1.5 应答机制(ACK/NACK)
I2C的应答机制,说白了就是“你发我确认”。每传完一个字节(8位),接收方必须在第9个时钟周期拉低SDA,表示“收到了”。
- ACK(应答):接收方拉低SDA。表示一切正常,继续发。
- NACK(非应答):接收方保持SDA高电平。表示“我不想要了”或“出错了”。
实战经验:我调试I2C设备时,第一个看的就是地址阶段的ACK。如果从机没拉低SDA,说明地址不对、设备没上电、或者总线被拉死了。有一次我查了半小时,结果发现从机地址焊错了引脚——A0本该接地,结果飞到了VCC。嗯,这种低级错误,谁还没犯过呢?
1.6 数据字节传输
数据字节是8位,MSB(最高位)先发。每个字节后面跟一个应答位。可以连续传多个字节,直到主机发停止条件或重复起始条件。
这里有个细节:时钟拉伸(Clock Stretching)。有些从机处理速度慢,会在接收完一个字节后,主动拉低SCL,告诉主机“你等等,我还没消化完”。主机检测到SCL被拉低,就会等待。这个特性在低速从机里很常见,比如某些OLED驱动芯片。
关键点:I2C协议允许从机通过时钟拉伸来降低通信速率。如果你的主机不支持时钟拉伸,碰到慢速从机就会丢数据。我建议你在选型时,确认主控的I2C外设是否支持这个特性。STM32的硬件I2C是支持的,但早期版本有bug——嗯,那是另一个故事了。
1.7 完整传输示例
咱们看一个实际例子:主机向地址0x50的EEPROM写入一个字节0xAB。
// 伪代码:I2C写一个字节到从机地址0x50
// 从机地址0x50 = 0b1010000,左移1位 = 0b10100000,写操作R/W=0
I2C_Start(); // 起始条件
I2C_SendByte(0xA0); // 发送地址字节:0x50 << 1 | 0 = 0xA0
// 等待从机ACK
I2C_SendByte(0x00); // 发送寄存器地址(高字节)
// 等待从机ACK
I2C_SendByte(0x10); // 发送寄存器地址(低字节)
// 等待从机ACK
I2C_SendByte(0xAB); // 发送数据
// 等待从机ACK
I2C_Stop(); // 停止条件
你看,整个流程就是:起始 → 地址+写 → ACK → 数据 → ACK → ... → 停止。规规矩矩,一步都不能少。
1.8 本章小结
I2C协议的基础,说白了就两件事:物理层的开漏+上拉,协议层的起始-地址-读写-应答-数据-停止。你把这六个步骤刻在脑子里,后面调试时就能快速定位问题。
我个人觉得,I2C的精髓在于它的“线与”逻辑和应答机制。多设备共用总线而不冲突,靠的就是开漏结构和ACK/NACK。下次你用示波器抓I2C波形时,试着按这个帧结构去解析,会发现一切都对得上。
好,这一章就到这儿。记住:上拉电阻别选错,地址别焊反,ACK一定要看到低电平。这三条做到了,I2C基本不会出大问题。