2、I2C协议详解:起始条件、停止条件、数据有效性、应答机制
各位同学,今天我们来啃I2C协议里最核心的四个概念。说实话,很多新手调不通I2C,往往就是这四个地方没吃透。我自己刚入行那会儿,也在这上面栽过跟头——有一次调试传感器,数据死活读不对,折腾了两天,最后发现是应答信号时序差了半个时钟周期。
好,咱们一个一个来拆解。
2.1 起始条件(S)与停止条件(P)
I2C总线在空闲时,SCL和SDA都是高电平。这是规矩,谁都不能违反。
起始条件:当SCL为高电平时,SDA从高电平跳变到低电平。这个下降沿告诉所有从机:「注意,主机要开始通信了!」
停止条件:当SCL为高电平时,SDA从低电平跳变到高电平。这个上升沿告诉所有从机:「本次传输结束,释放总线吧。」
我个人的习惯是,在写代码时把这两个条件封装成宏或内联函数。为什么?因为时序要求非常严格——SDA的跳变必须在SCL高电平期间完成,而且建立时间和保持时间都有明确要求。
我曾经在项目里见过一个坑:有人用GPIO模拟I2C时,在发送起始条件前忘了先把SCL拉高,结果从机根本没识别到起始信号。嗯,这种低级错误,调试起来真的很浪费时间。
2.2 数据有效性
I2C的数据传输有个铁律:数据在SCL高电平时必须稳定,只有在SCL低电平时才能变化。
你想想看,如果数据在SCL高电平时乱跳,接收方怎么判断你传的是0还是1?所以协议规定得死死的——SDA上的数据,只能在SCL为低电平时改变。
具体来说:
- SCL高电平期间:SDA保持稳定,此时读取数据
- SCL低电平期间:SDA可以变化,发送方准备下一位数据
每个字节传输8位数据,高位在前(MSB first)。我刚开始学的时候总记反,后来在代码里加了个注释才改过来。
2.3 应答机制(ACK/NACK)
这是I2C协议里最巧妙的设计之一。每传输完一个字节(8位),接收方必须在第9个时钟周期给出应答信号。
应答(ACK):接收方在第9个时钟周期将SDA拉低,表示「收到,请继续」。
非应答(NACK):接收方在第9个时钟周期保持SDA高电平,表示「没收到」或「不想收了」。
为什么需要应答?说白了,就是让发送方知道数据有没有成功到达。我在调试一个气压传感器时,发现读出来的数据全是0xFF,后来用逻辑分析仪一看,从机一直在发NACK——原来是地址写错了。
| 场景 | 应答信号 | 含义 |
|---|---|---|
| 主机发送地址后 | ACK | 从机存在且准备好通信 |
| 主机发送地址后 | NACK | 从机不存在或忙 |
| 主机发送数据后 | ACK | 从机成功接收 |
| 主机接收数据后 | ACK | 主机还要继续读 |
| 主机接收数据后 | NACK | 主机不再读了(停止信号前) |
2.4 完整的数据传输流程
把上面四个概念串起来,一次完整的I2C传输是这样的:
- 主机发送起始条件(S)
- 主机发送7位从机地址 + 1位读写位(共8位)
- 从机在第9个时钟回复ACK
- 主机发送/接收数据字节(每字节后跟ACK/NACK)
- 主机发送停止条件(P)
我画了一张流程图,把整个逻辑串起来,你一看就明白了:
2.5 避坑指南
最后分享几个我实际项目中踩过的坑:
- 起始条件重复发送:有些从机在收到起始条件后需要一定时间准备,连续快速发送起始条件可能导致从机漏掉。我建议在起始条件后加一个微秒级的延时。
- 应答超时处理:不要死等从机回复ACK。我曾经在一个项目里没加超时判断,从机挂了之后总线就卡死了。加个超时机制,比如等待500μs没收到ACK就报错重试。
- 停止条件后的释放时间:停止条件发出后,主机应该释放SDA和SCL(设置为输入或开漏输出高电平),给从机留出处理时间。我一般会加5μs的延时再开始下一次通信。
好了,I2C协议的这四个核心机制就讲到这里。记住:起始和停止条件是骨架,数据有效性是血肉,应答机制是灵魂。把这三点吃透了,I2C通信对你来说就是小菜一碟。