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协议看起来简单,但实际项目中容易出问题的往往就是这些基础细节。我个人建议,你在调WM8960之前,先用逻辑分析仪抓一下I2C波形,确认起始条件、地址、数据、应答位都符合预期。这一步做好了,后面调试音频通路会顺畅很多。
嗯,这一章就到这里。记住:I2C的物理层和时序是根基,根基不牢,后面全是坑。