2. I2C协议时序:起始条件、停止条件、数据有效性、字节传输格式、应答机制(ACK/NACK)
I2C协议,说白了就是一套“谁说话、谁听话、什么时候说、什么时候停”的规矩。很多刚入行的工程师觉得I2C简单,不就是两根线嘛。但我在项目里见过太多因为时序踩坑的案例——从设备不响应到数据错位,根子往往就出在这些基础时序上。
咱们今天就把I2C协议的五个核心要素掰开揉碎讲清楚。你把这些搞明白了,调试时至少能少走一半弯路。
2.1 起始条件(S)与停止条件(P)
I2C总线的空闲状态是:SCL高电平,SDA高电平。所有操作都从起始条件开始,以停止条件结束。
| 条件 | 时序定义 | 谁产生 |
|---|---|---|
| 起始条件(S) | SCL高电平时,SDA从高电平切换到低电平 | 主机(Master) |
| 停止条件(P) | SCL高电平时,SDA从低电平切换到高电平 | 主机(Master) |
| 重复起始条件(Sr) | 在未产生停止条件前,再次产生起始条件 | 主机(Master) |
关键点:起始和停止条件都是唯一的。SDA的跳变必须发生在SCL高电平期间,否则会被误判为数据位。我曾经调试一个温湿度传感器,死活读不到数据,用逻辑分析仪一看——主机在SCL低电平时就拉低了SDA,从机根本不认这个“起始条件”。
重复起始条件(Sr)是个好东西。比如你要读一个寄存器,先写寄存器地址,再读数据,中间不需要释放总线。用Sr代替“停止→再起始”,效率更高,也避免其他设备趁机抢占总线。
我的习惯:在写驱动时,我会把起始条件和停止条件封装成两个独立的函数。这样代码清晰,调试时也方便在关键位置加打印或逻辑分析仪触发。
2.2 数据有效性
I2C的数据采样规则很简单,但很多人会搞反:
- SCL低电平期间:SDA允许变化(数据准备阶段)
- SCL高电平期间:SDA必须稳定(数据采样阶段)
为什么这样设计?你想想看,如果SCL高电平时SDA还在跳变,从机就不知道你传的到底是0还是1。这个规则保证了数据传输的确定性。
避坑指南:我曾经遇到一个国产加速度传感器,它的数据手册里时序图画错了——SCL高电平时SDA还在变化。我按照手册写驱动,死活读不对。后来用示波器实测才发现,手册的保持时间(tHD:DAT)标反了。所以,永远不要100%相信数据手册的时序图,用示波器或逻辑分析仪实测才是王道。
2.3 字节传输格式
I2C每次传输一个字节(8位),顺序是MSB first(高位在前)。
一个完整的字节传输流程:
- 主机在SCL低电平时逐位设置SDA(从高位到低位)
- 每设置一位,主机产生一个SCL脉冲(低→高→低)
- 从机在SCL高电平时采样SDA
- 重复8次,完成一个字节传输
- 第9个SCL脉冲用于应答位
// 伪代码:主机发送一个字节
void i2c_send_byte(uint8_t data) {
for (int i = 7; i >= 0; i--) { // MSB first
if (data & (1 << i))
SDA_HIGH();
else
SDA_LOW();
SCL_HIGH(); // 从机采样
delay_us(4); // 保持时间
SCL_LOW(); // 准备下一位
delay_us(4);
}
// 第9位:释放SDA,等待应答
SDA_INPUT();
SCL_HIGH();
// 读取SDA电平判断ACK/NACK
ack = SDA_READ();
SCL_LOW();
}
注意:标准模式(100kHz)下,SCL高电平和低电平的保持时间通常需要4~5μs。快速模式(400kHz)下缩短到1μs左右。别把时序跑得太极限,留点余量——我一般按数据手册典型值的1.5倍来设延时。
2.4 应答机制(ACK/NACK)
这是I2C协议里最巧妙的设计。每传输完一个字节,接收方必须给出回应:
- ACK(应答):接收方在第9个SCL脉冲期间将SDA拉低,表示“收到,请继续”
- NACK(非应答):接收方在第9个SCL脉冲期间保持SDA高电平,表示“收完了/出错了/别发了”
| 场景 | 谁发ACK/NACK | 含义 |
|---|---|---|
| 主机发送地址+方向位 | 从机 | 从机存在且准备好通信 → ACK;否则NACK |
| 主机发送数据到从机 | 从机 | 从机成功接收 → ACK;接收失败或缓冲区满 → NACK |
| 主机从从机读取数据 | 主机 | 主机还想继续读 → ACK;读完最后一个字节 → NACK |
我的经验:调试I2C时,第一个要看的就是地址阶段有没有ACK。如果没有,90%的情况是地址写错了(比如7位地址忘了左移一位),或者从机地址线配置不对。我曾经花了一下午查一个气压计,最后发现是芯片的ADDR引脚电平没焊好。
主机读数据时的ACK/NACK逻辑很多人会搞混。记住一句话:主机想继续读,就发ACK;主机想结束读取,就发NACK。发完NACK后,主机再产生停止条件,从机就知道数据传输结束了。
2.5 完整传输序列示例
把上面这些串起来,一个典型的I2C写操作时序是这样的:
// 向从机地址0x48的寄存器0x05写入0xA3
[S] + [0x48<<1 | 0] + [ACK] + [0x05] + [ACK] + [0xA3] + [ACK] + [P]
// 分解:
// [S] : 起始条件
// [0x48<<1 | 0]: 7位地址0x48左移1位,最低位0表示写
// [ACK] : 从机回应
// [0x05] : 寄存器地址
// [ACK] : 从机回应
// [0xA3] : 要写入的数据
// [ACK] : 从机回应
// [P] : 停止条件
读操作稍微复杂一点,需要先写后读:
// 从从机地址0x48的寄存器0x05读取一个字节
[S] + [0x48<<1 | 0] + [ACK] + [0x05] + [ACK] +
[Sr] + [0x48<<1 | 1] + [ACK] + [data] + [NACK] + [P]
// 注意中间的[Sr]:重复起始条件,不释放总线
// 最后的[NACK]:主机告诉从机“我只读一个字节,够了”
常见坑:有些从机在写寄存器地址后,需要一小段“处理时间”才能响应读操作。如果你紧接着发Sr,从机可能还没准备好,返回的数据全是0xFF。我遇到过一颗触摸芯片就是这样,必须在写地址后加一个微秒级的延时。数据手册里不会写这个,全靠实测发现。
2.6 知识结构图
下面这张图把I2C协议时序的核心要素串起来了,建议你保存下来,调试时对照着看:
2.7 调试速查表
最后给你一张速查表,调试时对照着排查问题:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 起始条件后无任何响应 | SDA被拉死、从机地址错误、总线电容过大 | 用示波器看SDA/SCL波形,检查地址位 |
| 地址阶段收到NACK | 从机地址错误、从机未上电、地址线配置不对 | 核对数据手册地址,检查硬件连接 |
| 数据阶段收到NACK | 寄存器地址不存在、从机忙、写保护 | 确认寄存器地址范围,检查从机状态寄存器 |
| 读到的数据全是0xFF | 从机未准备好、时钟延展未处理、上拉电阻失效 | 加延时等待,检查SCL是否被从机拉低 |
| 总线死锁(SDA一直低) | 从机未释放SDA、时钟延展超时、噪声干扰 | 发9个SCL脉冲复位从机,检查上拉电阻 |
最后说一句:I2C的时序看起来简单,但实际调试时坑不少。我的建议是——逻辑分析仪是你的好朋友。花几百块买个带I2C解码的,能帮你省下大把抓头发的时间。别问我怎么知道的。