4. I2C数据传输模式:主发送器模式、主接收器模式、多主传输与仲裁

I2C总线协议定义了四种基本的数据传输模式,分别对应主设备与从设备之间的数据流向控制。理解这些模式是编写可靠I2C驱动的基础,尤其是在多主设备共享同一总线的复杂系统中。

4.1 主发送器模式(Master Transmitter)

在主发送器模式下,主设备负责产生时钟信号(SCL)并主动向从设备发送数据。这是I2C通信中最常用的模式,典型场景包括配置传感器寄存器、向EEPROM写入数据或发送控制命令。

关键特征:主设备控制时钟,数据从主设备流向从设备,从设备仅在每个字节后发送ACK/NACK响应。

4.1.1 主发送器模式传输流程

  1. START条件:主设备在SCL高电平时将SDA从高拉低,通知总线所有设备传输开始。
  2. 发送从设备地址(7位)+ R/W位(0):主设备发送7位从设备地址,随后发送写位(0),表示主设备将要发送数据。
  3. 等待从设备ACK:从设备在第9个SCL周期将SDA拉低,确认地址匹配。
  4. 发送数据字节:主设备逐字节发送数据,每个字节后等待从设备ACK。
  5. STOP条件:主设备在SCL高电平时将SDA从低拉高,释放总线。
驱动开发提示:在发送每个字节后,务必检查从设备返回的ACK位。如果从设备返回NACK(SDA保持高),应立即停止传输并进入错误处理流程,避免总线挂死。
主发送器模式传输时序与流程 主发送器模式传输流程 START条件 发送地址(7bit) + R/W=0 等待ACK 发送数据 (1~N字节) STOP ⚠ 关键检查点: 每个数据字节后必须检查从设备ACK。若收到NACK,主设备应发送STOP终止传输。 SCL/SDA波形示意 SCL SDA S A6 A0 ACK D7 D0 ACK P

4.1.2 主发送器模式代码示例

/* I2C主发送器模式:向从设备写入1字节数据 */
int i2c_master_write_byte(struct i2c_adapter *adap, 
                          u8 slave_addr, u8 reg_addr, u8 data)
{
    struct i2c_msg msg[2];
    u8 tx_buf[2];
    int ret;

    tx_buf[0] = reg_addr;
    tx_buf[1] = data;

    /* 第一条消息:发送寄存器地址 */
    msg[0].addr  = slave_addr;
    msg[0].flags = 0;           /* 写标志 */
    msg[0].len   = 1;
    msg[0].buf   = &tx_buf[0];

    /* 第二条消息:发送数据 */
    msg[1].addr  = slave_addr;
    msg[1].flags = 0;           /* 写标志 */
    msg[1].len   = 1;
    msg[1].buf   = &tx_buf[1];

    ret = i2c_transfer(adap, msg, 2);
    if (ret < 0) {
        dev_err(&adap->dev, "主发送器模式传输失败: %d\n", ret);
        return ret;
    }
    return 0;
}

4.2 主接收器模式(Master Receiver)

在主接收器模式下,主设备从从设备读取数据。主设备仍然控制时钟,但数据流向变为从设备到主设备。典型应用包括读取传感器测量值、读取EEPROM存储内容或获取设备状态寄存器。

常见陷阱:主接收器模式中,主设备在读取最后一个字节时必须发送NACK,以通知从设备停止发送。如果主设备发送ACK,从设备会继续发送下一个字节,导致总线数据错乱。

4.2.1 主接收器模式传输流程

  1. START条件:主设备发起传输。
  2. 发送从设备地址 + R/W位(1):R/W位设置为1,表示主设备将要接收数据。
  3. 等待从设备ACK:从设备确认地址。
  4. 接收数据字节:主设备读取每个字节,并在每个字节后发送ACK(除最后一个字节外)。
  5. 发送NACK + STOP:主设备在最后一个字节后发送NACK,随后发送STOP条件。
主接收器模式传输流程 主接收器模式传输流程 START 发送地址(7bit) + R/W=1 等待ACK 接收数据 (主发ACK) 最后字节 主发NACK+STOP ⚠ 关键规则: 主设备在接收最后一个字节时必须发送NACK,否则从设备会继续发送数据,导致总线数据错乱。 数据流向:从设备 → 主设备 从设备驱动SDA数据线,主设备驱动SCL时钟线

4.2.2 主接收器模式代码示例

