2. I2C 时序基础:起始条件、停止条件、数据有效性、字节传输格式、ACK/NACK 机制
I2C 总线上的所有通信行为都建立在严格的时序协议之上。理解起始条件、停止条件、数据有效性、字节传输格式以及应答机制,是后续配置寄存器实现从机地址匹配与应答时序的基石。本章将逐一拆解这些核心时序单元,并辅以图形化说明。
2.1 起始条件 (START Condition) 与停止条件 (STOP Condition)
SCL 为高电平时,SDA 上的特定跳变定义了总线的开始与结束。这两个条件是总线空闲与通信状态的切换标志。
| 条件 | SCL 状态 | SDA 跳变方向 | 功能说明 |
|---|---|---|---|
| 起始条件 (S) | 高电平 | 从高电平 → 低电平 | 通知所有从机:总线被主机占用,准备开始传输 |
| 停止条件 (P) | 高电平 | 从低电平 → 高电平 | 通知所有从机:主机释放总线,通信结束 |
关键时序约束:起始条件和停止条件必须在 SCL 为高电平时产生。在 SCL 低电平时,SDA 的变化被视为数据位传输,而非起始或停止信号。
2.2 数据有效性 (Data Validity)
I2C 协议规定,数据位的采样发生在 SCL 的上升沿。为了保证数据被正确采样,SDA 上的电平必须在 SCL 低电平期间完成切换,并在 SCL 高电平期间保持稳定。
- 数据变化:仅在 SCL 为低电平时,SDA 允许改变电平。
- 数据采样:在 SCL 的上升沿,接收方锁存 SDA 上的当前电平。
- 数据保持:在 SCL 高电平期间,SDA 必须保持稳定,不得跳变。
硬件设计启示:MCU 的 I2C 外设在配置 GPIO 时,必须将 SDA 和 SCL 设置为开漏输出(Open-Drain),并外接上拉电阻。开漏结构允许总线被多个设备拉低,且能实现线与逻辑,这是数据有效性和仲裁的基础。
2.3 字节传输格式 (Byte Transfer Format)
I2C 总线以字节为单位进行数据传输,每个字节固定为 8 位,且高位(MSB)在前。每传输完一个字节后,接收方必须回复一个应答位(ACK/NACK)。
单字节传输序列:
- 主机产生起始条件 (S)。
- 主机发送 8 位数据(高位在前)。
- 主机释放 SDA(置为高阻),等待从机应答。
- 从机在第 9 个 SCL 周期拉低 SDA(ACK)或保持高电平(NACK)。
- 主机产生停止条件 (P) 或重复起始条件 (Sr)。
| SCL 周期 | 1 - 8 | 9 |
|---|---|---|
| 数据线 (SDA) | 主机驱动的 8 位数据 (D7-D0) | 从机驱动的应答位 (ACK/NACK) |
| 时钟线 (SCL) | 主机产生 8 个脉冲 | 主机产生第 9 个脉冲 |
| 数据方向 | 主机 → 从机 | 从机 → 主机 |
2.4 ACK/NACK 机制详解
应答机制是 I2C 协议保证通信可靠性的核心。每传输完一个字节,接收方必须给出应答信号。主机通过检测应答位来判断下一步操作。
2.4.1 应答信号定义
- ACK (Acknowledge):在第 9 个 SCL 周期,接收方将 SDA 拉低,表示“已成功接收字节,请继续发送”。
- NACK (Not Acknowledge):在第 9 个 SCL 周期,接收方保持 SDA 为高电平,表示“接收失败”或“传输结束”。
2.4.2 常见 NACK 场景
| 场景 | 发送方 | 接收方 | 结果 |
|---|---|---|---|
| 从机地址不匹配 | 主机 | 所有从机 | 无从机拉低 SDA,主机收到 NACK,停止通信 |
| 从机忙或未就绪 | 主机 | 目标从机 | 从机无法处理数据,回复 NACK |
| 主机读操作结束 | 从机 | 主机 | 主机在最后一个字节回复 NACK,通知从机停止发送 |
| 非法命令或数据 | 主机 | 目标从机 | 从机不支持该命令,回复 NACK 拒绝 |
驱动开发注意:在配置 MCU 的 I2C 从机模式时,必须正确设置从机地址寄存器(如 STM32 的 OAR1/OAR2)。当地址匹配后,硬件自动产生 ACK。如果软件未及时读取接收数据寄存器(DR),硬件可能因 RXNE 溢出而自动回复 NACK,导致通信失败。务必在中断服务函数中快速处理数据。
2.5 完整帧结构示例
一个典型的 I2C 写操作帧包含:起始条件、7 位从机地址 + 读写位(R/W)、从机 ACK、8 位数据、从机 ACK、停止条件。下图展示了主机向地址为 0x50 的从机写入一个字节 0xA5 的完整时序。
// 伪代码:主机发送一字节数据到从机
// 1. 产生起始条件
I2C_GenerateSTART(ENABLE);
// 2. 等待起始条件完成
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
// 3. 发送从机地址 + 写位 (0x50 << 1 | 0 = 0xA0)
I2C_Send7bitAddress(0x50, I2C_Direction_Transmitter);
// 4. 等待地址发送完成并收到 ACK
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 5. 发送数据 0xA5
I2C_SendData(0xA5);
// 6. 等待数据发送完成并收到 ACK
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 7. 产生停止条件
I2C_GenerateSTOP(ENABLE);
时序总结:起始条件 → 地址+R/W (8位) → ACK → 数据 (8位) → ACK → ... → 停止条件。每个字节后紧跟一个应答位,这是 I2C 协议最基础的时序单元。理解这个节奏,是后续配置寄存器实现从机应答的基础。