1. I2C协议基础回顾:物理层、协议层、时序图解读
各位同学,咱们正式开始之前,我先说句实在话。I2C这个协议,看着简单,但真要在项目里把它调稳了,尤其是挂上三四个从机的时候,坑是真不少。我最早用STM32做I2C的时候,也以为不就是两根线嘛,结果被从机地址冲突、时钟延展、总线锁死这些问题折腾了好几个通宵。
所以第一章,咱们先把地基打牢。物理层长什么样?协议层怎么握手?时序图到底怎么看?这些东西搞不清楚,后面调试故障定位就是瞎蒙。
1.1 物理层:就两根线,但别小看它们
I2C总线物理上只需要两根线:SCL(串行时钟线)和SDA(串行数据线)。所有设备都挂在这两根线上,主从机共用。
关键特征:
- SCL和SDA都是开漏输出结构,必须外接上拉电阻
- 总线空闲时,两根线都被上拉电阻拉到高电平
- 任何设备都可以拉低总线——这就是“线与”特性
- 同一总线上最多可挂127个从机(7位地址模式)
我习惯把上拉电阻选在4.7kΩ左右,标准模式100kHz下基本够用。如果总线电容大或者线长,可以换2.2kΩ。但别太小,否则功耗会上去。
我曾经踩过的坑:有一次调试一块板子,SDA波形一直不对,示波器一看,上升沿慢得像爬坡。查了半天,发现上拉电阻焊了10kΩ,总线电容又大,信号根本建立不起来。换成3.3kΩ后,波形立马干净了。
物理层还有一个重要概念——多主机仲裁。多个主机同时尝试控制总线时,谁先拉低SDA谁就赢。这个机制靠的是开漏结构的“线与”特性,说白了就是谁先动手谁说了算。
1.2 协议层:起始、停止、数据、应答
协议层其实就四个基本动作,你把它记熟了,后面看时序图就跟看乐谱一样。
| 动作 | SCL状态 | SDA状态 | 说明 |
|---|---|---|---|
| 起始条件(S) | 高电平 | 下降沿(高→低) | SCL高时SDA跳变,表示传输开始 |
| 停止条件(P) | 高电平 | 上升沿(低→高) | SCL高时SDA跳变,表示传输结束 |
| 数据位传输 | 低电平时改变SDA | 高电平时采样SDA | SCL高时SDA必须稳定 |
| 应答位(ACK/NACK) | 第9个时钟脉冲 | 从机拉低为ACK | 从机不拉低表示NACK |
数据格式很固定:起始条件 → 7位从机地址 + 读/写位 → 应答位 → 数据字节 + 应答位 → ... → 停止条件。每个字节8位,高位在前(MSB first)。
个人经验:调试的时候,我最先看的就是应答位。如果从机没拉低ACK,八成是地址写错了,或者从机根本没上电。别急着怀疑时序,先查电源和地址。
1.3 时序图解读:别被波形吓到
很多同学看到I2C时序图就头大,其实拆开看就四个参数:建立时间、保持时间、上升时间、下降时间。标准模式100kHz下,这些时间都比较宽松,但快速模式400kHz就得小心了。
我画了一张时序结构图,帮你把整个流程串起来:
你看,整个传输过程就是:起始 → 地址+R/W → 应答 → 数据 → 应答 → ... → 停止。每个字节9个时钟(8位数据+1位应答),节奏非常固定。
1.4 时钟延展:从机的“慢点,我跟不上”
时钟延展(Clock Stretching)是I2C协议里一个容易被忽略的特性。从机如果处理不过来,可以拉低SCL,告诉主机“你慢点,我还没准备好”。主机检测到SCL被拉低,就会等待,直到从机释放SCL。
实际影响:
- STM32硬件I2C外设默认支持时钟延展
- 但有些从机(比如某些传感器)延展时间过长,会导致超时
- 我建议在I2C驱动里加一个超时处理,别死等
我记得有一次用STM32F103读一个温湿度传感器,读回来的数据偶尔会错。示波器一看,从机在应答位之后延展了SCL将近2ms,而我的I2C超时设得太短,主机直接放弃了传输。把超时放宽到5ms后,问题就解决了。
1.5 常见时序参数速查
不同模式下,时序参数差别很大。我整理了一张速查表,调试时对照着看:
| 参数 | 标准模式(100kHz) | 快速模式(400kHz) | 说明 |
|---|---|---|---|
| SCL时钟频率 | ≤100 kHz | ≤400 kHz | 实际频率由主机决定 |
| SCL低电平时间 | ≥4.7 μs | ≥1.3 μs | 保证数据建立时间 |
| SCL高电平时间 | ≥4.0 μs | ≥0.6 μs | 保证数据保持时间 |
| SDA建立时间 | ≥250 ns | ≥100 ns | SCL上升沿前SDA需稳定 |
| SDA保持时间 | ≥0 ns | ≥0 ns | SCL下降沿后SDA保持 |
| 上升时间 | ≤1000 ns | ≤300 ns | 受上拉电阻和总线电容影响 |
调试小技巧:用示波器看波形时,重点看SCL低电平时间和SDA建立时间。如果这两个参数不满足,数据采样就会出错。我习惯在代码里把I2C时序配置得比手册要求宽松20%,留点余量。
1.6 多从机通信的物理基础
为什么I2C能挂多个从机?因为每个从机都有唯一的地址。主机发送地址时,所有从机都会收到,但只有地址匹配的那个才会应答。其他从机保持沉默,继续监听总线。
这里有个容易忽略的点:从机地址冲突。如果两个从机用了同一个地址,它们会同时应答,导致总线数据混乱。我遇到过一块板子上两个传感器默认地址都是0x48,结果读数据时两个一起响应,数据完全对不上。
避坑指南:选型时一定要确认每个从机的I2C地址是否可配置。如果不可配置,就得用I2C多路复用器(比如TCA9548A)来分时访问。我曾经为了省一个I2C多路复用器的钱,硬着头皮改PCB,结果得不偿失。