/* I2C主接收器模式:从从设备读取N字节数据 */
int i2c_master_read_bytes(struct i2c_adapter *adap,
                          u8 slave_addr, u8 reg_addr,
                          u8 *rx_buf, int len)
{
    struct i2c_msg msg[2];
    int ret;

    /* 第一条消息:写寄存器地址(组合传输) */
    msg[0].addr  = slave_addr;
    msg[0].flags = 0;           /* 写 */
    msg[0].len   = 1;
    msg[0].buf   = &reg_addr;

    /* 第二条消息:读取数据 */
    msg[1].addr  = slave_addr;
    msg[1].flags = I2C_M_RD;    /* 读标志 */
    msg[1].len   = len;
    msg[1].buf   = rx_buf;

    ret = i2c_transfer(adap, msg, 2);
    if (ret != 2) {
        dev_err(&adap->dev, "主接收器模式传输失败: %d\n", ret);
        return ret < 0 ? ret : -EIO;
    }
    return 0;
}

4.3 多主传输与仲裁(Multi-Master & Arbitration)

I2C总线支持多个主设备共享同一总线,这称为多主传输。多主系统提高了系统的灵活性和冗余性,但也引入了总线冲突的风险。I2C协议通过总线仲裁(Arbitration)机制来解决多个主设备同时发起传输时的冲突问题。

核心原理:I2C总线仲裁基于“线与(Wired-AND)”逻辑。多个主设备可以同时驱动SDA线,但只有输出低电平的主设备才能将SDA拉低。如果某个主设备试图输出高电平,但检测到SDA实际为低电平,则该主设备失去仲裁并立即释放总线。

4.3.1 仲裁过程详解

步骤 主设备A行为 主设备B行为 SDA实际电平 仲裁结果
1 发送地址位:1(高) 发送地址位:0(低) 低(0) 主设备A失去仲裁
2 检测到SDA≠自身输出 继续正常传输 低(0) 主设备A释放总线
3 停止驱动SCL/SDA 完成剩余传输 由B控制 主设备B赢得仲裁
驱动开发要点:在多主系统中,每个主设备在发送每个位后必须读取SDA电平进行仲裁检查。Linux内核的I2C核心层已经封装了仲裁逻辑,驱动开发者只需确保在中断处理中不长时间占用总线,避免仲裁超时。
多主传输仲裁过程 多主传输仲裁过程 主设备A 发送位: 1 (高电平) 主设备B 发送位: 0 (低电平) SDA总线实际电平 = 0 (低) 线与逻辑:低电平优先 仲裁结果:主设备A失去仲裁,释放总线 仲裁后行为 主设备A:停止驱动SCL/SDA 进入总线空闲等待状态 主设备B:继续完成传输 发送剩余地址位和数据 💡 关键理解: 仲裁不会破坏数据完整性。失去仲裁的主设备在检测到冲突后立即释放总线,赢得仲裁的主设备继续传输,从设备感知不到仲裁发生。

4.3.2 多主系统中的驱动注意事项

  • 时钟同步:多个主设备可能产生不同频率的SCL时钟。I2C协议通过“时钟同步”机制,将SCL低电平时间最长的主设备作为同步基准,所有主设备在SCL低电平时等待,直到所有主设备释放SCL高电平。
  • 重试策略:失去仲裁的主设备应在检测到总线空闲(STOP条件后)后重新尝试传输。Linux内核的i2c_transfer()函数内部实现了重试逻辑,但驱动开发者可以设置重试次数上限。
  • 总线空闲检测:在多主系统中,主设备在发起START条件前必须检测总线是否空闲。检测方法:等待SCL和SDA同时为高电平超过一定时间(通常为4.7μs)。

4.3.3 多主传输模式对比

特性 单主传输 多主传输
主设备数量 1个 2个或更多
仲裁需求 无 需要硬件仲裁
时钟同步 不需要 需要时钟同步机制
总线空闲检测 可选 必须实现
驱动复杂度 低 中高
典型应用 单MCU控制多个外设 多MCU共享总线、热插拔主设备
驱动开发警告:在多主系统中,如果主设备在失去仲裁后没有正确释放总线(例如GPIO未配置为开漏输出),可能导致SDA线被持续拉低,造成总线死锁。务必确保所有主设备的SDA和SCL引脚配置为开漏(Open-Drain)模式,并启用内部或外部上拉电阻。

4.4 三种模式总结

模式 数据流向 主设备角色 ACK控制方 典型场景
主发送器 主设备 → 从设备 数据发送者 从设备 写寄存器、发送命令
主接收器 从设备 → 主设备 数据接收者 主设备 读传感器、读存储器
多主传输 双向(取决于角色) 发送者/接收者 动态切换 多MCU系统、冗余设计

掌握这三种数据传输模式是编写I2C设备驱动的基础。在实际开发中,主发送器和主接收器模式通常组合使用(例如先写寄存器地址再读数据),而多主传输则需要更严谨的总线管理和错误处理策略。