2. I2C协议时序:起始条件、停止条件、数据有效性、字节格式与应答机制
说实话,I2C协议本身并不复杂。但很多新手朋友一上来就对着逻辑分析仪发懵——满屏的波形,哪个是起始?哪个是停止?数据到底什么时候有效?
我当年第一次调I2C时也这样。拿着示波器探头在板子上戳了半天,愣是没找到SCL在哪。后来才发现是杜邦线虚焊了……嗯,这些坑咱们后面慢慢聊。
这一章,咱们就把I2C协议的几个核心时序拆开揉碎。你搞懂了这些,后面写寄存器代码就是水到渠成的事。
2.1 协议概览:两条线,一个主从关系
I2C总线就两条线:SCL(时钟线)和SDA(数据线)。所有设备都挂在这两条线上,通过地址区分谁跟谁说话。
我个人习惯把SCL想象成节拍器,SDA就是跟着节拍跳舞的人。节拍器不乱,舞步就不会错。
| 信号线 | 作用 | 默认状态 |
|---|---|---|
| SCL | 时钟同步,由主机产生 | 高电平(空闲) |
| SDA | 数据传输,双向 | 高电平(空闲) |
两条线空闲时都是高电平。为什么?因为I2C用的是开漏输出,加上拉电阻。谁拉低谁说话,不拉低就保持高。这个设计很有意思——你想想看,如果两个设备同时拉低,不会短路,只会产生一个确定的低电平。这就是I2C总线仲裁的基础。
2.2 起始条件与停止条件
这两个条件是I2C通信的“开始”和“结束”标志。没有它们,总线上的设备不知道你什么时候开始说话,什么时候说完。
起始条件(START Condition)
定义很简单:SCL为高电平时,SDA从高电平切换到低电平。
我刚开始学的时候总觉得这有什么难的?不就是个下降沿吗?后来在项目里遇到一个坑:有个传感器偶尔不响应,查了半天发现是起始条件建立时间不够。SCL拉高后,SDA要等一段时间才能拉低,这个时间叫tSU;STA。标准模式下至少4.7μs,快速模式下0.6μs。
停止条件(STOP Condition)
跟起始条件反过来:SCL为高电平时,SDA从低电平切换到高电平。
停止条件发出后,总线释放,回到空闲状态。其他设备就可以发起新的通信了。
这里有个小细节:停止条件之后,主机必须释放SDA和SCL,让上拉电阻把总线拉高。我曾经见过一个同事在代码里忘了释放SDA,结果下一轮通信起始条件死活发不出去——因为SDA一直被拉低,无法产生下降沿。
2.3 数据有效性:SCL高电平时采样
这是I2C协议里最核心的规则之一:数据在SCL高电平时必须稳定,在SCL低电平时才能变化。
为什么?因为接收方是在SCL的上升沿或高电平期间采样SDA的。如果你在SCL高电平时改变了SDA,接收方可能采到错误的数据。
你想想看,如果SCL高电平时SDA抖了一下,接收方可能把1读成0,或者把0读成1。这在通信里是致命的。
所以,发送方必须在SCL低电平时准备好下一个bit的数据,然后在SCL高电平时保持稳定。这个时序要求,在硬件I2C外设里由硬件自动保证。但如果你用GPIO模拟I2C,就得自己在代码里卡好时间。
2.4 字节格式与数据传输
I2C的数据是以字节为单位传输的,每个字节8位,高位在前(MSB first)。
一个完整的数据帧长这样:
起始条件 + 7位从机地址 + 读写位 + 应答位 + 数据字节 + 应答位 + ... + 停止条件
注意,地址和每个数据字节后面都跟着一个应答位。这是I2C协议的“确认机制”。
我画了一张图,把整个流程串起来:
2.5 应答机制:ACK与NACK
应答位是I2C协议里一个很巧妙的设计。每发送完一个字节,接收方必须回复一个应答位:
- ACK(0):接收方拉低SDA,表示“收到,请继续”。
- NACK(1):接收方释放SDA(保持高),表示“没准备好”或“别再发了”。
应答位在SCL的第9个时钟周期采样。发送方在第8个时钟周期结束后释放SDA,接收方在第9个时钟周期拉低SDA表示ACK。
我在项目里遇到过一种情况:从机地址发送后,一直收不到ACK。用示波器一看,地址对了,但从机就是没拉低SDA。后来发现是从机电源没焊好……嗯,硬件问题永远是最先该查的。
2.6 常见时序参数速查
不同速率模式下,时序参数不一样。我整理了一张表,方便你查:
| 参数 | 标准模式 (100kHz) | 快速模式 (400kHz) | 说明 |
|---|---|---|---|
| tSCL (时钟周期) | 10 μs | 2.5 μs | SCL高低电平各占一半 |
| tHD;STA (起始保持时间) | 4.0 μs | 0.6 μs | 起始条件后SCL拉低前的等待 |
| tSU;STA (起始建立时间) | 4.7 μs | 0.6 μs | SCL高电平到SDA下降沿 |
| tSU;STO (停止建立时间) | 4.0 μs | 0.6 μs | 停止条件前SCL高电平保持 |
| tBUF (总线空闲时间) | 4.7 μs | 1.3 μs | 停止到下一次起始的最小间隔 |
这些参数在STM32的硬件I2C外设里,配置时钟寄存器时会自动计算。但如果你用GPIO模拟,就得自己在代码里加延时。我一般用示波器实测,调整延时直到波形符合规范。
2.7 避坑指南:我踩过的几个雷
最后分享几个实战中容易出问题的地方:
- 总线被拉死:如果从机异常,可能一直拉着SDA不放。主机发起始条件时发现SDA拉不下来,通信就卡死了。解决办法:在初始化时先发9个时钟脉冲,让从机复位。
- 应答位时序不对:有些从机对应答位的采样窗口很严格。如果SCL频率偏高,从机可能来不及拉低SDA。我建议先跑100kHz标准模式,调通了再升速。
- 停止条件后总线没释放:主机发完停止条件后,要确保SDA和SCL都被释放为高。如果代码里忘了配置GPIO模式,总线可能一直处于低电平。
嗯,这一章的内容就到这。I2C的时序其实不复杂,但细节很多。你把这些基础打牢了,后面写寄存器代码、抓波形分析,都会顺手很多。