3. I2C协议时序:起始条件、停止条件、数据有效性、字节传输与应答机制
I2C(Inter-Integrated Circuit)总线协议由Philips公司(现NXP)开发,是一种同步、多主从、串行通信总线。它仅需两根线——串行数据线(SDA)和串行时钟线(SCL)——即可实现设备间的通信。理解其核心时序是使用GPIO模拟I2C驱动的基础。本章将详细拆解I2C协议的五个关键要素:起始条件、停止条件、数据有效性、字节传输与应答机制。
核心要点: I2C协议的所有时序都建立在SCL时钟信号的控制之上。SDA线上的数据变化必须在SCL低电平时进行,而起始和停止条件则是在SCL高电平时对SDA的特定操作。
3.1 起始条件(START Condition)
起始条件标志着一次I2C通信的开始。在总线空闲状态(SDA和SCL均为高电平)下,主设备通过将SDA线从高电平拉低,同时保持SCL为高电平,来产生起始条件。
- 时序要求: SCL为高电平时,SDA发生高→低的跳变。
- 作用: 唤醒所有从设备,并宣告总线进入忙碌状态。
- 重复起始条件(Repeated START): 在一次传输过程中,不发送停止条件,而是直接再次发送起始条件,用于改变数据传输方向(如从写模式切换到读模式)。
模拟实现提示: 在GPIO模拟中,起始条件的实现顺序为:先将SDA和SCL都置为高电平(释放总线),然后拉低SDA,最后拉低SCL准备发送时钟。必须确保在SCL高电平时完成SDA的下降沿。
3.2 停止条件(STOP Condition)
停止条件标志着一次I2C通信的结束。主设备通过将SDA线从低电平释放为高电平,同时保持SCL为高电平,来产生停止条件。
- 时序要求: SCL为高电平时,SDA发生低→高的跳变。
- 作用: 释放总线,使总线恢复到空闲状态,允许其他主设备发起通信。
- 与起始条件的区别: 起始条件是SDA下降沿,停止条件是SDA上升沿,两者都发生在SCL高电平期间。
常见错误: 在模拟停止条件时,如果先拉高SCL再拉高SDA,可能会被从设备误判为数据位。正确的顺序是:先拉低SCL,释放SDA(拉高),然后拉高SCL,最后再拉高SDA(如果SDA之前是低电平)。但标准停止条件要求SCL高时SDA上升,因此模拟时需确保在SCL高电平期间完成SDA的上升沿。
3.3 数据有效性(Data Validity)
I2C协议规定,SDA线上的数据必须在SCL时钟的高电平期间保持稳定。数据的变化(即SDA的上升或下降)只能在SCL为低电平时进行。
| SCL状态 | SDA允许的操作 | 说明 |
|---|---|---|
| 高电平 | 保持稳定(不变) | 数据被从设备采样读取 |
| 低电平 | 允许变化(上升/下降) | 主设备准备下一个数据位 |
| 高电平(特殊) | 起始条件(下降沿)或停止条件(上升沿) | 仅用于起始和停止,不用于数据位 |
模拟实现关键: 在GPIO模拟中,设置SDA数据后,必须等待一小段延时(t_SU;DAT),然后再拉高SCL。SCL高电平期间,SDA必须保持不变。读取数据时,应在SCL高电平的中间时刻采样SDA电平。
3.4 字节传输与帧格式
I2C协议以字节(8位)为单位进行数据传输,每次传输后紧跟一个应答位(ACK/NACK)。标准的I2C帧格式如下:
- 起始条件(S): 主设备发送起始条件。
- 从设备地址(7位或10位): 主设备发送要通信的从设备地址。7位地址模式最为常见。
- 读/写位(R/W): 第8位,0表示写(主设备发送数据),1表示读(主设备接收数据)。
- 应答位(ACK): 从设备在第9个时钟周期拉低SDA,表示确认收到地址。
- 数据字节(8位): 主设备(写模式)或从设备(读模式)发送数据,MSB(最高位)优先。
- 应答位(ACK/NACK): 接收方在第9个时钟周期给出应答。
- 停止条件(P): 主设备发送停止条件结束通信。
| 帧段 | 位数 | 发送方 | 说明 |
|---|---|---|---|
| S | 1 | 主设备 | 起始条件 |
| Slave Addr | 7 | 主设备 | 从设备地址(如0x3C) |
| R/W | 1 | 主设备 | 0=写,1=读 |
| ACK | 1 | 从设备 | 应答位(低电平有效) |
| Data Byte | 8 | 主/从设备 | 数据字节,MSB先发 |
| ACK/NACK | 1 | 接收方 | 应答或非应答 |
| P | 1 | 主设备 | 停止条件 |
模拟实现提示: 在GPIO模拟中,发送一个字节需要循环8次,每次设置SDA(根据当前位),然后产生一个SCL脉冲(拉高→延时→拉低)。发送完8位后,释放SDA(设置为输入或拉高),并产生第9个SCL脉冲来读取从设备的应答位。
3.5 应答机制(ACK/NACK)
应答机制是I2C协议可靠性的核心。每传输一个字节(8位)后,接收方必须在第9个时钟周期内给出应答信号。
- ACK(应答,低电平): 接收方在第9个SCL脉冲期间将SDA拉低,表示“已收到,请继续发送”。
- NACK(非应答,高电平): 接收方在第9个SCL脉冲期间保持SDA为高电平,表示“未准备好/接收错误/传输结束”。
应答机制的作用:
- 地址应答: 从设备确认自己的地址被正确识别。如果总线上没有设备响应,主设备会收到NACK,此时应发送停止条件终止通信。
- 数据应答: 写操作时,从设备每收到一个字节都需应答ACK;读操作时,主设备在收到最后一个字节后应发送NACK,通知从设备停止发送。
3.6 完整通信流程示例
以下是一个完整的I2C写操作流程示例(主设备向地址为0x3C的从设备写入一个字节0xA5):
// 伪代码:GPIO模拟I2C写一个字节
void i2c_write_byte(uint8_t slave_addr, uint8_t data) {
// 1. 发送起始条件
SDA_HIGH(); SCL_HIGH(); delay();
SDA_LOW(); delay(); // SCL高时SDA下降
SCL_LOW(); delay();
// 2. 发送从设备地址 + 写位 (0x3C << 1 | 0 = 0x78)
for (int i = 7; i >= 0; i--) {
if (slave_addr & (1 << i)) SDA_HIGH();
else SDA_LOW();
delay();
SCL_HIGH(); delay();
SCL_LOW(); delay();
}
// 3. 发送R/W位 (0 = 写)
SDA_LOW(); delay();
SCL_HIGH(); delay();
SCL_LOW(); delay();
// 4. 等待从设备应答 (ACK)
SDA_INPUT(); // 释放SDA,设置为输入
SCL_HIGH(); delay();
if (SDA_READ() == HIGH) { // NACK
// 错误处理:从设备无应答
i2c_stop();
return;
}
SCL_LOW(); delay();
// 5. 发送数据字节 (0xA5)
for (int i = 7; i >= 0; i--) {
if (data & (1 << i)) SDA_HIGH();
else SDA_LOW();
delay();
SCL_HIGH(); delay();
SCL_LOW(); delay();
}
// 6. 等待从设备应答 (ACK)
SDA_INPUT();
SCL_HIGH(); delay();
if (SDA_READ() == HIGH) { // NACK
// 错误处理
}
SCL_LOW(); delay();
// 7. 发送停止条件
SDA_LOW(); delay();
SCL_HIGH(); delay();
SDA_HIGH(); delay(); // SCL高时SDA上升
}
时序关键点:
- 延时(delay) 必须根据I2C标准速率(标准模式100kHz,快速模式400kHz)计算,GPIO模拟时通常使用微秒级延时。
- SDA_INPUT() 在等待应答时至关重要,必须将SDA引脚切换为输入模式,否则主设备会强制拉低SDA,导致无法检测从设备的应答信号。
- 起始条件后的SCL低电平 必须确保在发送第一个数据位之前,SCL已经被拉低,否则数据位变化可能被误判为起始条件。
3.7 时序参数对比
| 参数 | 符号 | 标准模式 (100kHz) | 快速模式 (400kHz) | 说明 |
|---|---|---|---|---|
| SCL时钟频率 | f_SCL | 100 kHz | 400 kHz | 主设备产生 |
| SCL低电平时间 | t_LOW | ≥ 4.7 μs | ≥ 1.3 μs | SCL保持低电平 |
| SCL高电平时间 | t_HIGH | ≥ 4.0 μs | ≥ 0.6 μs | SCL保持高电平 |
| 起始条件保持时间 | t_HD;STA | ≥ 4.0 μs | ≥ 0.6 μs | 起始条件后SCL高电平 |
| 数据建立时间 | t_SU;DAT | ≥ 250 ns | ≥ 100 ns | SDA变化后到SCL上升 |
| 数据保持时间 | t_HD;DAT | ≥ 0 ns | ≥ 0 ns | SCL下降后SDA保持 |
| 停止条件建立时间 | t_SU;STO | ≥ 4.0 μs | ≥ 0.6 μs | 停止条件前SCL高电平 |
模拟实现建议: 在GPIO模拟I2C驱动中,建议以标准模式(100kHz)为起点进行实现,延时参数可适当放宽(如t_LOW=5μs,t_HIGH=5μs),以确保兼容性。待驱动稳定后,再根据硬件性能优化延时,提升至快速模式。
通过以上对I2C协议时序的详细拆解,我们为后续使用GPIO模拟实现I2C主设备驱动奠定了坚实的理论基础。起始条件、停止条件、数据有效性、字节传输和应答机制这五个要素,将在实际的代码实现中被反复调用,其时序的准确性直接决定了通信的成败。