4. I2C 数据传输模式:主设备发送/接收、从设备发送/接收、复合传输(Combined Format)

I2C 总线上的数据传输严格遵循主-从架构,所有传输均由主设备发起并控制时钟信号(SCL)。根据数据传输方向的不同,I2C 协议定义了四种基本传输模式以及一种高效的复合传输模式。理解这些模式是编写正确驱动、调试通信故障以及优化吞吐量的基础。

4.1 传输模式分类与方向控制

I2C 协议通过控制读写位(R/W#)来决定数据流方向。在起始条件(S)后,主设备发送 7 位从设备地址 + 1 位读写标志位:

  • R/W# = 0:主设备发送数据(写操作)。
  • R/W# = 1:主设备接收数据(读操作)。
传输模式 方向 主设备角色 从设备角色 典型应用场景
主设备发送 主 → 从 发送器(Transmitter) 接收器(Receiver) 配置寄存器、发送命令、写入 EEPROM
主设备接收 从 → 主 接收器(Receiver) 发送器(Transmitter) 读取传感器数据、读取状态寄存器
复合传输 主→从 + 从→主 发送器 + 接收器 接收器 + 发送器 先写寄存器地址再读数据(最常用)
驱动开发提示: 在 Linux 内核中,i2c_msg 结构体的 flags 字段通过 I2C_M_RD 标志位控制方向。主设备发送时 flags = 0,主设备接收时 flags = I2C_M_RD。

4.2 主设备发送模式(Master Transmitter Mode)

在此模式下,主设备作为发送器,从设备作为接收器。数据从主设备流向从设备。这是 I2C 通信中最基本的写操作流程。

主设备发送模式(写操作)时序与数据流 主设备(发送器) 从设备(接收器) 数据字节 + ACK 传输步骤 ① 主设备产生起始条件(S) ② 主设备发送 7 位从地址 + R/W# = 0 ③ 从设备在第 9 个 SCL 周期回复 ACK ④ 主设备发送 1 字节数据,从设备每字节回复 ACK ⑤ 主设备产生停止条件(P)
关键点: 主设备发送模式下,从设备通过拉低 SDA 线(ACK)来确认每个字节的接收。如果从设备未准备好或地址不匹配,从设备会在第 9 个时钟周期保持 SDA 为高(NACK),主设备应据此判断错误并发送停止条件。

4.3 主设备接收模式(Master Receiver Mode)

在此模式下,主设备作为接收器,从设备作为发送器。数据从从设备流向主设备。主设备在接收完每个字节后必须发送 ACK 以继续传输,发送 NACK 则通知从设备停止发送。

主设备接收模式(读操作)时序与数据流 主设备(接收器) 从设备(发送器) 数据字节 + ACK/NACK 传输步骤 ① 主设备产生起始条件(S) ② 主设备发送 7 位从地址 + R/W# = 1 ③ 从设备在第 9 个 SCL 周期回复 ACK ④ 从设备发送 1 字节数据,主设备每字节回复 ACK ⑤ 主设备接收最后一个字节后回复 NACK,产生停止条件(P)
常见调试陷阱: 主设备接收模式下,如果主设备在最后一个字节后没有发送 NACK 就直接发送停止条件,从设备可能会继续发送数据,导致总线冲突。务必在读取最后一个字节前将 ACK 位设置为 NACK。

4.4 复合传输模式(Combined Format)

复合传输是 I2C 协议中最强大、最常用的模式。它允许在一次总线事务中,不释放总线(不发送停止条件),连续执行一次写操作和一次读操作。这种模式通常用于先向从设备写入寄存器地址,再读取该寄存器的数据。

复合传输模式(Combined Format)完整流程 S + 从地址 + W 寄存器地址字节 Sr(重启条件) S + 从地址 + R 数据流方向 主设备 → 从设备:发送从地址(写方向)+ 寄存器地址 主设备 → 从设备:发送重启条件(Sr),不释放总线 从设备 → 主设备:发送从地址(读方向)+ 寄存器数据 复合传输的关键优势 ✅ 原子性:整个操作在一个总线事务中完成,不会被其他主设备打断。 ✅ 效率:省去了两次传输之间的停止-启动开销,减少总线空闲时间。 ✅ 兼容性:几乎所有 I2C 从设备(传感器、ADC、DAC)都支持此模式。

4.5 Linux 内核中的复合传输实现

在 Linux 内核中,复合传输通过 i2c_transfer() 函数发送一个包含两个 i2c_msg 的消息数组来实现。第一个消息为写方向(flags = 0),第二个消息为读方向(flags = I2C_M_RD)。内核 I2C 核心层会自动处理起始条件、重启条件和停止条件。

#include <linux/i2c.h>

/* 复合传输:先写寄存器地址,再读数据 */
static int i2c_read_reg(struct i2c_client *client, u8 reg_addr, u8 *buf, int len)
{
    struct i2c_msg msgs[2];
    int ret;

    /* 消息 0:主设备发送模式,写入寄存器地址 */
    msgs[0].addr  = client->addr;   /* 从设备地址 */
    msgs[0].flags = 0;              /* 写方向 */
    msgs[0].len   = 1;              /* 寄存器地址长度 */
    msgs[0].buf   = &reg_addr;      /* 寄存器地址数据 */

    /* 消息 1:主设备接收模式,读取寄存器数据 */
    msgs[1].addr  = client->addr;   /* 从设备地址 */
    msgs[1].flags = I2C_M_RD;       /* 读方向 */
    msgs[1].len   = len;            /* 要读取的字节数 */
    msgs[1].buf   = buf;            /* 数据缓冲区 */

    /* 执行复合传输,内核自动处理 Sr 重启条件 */
    ret = i2c_transfer(client->adapter, msgs, 2);
    if (ret < 0) {
        dev_err(&client->dev, "复合传输失败: %d\n", ret);
        return ret;
    }

    return 0;
}
性能调优要点: 复合传输模式将两次独立的传输合并为一次总线事务,减少了总线仲裁开销和时钟周期浪费。在批量读取传感器数据时,应始终使用复合传输而非两次独立的 i2c_master_send() + i2c_master_recv() 调用,后者会在两次传输之间插入停止-启动条件,降低总线利用率。

4.6 四种模式对比与选择指南

特性 主设备发送 主设备接收 复合传输
总线事务数 1 次 1 次 1 次(含重启)
起始条件 1 次(S) 1 次(S) 2 次(S + Sr)
停止条件 1 次(P) 1 次(P) 1 次(P)
数据方向 单向(主→从) 单向(从→主) 双向(先写后读)
典型用途 写配置、发送命令 读状态、读固定数据 读寄存器、读传感器值
总线效率 高 高 最高(无空闲周期)
调试建议: 使用逻辑分析仪抓取 I2C 总线波形时,重点关注复合传输中的重启条件(Sr)。如果波形显示在写操作后出现了停止条件(P)再重新启动(S),说明驱动未正确使用复合传输,而是拆分为两次独立传输,这会降低性能并可能引入竞态条件。