3、I2C总线协议:起始条件、停止条件、数据有效性、ACK/NACK机制
I2C总线,说白了就是两根线(SCL时钟线、SDA数据线)搞定一切通信。很多刚入行的工程师觉得它简单,不就是拉高拉低嘛。但我在项目中吃过不少亏,发现越是看似简单的协议,越容易在时序细节上翻车。今天咱们就把I2C最核心的四个机制掰开揉碎了讲清楚。
3.1 起始条件(Start Condition)
先说说起始条件。这是I2C通信的“发令枪”。
定义:当SCL为高电平时,SDA从高电平切换到低电平。
你想想看,为什么要在SCL高电平时切换?因为I2C规定,数据在SCL低电平时变化,高电平时采样。起始条件故意打破这个规则,就是为了让所有从机知道:注意,要开始通信了!
时序要求:
- SDA下降沿必须在SCL高电平期间完成
- 起始条件建立时间(tSU:STA):至少4.7μs(标准模式100kHz)
- 起始条件保持时间(tHD:STA):至少4.0μs
我个人习惯在代码里这样实现起始条件:
void i2c_start(void) {
SDA_HIGH(); // 先确保SDA为高
delay_us(5); // 留点余量
SCL_HIGH(); // SCL拉高
delay_us(5); // 等待SCL稳定
SDA_LOW(); // SDA拉低,产生起始条件
delay_us(5); // 保持时间
SCL_LOW(); // 拉低SCL,准备传输数据
}
我在项目中遇到过一个问题:某次用GPIO模拟I2C时,起始条件后紧接着发送地址,结果从机一直没响应。查了半天,发现是起始条件保持时间不够,从机还没反应过来我就开始发数据了。嗯,这里要注意,起始条件后的第一个SCL时钟一定要给足时间。
3.2 停止条件(Stop Condition)
有开始就有结束。停止条件就是通信的“休止符”。
定义:当SCL为高电平时,SDA从低电平切换到高电平。
和起始条件正好相反。停止条件告诉所有从机:总线释放了,你们可以继续监听下一次通信了。
小技巧:停止条件后,SDA和SCL都应该被释放为高电平(通过上拉电阻)。我习惯在停止条件后加一个短延时,确保总线完全释放,避免下一个起始条件被误判。
代码实现也很直观:
void i2c_stop(void) {
SDA_LOW(); // 先确保SDA为低
delay_us(5);
SCL_HIGH(); // SCL拉高
delay_us(5);
SDA_HIGH(); // SDA拉高,产生停止条件
delay_us(5); // 总线释放时间
}
我曾经犯过一个低级错误:在停止条件后立刻重新发送起始条件,中间没有留总线释放时间。结果从机认为还在上一次通信中,直接无视了新的起始条件。避坑指南:停止条件后至少等待4.7μs再发起新的起始条件。
3.3 数据有效性(Data Validity)
这是I2C协议里最容易忽略但又最关键的部分。
核心规则:
- SDA上的数据必须在SCL低电平时变化
- SDA上的数据必须在SCL高电平时被采样(保持稳定)
说白了,就是SCL高电平时SDA不能变。你想想看,如果SCL高电平时SDA变了,从机会把它当成起始条件或停止条件来处理,通信就乱套了。
注意:数据变化只允许在SCL低电平期间进行。SCL高电平期间,SDA必须保持稳定。这个规则对主从双方都适用。
我见过不少工程师在写I2C驱动时,为了追求速度,在SCL上升沿刚过就改变SDA。结果就是数据采样错误,通信时好时坏。我的建议是:在SCL低电平的中间段改变SDA,留足建立时间和保持时间。
数据有效性的时序参数:
| 参数 | 标准模式(100kHz) | 快速模式(400kHz) |
|---|---|---|
| 数据建立时间 tSU:DAT | ≥ 250ns | ≥ 100ns |
| 数据保持时间 tHD:DAT | ≥ 0ns(实际建议≥ 300ns) | ≥ 0ns(实际建议≥ 100ns) |
这里有个坑:数据保持时间理论上可以是0ns,但实际项目中我建议至少留300ns。为什么?因为从机的输入滤波器、信号传播延迟都会吃掉你的时序余量。我曾经在快速模式下只留了50ns保持时间,结果从机偶尔会采样到错误数据,排查了整整两天才找到原因。
3.4 ACK/NACK机制
ACK/NACK是I2C的“握手信号”,用来确认数据是否被正确接收。
工作机制:
- 每发送完一个字节(8位数据),发送方释放SDA
- 接收方在第9个SCL时钟周期拉低SDA表示ACK(确认)
- 接收方保持SDA为高表示NACK(不确认)
你可能会问:为什么要搞这么个机制?说白了,就是为了让发送方知道数据有没有被正确接收。如果没有ACK,发送方可以决定重发或者终止通信。
ACK/NACK的典型场景:
- 主机发送地址后:从机ACK表示地址匹配,NACK表示无此从机或从机忙
- 主机发送数据后:从机ACK表示数据已接收,NACK表示无法处理(如接收缓冲区满)
- 主机接收数据后:主机ACK表示还要继续接收,NACK表示够了,停止传输
代码实现ACK检测:
uint8_t i2c_wait_ack(void) {
uint8_t timeout = 100;
SDA_INPUT(); // 释放SDA,设置为输入
SCL_HIGH(); // 产生第9个时钟
delay_us(5);
while (SDA_READ() == 1) { // 等待SDA被拉低
if (--timeout == 0) {
SCL_LOW();
return 1; // 超时,返回NACK
}
}
SCL_LOW();
return 0; // 收到ACK
}
我在项目中遇到过一种情况:从机明明存在,但发送地址后总是收到NACK。后来用示波器一看,发现从机的ACK信号太短,只有几百纳秒,而我的采样点刚好错过了。嗯,这里要注意,ACK检测时SCL高电平的保持时间要足够长,确保能采样到从机的ACK信号。
避坑指南:我曾经在批量生产时发现,同一批次的从机芯片,有的ACK响应时间快,有的慢。后来在代码里加了ACK超时检测,超时后重试3次,问题就解决了。建议大家在写驱动时,永远不要假设从机一定会及时响应。
最后总结一下这四个机制的关系:起始条件开启通信,停止条件结束通信,数据有效性保证数据正确传输,ACK/NACK确保传输可靠。这四个机制环环相扣,任何一个出问题,整个通信都会失败。下次调试I2C时,不妨先用示波器抓一下这四个信号,往往能快速定位问题。