2. I2C时序详解:起始条件、停止条件、数据有效性、字节传输格式、ACK/NACK机制
各位同学,咱们今天来啃I2C时序这块硬骨头。说实话,我当年刚接触I2C时,总觉得时序图看着像天书——一堆方波、箭头、标记,完全不知道从哪下手。后来被项目逼着调了三天三夜的从机响应,才真正把这些波形和寄存器操作对上号。
I2C的时序其实不复杂,但细节特别多。你想想看,两根线(SCL和SDA)要完成地址发送、读写控制、数据收发、应答确认这么多事,全靠时序上的微妙变化来区分。咱们一个一个拆开讲。
2.1 起始条件与停止条件
这是I2C通信的“开门”和“关门”动作。没有起始条件,从机根本不知道总线要开始干活了;没有停止条件,从机不知道这轮通信结束了。
| 条件 | SCL状态 | SDA变化 | 谁发起 |
|---|---|---|---|
| 起始条件 (S) | 高电平 | 从高→低跳变 | 主机 |
| 停止条件 (P) | 高电平 | 从低→高跳变 | 主机 |
关键点在于:SCL为高时,SDA不能随意变化。只有在SCL为低时,SDA才能改变数据。起始和停止条件恰恰是“例外”——它们在SCL高时改变SDA,所以从机能识别出这是控制信号,不是数据。
2.2 数据有效性
这个规则很简单,但很多人写代码时会忽略:SCL高电平时,SDA上的数据必须稳定。数据只能在SCL低电平时改变。
为什么这么设计?说白了就是为了抗干扰。你想想看,如果SCL高时SDA还在跳变,接收方根本不知道当前采到的是0还是1。所以协议规定:接收方在SCL的上升沿或高电平期间采样SDA,发送方必须在SCL低电平时准备好下一个bit。
从机开发要点:
- 发送数据时:在SCL低电平期间更新SDA输出
- 接收数据时:在SCL高电平中间时刻采样SDA
- 我习惯在SCL上升沿触发的中断里采样SDA,这样时序最稳定
2.3 字节传输格式
I2C的数据传输是以字节为单位的,每个字节8位,高位在前(MSB first)。这个顺序和很多CPU的寄存器位序一致,但和SPI的某些模式不同,写代码时别搞混了。
一个完整的字节传输流程是这样的:
- 主机拉低SCL,发送方(主机或从机)在SDA上准备好第7位(最高位)
- 主机释放SCL(拉高),接收方在SCL高电平期间采样
- 主机拉低SCL,发送方切换SDA到第6位
- 重复步骤2-3,直到第0位(最低位)发送完毕
- 主机释放SCL,进入ACK/NACK阶段
这里有个细节:每个字节后面必须跟一个ACK位。也就是说,发送8位数据后,第9个SCL时钟是留给应答的。我刚开始做从机时,就忘了这个第9个时钟,结果主机一直收不到ACK,总线卡死。
个人经验: 在从机中断服务函数里,我通常用一个状态机来跟踪当前正在处理的是数据位还是ACK位。比如用变量 bit_count 从0数到7表示数据位,到8表示ACK位。这样逻辑清晰,不容易出错。
2.4 ACK/NACK机制
ACK/NACK是I2C协议的“握手信号”。每次发送完一个字节,接收方必须告诉发送方:“我收到了”或者“我没收到/不想收了”。
| 应答类型 | SDA电平(第9个SCL) | 含义 |
|---|---|---|
| ACK | 低电平 | 接收成功,请继续发送下一字节 |
| NACK | 高电平 | 接收失败/传输结束/从机忙 |
具体规则:
- 接收方在第9个SCL时钟期间控制SDA
- 发送方在第9个SCL时钟期间释放SDA(设为输入或高阻)
- 如果接收方拉低SDA → ACK,表示“收到,继续”
- 如果接收方不拉低SDA(保持高) → NACK,表示“停止”
在从机模式下,NACK的使用场景很关键:
- 从机接收数据时,如果接收缓冲区满了,可以回复NACK让主机停止发送
- 从机发送数据时,如果主机回复NACK,从机就知道主机不要更多数据了,应该释放总线
- 地址匹配失败时,从机必须回复NACK(实际上是不响应,保持SDA高)
我曾经踩过的坑: 有一次做从机发送,主机读数据时回复了NACK,我的从机代码没检测到这个NACK,还在继续往SDA上写数据。结果主机已经释放总线了,SDA被从机一直拉着,总线直接锁死。后来我加了个NACK检测:在ACK阶段如果检测到SDA为高,立即释放SDA并回到IDLE状态。
2.5 完整时序流程
把上面这些拼起来,一个完整的I2C写操作时序是这样的:
- 主机发送起始条件(S)
- 主机发送7位从机地址 + 1位写标志(0)
- 从机在第9个SCL回复ACK
- 主机发送寄存器地址(1字节)
- 从机回复ACK
- 主机发送数据(1字节或多字节)
- 每发完1字节,从机回复ACK
- 主机发送停止条件(P)
读操作类似,只是地址字节的最后一位是1(读),并且数据方向会反转:从机变成发送方,主机变成接收方。
下面这张图把整个时序流程串起来了,我建议你对着图把每个阶段的状态机走一遍:
2.6 从机视角的时序要点
作为从机开发者,你需要关注的是“什么时候该我说话,什么时候该我闭嘴”。我总结了几条核心原则:
- 地址匹配前:从机只监听,不驱动SDA(高阻态)
- 地址匹配后:如果是写操作,从机在ACK阶段拉低SDA;如果是读操作,从机在数据阶段驱动SDA
- 每个字节结束后:从机必须准备好ACK/NACK响应
- 收到停止条件后:从机立即释放SDA,回到IDLE状态
从机状态机核心状态:
- IDLE:等待起始条件
- ADDR:接收地址字节,比较是否匹配
- ACK_ADDR:地址匹配后发送ACK
- DATA_RX:接收数据字节(写操作)
- DATA_TX:发送数据字节(读操作)
- ACK_DATA:数据字节后的应答阶段
- STOP:检测到停止条件,复位到IDLE
嗯,时序这块内容确实多,但它是I2C从机开发的基本功。我建议你找个逻辑分析仪,抓一段真实的I2C波形,对着时序图一个一个bit地看。看懂了波形,写代码时心里就有底了。