3、I2C协议详解:起始条件、停止条件、数据有效性、应答机制(ACK/NACK)
好,咱们进入正题。上一章我们聊了I2C总线的基本概念,说白了就是两根线(SCL和SDA)怎么把一堆设备串起来。但光有物理连接还不够,你得知道什么时候开始说话、什么时候闭嘴、数据怎么才算有效、对方听懂了没有。这就是本章要讲的四个核心机制。
我个人习惯把这四个东西称为I2C的“交通规则”。你想想看,没有红绿灯和交警,路上再好的车也得堵成一锅粥。I2C也一样,没有这些规则,主设备和从设备根本没法正常沟通。
3.1 起始条件(Start Condition)
起始条件,就是主设备告诉总线上所有从设备:“注意了,我要开始发消息了!”
怎么实现的呢?很简单:SCL为高电平时,SDA从高电平跳变到低电平。
你可能会问:“为什么非得在SCL高的时候跳变?”嗯,这里有个关键点:I2C协议规定,数据在SCL高电平时必须稳定,只有SCL低电平时SDA才能变化。起始条件和停止条件是两个例外——它们故意在SCL高时改变SDA,就是为了让所有设备识别出“这不是数据,是控制信号”。
起始条件时序要点:
- SCL保持高电平
- SDA从高电平切换到低电平
- 切换完成后,SCL变为低电平,开始传输数据
我在项目中遇到过一个问题:有个同事写的代码,起始条件之后紧接着就发数据,中间没给SCL足够的低电平时间。结果从设备死活不响应。后来查手册才发现,起始条件之后SCL必须保持至少4.7μs的低电平(标准模式100kHz下)。这个坑我踩过,你们别踩。
3.2 停止条件(Stop Condition)
有开始就有结束。停止条件就是主设备告诉总线:“我说完了,你们可以歇着了。”
停止条件的时序正好和起始条件相反:SCL为高电平时,SDA从低电平跳变到高电平。
停止条件发出后,总线就进入空闲状态。这时候SDA和SCL都是高电平,其他主设备才能发起新的传输。
小技巧:我写驱动时习惯在每次传输结束后都检查一下总线状态。如果SDA和SCL不是同时为高,说明总线可能被某个设备锁住了。这时候可以发一个伪起始条件(先发停止再发起始)来复位总线。我曾经用这招救回过一次现场调试。
3.3 数据有效性(Data Validity)
这个规则其实就一句话:SCL高电平时,SDA上的数据必须稳定;SCL低电平时,SDA才能变化。
为什么这么设计?说白了就是为了抗干扰。你想想看,如果数据在SCL高的时候乱跳,接收方根本不知道哪个才是有效数据。所以协议规定:接收方只在SCL的上升沿或下降沿采样SDA,其他时间SDA随便变,反正我不看。
具体来说:
- SCL高电平期间:SDA上的电平就是当前位的值(1或0)
- SCL低电平期间:发送方可以改变SDA,准备下一位数据
- 数据从最高位(MSB)开始传输,先发高位,再发低位
数据有效性总结:
| SCL状态 | SDA行为 | 说明 |
|---|---|---|
| 高电平 | 必须稳定 | 接收方在此阶段采样数据 |
| 低电平 | 允许变化 | 发送方准备下一位数据 |
我记得有一次调试一个制氧机的流量传感器,发现读出来的数据总是错位。查了半天,原来是主设备在SCL低电平时采样了——这相当于在别人换挡的时候看仪表盘,能准才怪。
3.4 应答机制(ACK/NACK)
应答机制,是I2C协议里最人性化的设计。它让接收方有机会说:“我收到了”或者“我没收到”。
具体流程是这样的:
- 发送方每发完8位数据(一个字节),就释放SDA线(设置为输入)
- 发送方在第9个SCL时钟周期拉高SCL
- 接收方如果收到数据,就把SDA拉低(ACK);如果没收到或不想收,就保持SDA高电平(NACK)
注意:ACK是低电平有效,NACK是高电平有效。这个和很多人的直觉相反——你可能会觉得“应答”应该是高电平。但I2C就是这么设计的,习惯就好。
应答机制有几个关键点:
- 地址字节后的ACK:从设备收到自己的地址后,必须发ACK。如果没发,说明总线上没有这个设备,或者设备挂了。
- 数据字节后的ACK:接收方每收到一个字节都要应答。如果接收方发NACK,发送方就知道该停止了。
- 读操作时的NACK:主设备在读最后一个字节时,会主动发NACK,告诉从设备“不用再发了”。
避坑指南:我曾经在写驱动时,忘记在读取最后一个字节后发NACK。结果从设备以为我还要继续读,一直往总线上发数据,把后续的通信全搞乱了。后来我养成了一个习惯:读操作结束后,一定检查是否发了NACK和停止条件。
说到应答,还有一个容易忽略的点:时钟拉伸(Clock Stretching)。有些从设备处理速度慢,会在应答位把SCL拉低,让主设备等一等。主设备如果支持时钟拉伸,就得乖乖等着,直到从设备释放SCL。我在用某款国产流量传感器时就遇到过这个问题,主设备不支持时钟拉伸,结果通信老是超时。最后换了个支持时钟拉伸的MCU才搞定。
3.5 完整传输示例
咱们把上面四个机制串起来,看一个完整的写操作流程:
1. 主设备发送起始条件(SCL高,SDA从高到低)
2. 主设备发送7位从设备地址 + 1位读写位(0表示写)
3. 从设备在第9个SCL时钟发ACK(拉低SDA)
4. 主设备发送第一个数据字节(8位)
5. 从设备发ACK
6. 主设备发送第二个数据字节
7. 从设备发ACK
8. ...(重复直到数据发完)
9. 主设备发送停止条件(SCL高,SDA从低到高)
读操作类似,只是读写位变成1,而且最后主设备要发NACK:
1. 起始条件
2. 发送地址 + 读位(1)
3. 从设备发ACK
4. 从设备发送数据字节
5. 主设备发ACK(表示还要继续读)
6. 从设备发送下一个数据字节
7. 主设备发ACK
8. ...(重复直到读完)
9. 主设备发NACK(表示不再读了)
10. 停止条件
嗯,到这里,I2C协议的四个核心机制就讲完了。你可能会觉得这些规则有点繁琐,但相信我,一旦你理解了它们的设计初衷——让通信可靠、让设备之间能互相理解——你就会觉得这些规则其实很优雅。
下一章,我们会把这些规则落实到代码里,手写一个I2C驱动。到时候你会发现,理解了这些底层机制,写驱动就像搭积木一样简单。