4. I2C 读写时序:单字节、多字节与复合传输

I2C 的读写时序,说白了就是主设备和从设备之间怎么“说话”。你想想看,一根 SDA 线,一根 SCL 线,要完成地址发送、数据读写、应答确认,全靠时序来约定。我刚开始调 I2C 时,总觉得时序图看着复杂,后来发现只要抓住几个关键点,其实就那么回事。

4.1 单字节写入:最基础的通信单元

单字节写入是 I2C 通信的最小原子操作。主设备发送一个字节的数据到从设备的某个寄存器地址。流程如下:

  1. 起始条件:SCL 高电平时,SDA 从高变低。
  2. 从设备地址 + 写位 (0):7 位地址 + 1 位写方向。
  3. 从设备应答 (ACK):从设备拉低 SDA 表示收到。
  4. 寄存器地址:要写入的目标寄存器地址。
  5. 从设备应答 (ACK):再次确认。
  6. 数据字节:要写入的 8 位数据。
  7. 从设备应答 (ACK):确认数据已接收。
  8. 停止条件:SCL 高电平时,SDA 从低变高。

关键点:每个字节后面必须跟一个应答位。如果从设备不拉低 SDA(即 NACK),说明通信出问题了。我在项目中遇到过,从设备地址写对了,但寄存器地址写错,从设备直接 NACK,抓了半天波形才发现。

4.2 单字节读取:先写后读

单字节读取稍微绕一点。主设备需要先告诉从设备“我要读哪个寄存器”,然后再切换方向读取数据。这就是所谓的“复合传输”的雏形。

  1. 起始条件。
  2. 从设备地址 + 写位 (0)。
  3. 从设备应答 (ACK)。
  4. 寄存器地址:指定要读取的寄存器。
  5. 从设备应答 (ACK)。
  6. 重复起始条件 (Sr):不产生停止条件,直接再次发起起始。
  7. 从设备地址 + 读位 (1)。
  8. 从设备应答 (ACK)。
  9. 读取数据字节:主设备释放 SDA,从设备发送数据。
  10. 主设备非应答 (NACK):表示只读一个字节,不再继续。
  11. 停止条件。

个人习惯:我调试时,会先确认“写寄存器地址”这一步是否成功。如果从设备在写地址后 ACK 了,但读数据时 NACK,那多半是从设备不支持该寄存器,或者时序太快了。

4.3 多字节读写:批量操作效率高

实际项目中,很少只读写一个字节。比如配置传感器,一次要写好几个寄存器。多字节读写就是单字节操作的连续执行。

多字节写入(页写入)

主设备连续发送多个数据字节,从设备内部地址自动递增。流程:

  • 起始 → 从地址+写 → ACK → 寄存器地址 → ACK → 数据1 → ACK → 数据2 → ACK → ... → 数据N → ACK → 停止。

注意:很多 EEPROM 和传感器有“页大小”限制。比如一次最多写 16 个字节,超过会回卷覆盖。我吃过这个亏,写 32 个字节,结果前 16 个被后 16 个覆盖了。

多字节读取(顺序读取)

主设备在读取阶段连续接收多个字节,每接收一个字节就回复 ACK,直到最后一个字节回复 NACK。流程:

  • 起始 → 从地址+写 → ACK → 寄存器地址 → ACK → Sr → 从地址+读 → ACK → 数据1 → ACK → 数据2 → ACK → ... → 数据N → NACK → 停止。

避坑指南:我曾经调试一个温湿度传感器,多字节读取时数据总是错位。后来发现是主设备在读取最后一个字节后,没有及时发送 NACK 和停止条件,导致从设备继续发送了多余的数据。记住:最后一个字节必须是 NACK。

4.4 复合传输格式:灵活的组合拳

复合传输(Combined Transfer)是 I2C 协议的精髓。它允许在一次传输过程中,包含多个起始条件、多个方向切换,而不产生停止条件。最常见的场景就是上面说的“先写地址再读数据”。

复合传输的典型结构:

S 从地址+W ACK 寄存器地址 ACK Sr 从地址+R ACK 数据1 ACK 数据2 NACK P

其中:

  • S:起始条件
  • Sr:重复起始条件
  • P:停止条件
  • W/R:写/读方向位

复合传输的好处是:原子性。整个操作不会被其他主设备打断,保证了寄存器地址和数据的关联性。如果先发停止再发起始,中间可能被其他设备抢占总线。

实战经验:我调试过一个音频编解码芯片,它的控制接口要求必须用复合传输。如果分开成两次传输(先写地址停止,再读数据),芯片会认为你在写一个新的寄存器,结果读回来的数据永远是错的。嗯,这个坑我记忆犹新。

4.5 时序参数速查表

下面这个表是我自己整理的,调试时经常对照。标准模式(100kHz)和快速模式(400kHz)的参数差异很大。

参数 符号 标准模式 快速模式 说明
SCL 时钟频率 f_SCL 100 kHz 400 kHz 不要超限
保持时间(起始) t_HD;STA 4.0 μs 0.6 μs SDA 低后保持
建立时间(停止) t_SU;STO 4.0 μs 0.6 μs SDA 高前建立
数据保持时间 t_HD;DAT 0 μs 0 μs SCL 下降沿后保持
数据建立时间 t_SU;DAT 250 ns 100 ns SCL 上升沿前建立
SCL 低电平时间 t_LOW 4.7 μs 1.3 μs 最小时间
SCL 高电平时间 t_HIGH 4.0 μs 0.6 μs 最小时间

我的习惯:调试时先用标准模式(100kHz),确保通信正常后再切换到快速模式。很多问题都是因为上升沿太慢或者电容太大导致的。你想想看,400kHz 时一个周期才 2.5μs,稍微有点容性负载,波形就变形了。

4.6 知识结构图:I2C 读写时序全景

下面这张图把单字节、多字节、复合传输的关系画清楚了。建议你保存下来,调试时对照。

I2C 读写时序知识结构图 I2C 读写时序全景图 单字节写入 单字节读取 多字节操作 流程 S → 地址+W → ACK → 寄存器地址 → ACK → 数据 → ACK → P 一次只写1个字节 流程(复合传输) S → 地址+W → ACK → 寄存器地址 → ACK → Sr → 地址+R → ACK → 数据 → NACK → P 含重复起始条件 流程 写入:连续发N个数据 读取:连续收N个数据 最后1个字节发NACK 注意页大小限制 核心要点 每个字节后跟ACK/NACK | 复合传输保证原子性 | 时序参数不能超限

4.7 调试要点总结

  • 起始和停止条件:用示波器抓 SDA 和 SCL,确认边沿是否符合规范。我见过很多板子,起始条件 SDA 下降不够陡,导致从设备无法识别。
  • 应答位检查:每个字节后的第 9 个 SCL 脉冲,SDA 是否被拉低。如果一直是高,说明从设备没响应。
  • 重复起始条件:复合传输中,Sr 之后不要有停止条件。很多新手在这里犯错,导致读数据失败。
  • 时钟拉伸:有些从设备会拉低 SCL 来延长时钟。主设备必须支持时钟拉伸,否则会死等。
  • 上拉电阻:I2C 总线需要上拉电阻。阻值太小会拉低速度,太大则上升沿太慢。我一般用 4.7kΩ 做标准配置。

曾经踩过的坑:有一次调试一个气压传感器,单字节读写都正常,但多字节读取时数据总是乱序。抓波形发现,从设备在连续读取时地址自动递增,但主设备没有正确递增寄存器地址。后来在每次读取前重新发送寄存器地址才解决。嗯,有些芯片的自动递增行为跟数据手册写的不一样,还是要以实测为准。