4. I2C数据传输:主写从读、主读从写、复合传输、重复起始条件
数据传输是I2C协议的核心。说白了,总线上的所有操作,最终都归结为「谁写谁读」的问题。我刚开始接触I2C时,总觉得协议很绕——又是起始条件,又是应答,还要区分读写方向。但后来发现,只要理清四种基本传输模式,剩下的都是套路。
这一节,我们就把这四种模式掰开揉碎。你想想看,主设备要跟从设备说话,无非就这几种情况:
- 主写从读:主设备发数据,从设备收数据
- 主读从写:主设备收数据,从设备发数据
- 复合传输:一次传输里既有写又有读
- 重复起始条件:不释放总线,直接开始下一轮传输
嗯,咱们一个一个来看。
4.1 主写从读:最基础的传输
这是I2C最常用的模式。主设备发送数据给从设备,比如配置传感器寄存器、写入显示缓冲区等。
流程其实很简单:
- 主设备发送起始条件(S)
- 发送从设备地址 + 写位(0)
- 等待从设备应答(ACK)
- 发送数据字节,每字节后等待应答
- 发送停止条件(P)
我在项目中遇到过一个问题:某个温度传感器,写入配置寄存器后死活不响应。后来发现,从设备在收到每个字节后需要一点时间处理,而我连续发送太快了。解决办法是在每字节后加一个微秒级的延时,或者检查SCL时钟延展。
关键点:主写模式下,数据方向是从主设备到从设备。从设备通过拉低SDA来应答,表示「我收到了」。
4.2 主读从写:读取从设备数据
这个模式用于主设备从从设备读取数据,比如读取传感器数值、读取存储器内容。
流程稍微有点不同:
- 主设备发送起始条件(S)
- 发送从设备地址 + 读位(1)
- 等待从设备应答(ACK)
- 主设备接收数据字节,每字节后发送应答
- 最后一个字节,主设备发送非应答(NACK)
- 发送停止条件(P)
这里有个坑:最后一个字节必须发NACK。为什么?因为从设备看到NACK就知道「主设备不想再要数据了」,然后释放SDA总线。如果你发了ACK,从设备会继续发数据,总线就乱套了。
我曾经在调试一个加速度计驱动时,忘了在最后一个字节发NACK。结果从设备一直发数据,主设备一直收,最后总线挂死。查了两天才找到原因——嗯,从那以后我写驱动都会在接收循环里单独处理最后一个字节。
4.3 复合传输:一次传输搞定读写
很多时候,我们需要先写再读。比如读取某个寄存器:先写入寄存器地址,再读取寄存器值。
复合传输就是干这个的。它把写和读合并到一次传输里,中间用重复起始条件(Sr)连接,不释放总线。
流程是这样的:
- 起始条件(S)
- 从设备地址 + 写位(0)
- 发送寄存器地址(写操作)
- 重复起始条件(Sr)
- 从设备地址 + 读位(1)
- 读取寄存器值(读操作)
- 停止条件(P)
你看,中间没有停止条件,总线一直被主设备占用。这样做的好处是:原子性。其他主设备无法在中间插入操作,保证了数据传输的完整性。
我个人习惯:在RTOS驱动中,复合传输一定要用临界区保护。因为如果被任务切换打断,重复起始条件可能发不完整,总线状态就乱了。
4.4 重复起始条件:不释放总线的艺术
重复起始条件(Repeated Start,简称Sr)是I2C协议里一个很巧妙的设计。它允许主设备在不发送停止条件的情况下,直接开始新一轮传输。
为什么要这么做?
- 防止总线被抢占:如果发了停止条件,其他主设备可能抢占总线
- 提高效率:省去了停止和重新开始的时间
- 保证原子性:多步操作不会被中断
时序上,重复起始条件和普通起始条件完全一样:SCL高电平时,SDA从高变低。区别只在于它前面没有停止条件。
我记得在调试一个多主设备系统时,两个主设备同时访问同一个从设备。如果不使用重复起始条件,一个主设备发了停止条件后,另一个主设备立刻抢占了总线,导致数据错乱。改用重复起始条件后,问题就解决了。
4.5 四种模式对比
为了方便记忆,我整理了一个对比表:
| 传输模式 | 数据方向 | 起始条件 | 停止条件 | 典型应用 |
|---|---|---|---|---|
| 主写从读 | 主→从 | 1次 | 1次 | 配置寄存器、写存储器 |
| 主读从写 | 从→主 | 1次 | 1次 | 读取传感器、读存储器 |
| 复合传输 | 先主→从,后从→主 | 1次起始 + 1次重复起始 | 1次 | 先写地址再读数据 |
| 重复起始 | 任意 | 多次(含Sr) | 最后1次 | 多步原子操作 |
4.6 知识结构图
下面这张图展示了四种传输模式的关系和流程:
4.7 RTOS驱动中的实现要点
在RTOS环境下实现这些传输模式,有几个地方要特别注意:
- 超时处理:从设备可能因为忙而拉低SCL(时钟延展),驱动必须设置超时,防止死等
- 中断与轮询:传输过程中,建议用中断方式处理每字节的收发,避免阻塞任务调度
- 错误恢复:如果从设备无应答,驱动应该能发送停止条件复位总线,而不是卡死
- 缓冲区管理:复合传输涉及多次数据交换,缓冲区要提前规划好,避免溢出
我建议:在驱动层封装一个统一的传输接口,把四种模式抽象成参数。上层应用只需要告诉驱动「我要做什么」,不用关心底层时序。这样代码复用率高,也容易维护。
好了,四种传输模式就讲到这里。理解了这些,I2C协议的基本操作你就掌握了八成。剩下的两成,是各种细节和坑——我们后面章节慢慢聊。