I2C协议基础:从物理层到仲裁机制

各位同学,今天我们来聊聊I2C协议。说实话,I2C在嵌入式系统里太常见了,但很多人只是会用库函数,真遇到问题就抓瞎。我记得刚入行那会儿,调一个I2C的音频编解码芯片,死活读不到寄存器,折腾了两天才发现是上拉电阻选错了。所以这一章,咱们把I2C的底裤扒干净,后面调WM8960的时候你心里才有底。

I2C总线物理层

I2C总线就两根线:SCL(时钟线)和SDA(数据线)。所有设备都挂在这两根线上,主设备负责产生时钟,从设备响应。说白了,这就是一个"一主多从"的通信模型。

这里有个关键点:I2C总线是开漏输出结构。什么意思?就是芯片只能把总线拉低,不能主动拉高。高电平靠外部上拉电阻实现。我见过不少新手直接把IO口配置成推挽输出,结果通信一塌糊涂。

上拉电阻选型经验:

  • 标准模式(100kHz):4.7kΩ ~ 10kΩ
  • 快速模式(400kHz):1.5kΩ ~ 4.7kΩ
  • 高速模式(3.4MHz):需要更小的电阻,但STM32一般不跑这个模式

我曾经踩过的坑:用10kΩ上拉电阻跑400kHz,总线电容稍微大一点,上升沿就变缓了,导致从设备采样出错。后来换成2.2kΩ,问题解决。所以实际项目中,建议用示波器看一下波形,确保上升沿够陡。

起始条件与停止条件

I2C总线的数据传输,以起始条件开始,以停止条件结束。这两个条件由主设备产生,是总线上所有设备同步的"发令枪"。

条件 SCL状态 SDA状态 说明
起始条件(S) 高电平 从高电平跳变到低电平 总线从空闲变为忙碌
停止条件(P) 高电平 从低电平跳变到高电平 总线从忙碌变为空闲

嗯,这里要注意:起始和停止条件都是在SCL高电平时检测SDA的变化。正常数据传输时,SDA只能在SCL低电平时变化。这个时序关系搞反了,通信就会乱套。

数据帧格式

I2C的数据帧格式其实很规整。每个字节8位,后面跟一个应答位(ACK/NACK)。我习惯把一次完整的I2C传输称为一个"事务",一个事务可以包含多个字节。

标准的数据帧结构如下:

起始条件 | 7位从机地址 | 读/写位 | 应答位 | 数据字节1 | 应答位 | ... | 数据字节N | 应答位 | 停止条件

举个例子,我们要向WM8960的某个寄存器写数据:

主设备发送:起始条件
主设备发送:0x34(WM8960地址7位为0x1A,左移1位,写操作为0)
从设备应答:ACK
主设备发送:0x00(寄存器地址高字节)
从设备应答:ACK
主设备发送:0x1F(寄存器地址低字节)
从设备应答:ACK
主设备发送:0x00(数据高字节)
从设备应答:ACK
主设备发送:0x80(数据低字节)
从设备应答:ACK
主设备发送:停止条件

个人习惯:我写I2C驱动时,会把每个事务的完整时序用逻辑分析仪抓下来,跟数据手册对比。这样调试效率最高,比盯着代码猜快多了。

7位地址模式与10位地址模式

I2C支持两种地址模式:7位和10位。WM8960用的是7位地址,所以我们重点讲这个。

特性 7位地址模式 10位地址模式
地址范围 0x00 ~ 0x7F(128个设备) 0x000 ~ 0x3FF(1024个设备)
首字节格式 7位地址 + R/W位 11110xx + 地址高2位 + R/W位
后续字节 无 地址低8位
常用场景 大多数传感器、音频芯片 需要大量从设备的复杂系统

WM8960的7位地址是0x1A(ADDR引脚接地)或0x1B(ADDR引脚接高)。实际发送时,左移1位变成0x34或0x36,最低位是读写标志。这个转换关系,我建议你直接记在代码注释里,省得每次都要算。

时钟同步与仲裁

这两个机制是I2C协议的精髓,也是很多开发者容易忽略的地方。

时钟同步

I2C总线上可以有多个主设备。时钟同步保证这些主设备能协调SCL信号。说白了,就是所有主设备一起产生时钟,谁先把SCL拉低,其他人就得跟着等。谁最后一个释放SCL,时钟周期就以谁为准。

为什么会这样?因为开漏结构下,只要有一个设备拉低SCL,总线就是低电平。所以时钟同步实际上是"线与"的结果。

仲裁

仲裁解决的是"多个主设备同时发送数据怎么办"的问题。原理很简单:谁先发送低电平,谁就赢得仲裁。因为SDA也是线与结构,发送高电平的设备检测到总线是低电平,就知道自己输了,自动退出。

仲裁的关键规则:

  • 仲裁在SDA上进行,SCL由时钟同步协调
  • 丢失仲裁的主设备必须释放SDA和SCL
  • 仲裁不会丢失数据,赢得仲裁的设备继续传输
  • 地址阶段和数据阶段都可能发生仲裁

我曾经遇到的情况:在一个项目里,两个STM32通过I2C通信,都配置成了主设备。结果数据经常出错,查了半天才发现是仲裁机制没处理好。后来改成单一主设备,问题消失。所以,除非必要,我建议你尽量用一主多从的拓扑,省心很多。

I2C协议核心流程总览

下面这张图把I2C协议的核心流程串起来了,从物理层到仲裁,你看完应该能有个整体印象。

I2C协议核心流程总览 I2C协议核心流程总览 物理层 SCL + SDA 开漏输出 上拉电阻 4.7kΩ ~ 10kΩ 起始/停止条件 SCL高时SDA跳变 数据帧格式 地址 + R/W + 数据 + ACK 地址模式 7位地址(0x00~0x7F) 10位地址(0x000~0x3FF) 时钟同步 多主设备协调SCL 线与机制,谁慢谁决定 仲裁机制 SDA线与,低电平优先 丢失仲裁自动释放总线 总结:I2C协议 = 物理层 + 时序控制 + 地址寻址 + 多主协调 理解这些基础,调WM8960的I2C接口就轻松多了

写在最后

I2C协议看起来简单,但实际项目中容易出问题的往往就是这些基础细节。我个人建议,你在调WM8960之前,先用逻辑分析仪抓一下I2C波形,确认起始条件、地址、数据、应答位都符合预期。这一步做好了,后面调试音频通路会顺畅很多。

嗯,这一章就到这里。记住:I2C的物理层和时序是根基,根基不牢,后面全是坑。