2. I2C协议时序基础:起始条件、停止条件、数据有效性、字节格式与ACK/NACK机制
各位同学,咱们今天聊I2C协议最核心的东西——时序。说白了,就是总线上那些高低电平怎么跳变,主设备和从设备怎么“对话”。我刚开始搞驱动时,总觉得时序图看着头疼,后来发现只要抓住几个关键点,这东西其实挺直观的。
2.1 起始条件与停止条件
I2C总线在空闲时,SCL和SDA都是高电平。这是“静默”状态。任何通信,都得先打破这个静默。
起始条件(S): SCL为高电平时,SDA从高电平切换到低电平。
停止条件(P): SCL为高电平时,SDA从低电平切换到高电平。
你想想看,这两个条件都是SCL在高电平时发生的。为什么?因为数据采样也是在SCL高电平时,所以起始和停止必须“插空”在SCL高电平期间改变SDA,才能被所有设备识别到。
我在项目中遇到过一个问题:某个从设备偶尔不响应,抓波形发现是主设备在发送停止条件时,SDA上升沿太慢,导致从设备没识别到。后来调整了上拉电阻的阻值,问题就解决了。嗯,这里要注意,上拉电阻不是随便选的,它直接影响边沿斜率。
| 条件 | SCL状态 | SDA变化 | 说明 |
|---|---|---|---|
| 起始(S) | 高电平 | 高 → 低 | 通信开始,总线被占用 |
| 停止(P) | 高电平 | 低 → 高 | 通信结束,总线释放 |
| 重复起始(Sr) | 高电平 | 高 → 低 | 不停止直接开始新传输 |
个人习惯: 我写驱动时,会把起始和停止条件封装成两个内联函数。这样代码读起来清晰,而且方便调试时加断点。
2.2 数据有效性
I2C协议规定:SCL为高电平时,SDA必须保持稳定;SCL为低电平时,SDA允许变化。
这个规则很关键。你想想,如果SCL高电平时SDA乱跳,从设备就会误判数据。所以主设备必须在SCL低电平时准备好下一个bit,然后在SCL高电平时让从设备采样。
我见过一些新手写的软件模拟I2C,在SCL高电平时去改变SDA,结果通信时好时坏。说白了,就是时序没卡对。
我曾经踩过的坑: 在STM32上用GPIO模拟I2C时,忘了加延时,SCL高电平持续时间太短,从设备来不及采样。后来查手册才发现,标准模式下SCL高电平最小时间是4微秒。别小看这几微秒,差一点都不行。
2.3 字节格式与数据传输
I2C传输数据时,是以字节为单位的。每个字节8位,高位在前(MSB first)。
传输流程是这样的:
- 主设备发送起始条件
- 主设备发送7位从设备地址 + 1位读写位(R/W)
- 从设备回复ACK
- 主设备发送/接收数据字节(每字节后跟ACK)
- 主设备发送停止条件
这里有个细节:地址是7位的,但实际发送时是8位——高7位是地址,最低位是读写方向。0表示写,1表示读。
| 位序 | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|---|
| 内容 | A6 | A5 | A4 | A3 | A2 | A1 | A0 | R/W |
| 示例 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
上面这个例子,地址是0x68(1101000),R/W=0表示写操作。所以发送的字节是0xD0。
2.4 ACK/NACK机制
这是I2C协议的“握手”环节。每传输完一个字节(8位),接收方必须在第9个SCL时钟周期内拉低SDA,表示“收到了”。这就是ACK。
如果接收方没拉低SDA(保持高电平),那就是NACK,表示“没收到”或“不想收”。
ACK/NACK规则:
- 主设备发送地址后,从设备必须回复ACK,否则通信终止
- 主设备发送数据后,从设备回复ACK表示可以继续发
- 主设备接收数据后,主设备回复ACK表示“我还要读”,回复NACK表示“够了,停止”
我建议你在调试时,重点看ACK位。很多通信问题都是卡在这里——地址不对、从设备没上电、总线被拉死,都会导致NACK。
调试技巧: 用逻辑分析仪抓波形时,先看起始条件对不对,再看地址和ACK。如果地址对了但没ACK,八成是从设备没响应。如果地址错了,查查你的地址是不是左移了一位。
2.5 完整时序流程(SVG图)
下面这张图把整个I2C单字节写操作的时序画出来了。你可以对照着看,每个时钟周期发生了什么。
2.6 常见时序问题与避坑
搞I2C驱动,说白了就是跟时序打交道。我总结几个常见问题:
- 起始条件不满足: SCL高电平时SDA没拉低,从设备不认。检查代码中GPIO操作的顺序。
- ACK丢失: 从设备没拉低SDA。可能原因:地址错误、从设备忙、总线被其他设备占用。
- 时钟拉伸: 从设备拉低SCL表示“我还没准备好”。主设备必须等待SCL变高才能继续。很多MCU的硬件I2C会自动处理,但软件模拟时容易忽略。
- 总线死锁: 从设备把SDA拉低后不释放。我遇到过这种情况,最后只能复位从设备。预防措施是在驱动中加入超时处理。
我曾经踩过的坑: 有一次调试温度传感器,读回来的数据总是0xFF。抓波形发现,主设备发送读命令后,从设备回复了ACK,但数据字节全是1。后来查手册才知道,这个传感器在转换期间不响应读请求,需要先写寄存器触发转换,等一段时间再读。嗯,不看手册就写驱动,迟早要还的。
2.7 小结
这一章的内容,是I2C协议的“语法”。起始条件、停止条件、数据有效性、字节格式、ACK/NACK,这五个概念构成了所有I2C通信的基础。你写驱动时,不管是用MCU的硬件外设还是GPIO模拟,最终都是在跟这些时序打交道。
我个人习惯是,拿到一个新器件,先看它的时序图,对照着协议标准过一遍。哪里是起始,哪里是ACK,数据什么时候采样,心里有数了再写代码。这样调试起来会省很多时间。