2. I2C协议深度解析:从物理层到仲裁机制
I2C协议,说实话,是嵌入式开发里最常用的串行总线之一。我做了这么多年驱动,跟它打交道最多。它只有两根线,却能挂一堆设备,这个设计本身就很有智慧。
咱们这一章,就把I2C从物理层到仲裁机制彻底讲透。你以后写驱动、调bug,心里就有底了。
2.1 I2C总线物理层:两根线的艺术
I2C总线就两根线:SCL(时钟线)和SDA(数据线)。所有设备都挂在这两根线上。
这两根线都是开漏输出结构。什么意思?说白了,设备只能把线拉低,不能主动拉高。拉高靠的是上拉电阻。
关键点:空闲时,SCL和SDA都是高电平。谁要通信,谁就把线拉低。
上拉电阻的取值有讲究。我早期一个项目,选了10kΩ上拉,总线长度半米,结果通信老出错。后来换成4.7kΩ,问题就解决了。
| 总线电容 | 推荐上拉电阻 | 最大速率 |
|---|---|---|
| < 100 pF | 10 kΩ | 100 kHz |
| 100 - 200 pF | 4.7 kΩ | 400 kHz |
| 200 - 400 pF | 2.2 kΩ | 1 MHz |
注意:上拉电阻太小,功耗大;太大,信号上升沿变缓,高速通信会失败。我一般先按4.7kΩ起步,再根据波形微调。
2.2 起始条件与停止条件
I2C通信的开始和结束,有专门的信号。这两个信号是唯一允许SDA在SCL高电平时变化的情况。
- 起始条件(S):SCL高电平时,SDA从高变低。
- 停止条件(P):SCL高电平时,SDA从低变高。
你想想看,如果SCL低电平时SDA变了,那是在传数据。只有SCL高电平时SDA跳变,才表示开始或结束。这个设计很巧妙,不会混淆。
经验:我调试时,习惯用逻辑分析仪抓起始和停止条件。如果波形里这两个信号不干净,基本可以断定是上拉电阻或时序问题。
2.3 数据帧格式:字节与应答
I2C的数据传输,以字节为单位。每传一个字节,后面跟一个应答位(ACK)。
流程是这样的:
- 主机发送起始条件
- 主机发送7位从机地址 + 1位读写位(0写1读)
- 从机在第9个SCL周期拉低SDA,表示应答(ACK)
- 主机发送/接收数据字节,每字节后跟应答
- 主机发送停止条件
应答位是I2C的可靠性保障。如果从机没拉低SDA(即SDA保持高),那就是NACK,表示从机没准备好或地址不对。
我遇到过:一次从机地址写错了,主机发地址后一直收不到ACK。逻辑分析仪一看,SDA在第9个时钟还是高。改地址后就好了。所以,检查ACK是调试的第一步。
2.4 7位地址与10位地址
I2C地址有两种格式:7位和10位。7位地址最多挂127个设备(地址0x00保留),实际够用。但有些场景设备多,就需要10位地址。
10位地址怎么传?它用两个字节:
- 第一个字节:1111 0XX + R/W(XX是10位地址的高2位)
- 第二个字节:10位地址的低8位
说白了,就是用一个特殊的前缀来标识这是10位地址。从机看到1111 0开头,就知道后面还有一字节地址。
| 地址类型 | 地址范围 | 最大设备数 | 应用场景 |
|---|---|---|---|
| 7位地址 | 0x08 - 0x77 | 112 | 大多数传感器、EEPROM |
| 10位地址 | 0x00 - 0x3FF | 1024 | 复杂系统、多设备总线 |
建议:除非必要,优先用7位地址。10位地址的驱动实现稍复杂,而且不是所有从机都支持。
2.5 时钟同步与仲裁
这两个机制是I2C多主机通信的核心。我当年第一次接触时,觉得挺绕,但弄懂了就觉得设计得很精妙。
时钟同步
多个主机同时发时钟时,SCL的低电平时间由最长的那个决定,高电平时间由最短的那个决定。结果就是,所有主机的时钟被同步到一个共同的节奏上。
为什么会这样?因为开漏结构。只要有一个主机拉低SCL,SCL就是低。只有所有主机都释放SCL,SCL才会变高。所以,慢设备会拖慢总线。
仲裁
多个主机同时发数据时,谁先拉低SDA,谁就赢得仲裁。输的那个主机,必须退出并重试。
仲裁过程是无损的。赢的主机继续通信,输的检测到SDA与自己发出的不一致,就知道输了,立即释放总线。
注意:仲裁只在数据位上进行。起始条件和停止条件不参与仲裁。如果两个主机同时发起始条件,那就会产生一个合法的起始条件,没问题。
我有个项目,两个MCU通过I2C通信,偶尔数据错乱。查了半天,发现是仲裁逻辑没处理好。一个主机发完数据后,没等总线空闲就又开始发,导致冲突。后来加了总线空闲检测,问题解决。
2.6 知识体系总览
下面这张图,把I2C协议的核心知识点串起来了。你可以把它当一张地图,哪里不懂就回来看。
2.7 避坑指南
最后,分享几个我踩过的坑:
- 上拉电阻选型:别照搬参考设计。根据你的总线长度和速率,实际测试一下波形。
- 地址冲突:两个从机地址一样,总线就乱了。选型时注意地址可配置性。
- 时钟延展:有些从机处理慢,会拉低SCL让主机等。驱动里要处理这种情况,否则会超时。
- 总线锁死:从机异常时可能一直拉低SDA。我遇到过,后来加了超时复位逻辑才解决。
我的习惯:每个I2C驱动都加一个超时参数。默认100ms,如果总线没响应,就复位从机或重试。这个习惯救了我好几次。