4. I2C数据传输模式:主发送器模式、主接收器模式、多主传输与仲裁
I2C总线协议定义了四种基本的数据传输模式,分别对应主设备与从设备之间的数据流向控制。理解这些模式是编写可靠I2C驱动的基础,尤其是在多主设备共享同一总线的复杂系统中。
4.1 主发送器模式(Master Transmitter)
在主发送器模式下,主设备负责产生时钟信号(SCL)并主动向从设备发送数据。这是I2C通信中最常用的模式,典型场景包括配置传感器寄存器、向EEPROM写入数据或发送控制命令。
关键特征:主设备控制时钟,数据从主设备流向从设备,从设备仅在每个字节后发送ACK/NACK响应。
4.1.1 主发送器模式传输流程
- START条件:主设备在SCL高电平时将SDA从高拉低,通知总线所有设备传输开始。
- 发送从设备地址(7位)+ R/W位(0):主设备发送7位从设备地址,随后发送写位(0),表示主设备将要发送数据。
- 等待从设备ACK:从设备在第9个SCL周期将SDA拉低,确认地址匹配。
- 发送数据字节:主设备逐字节发送数据,每个字节后等待从设备ACK。
- STOP条件:主设备在SCL高电平时将SDA从低拉高,释放总线。
驱动开发提示:在发送每个字节后,务必检查从设备返回的ACK位。如果从设备返回NACK(SDA保持高),应立即停止传输并进入错误处理流程,避免总线挂死。
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 主接收器模式传输流程
- START条件:主设备发起传输。
- 发送从设备地址 + R/W位(1):R/W位设置为1,表示主设备将要接收数据。
- 等待从设备ACK:从设备确认地址。
- 接收数据字节:主设备读取每个字节,并在每个字节后发送ACK(除最后一个字节外)。
- 发送NACK + STOP:主设备在最后一个字节后发送NACK,随后发送STOP条件。
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 = ®_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核心层已经封装了仲裁逻辑,驱动开发者只需确保在中断处理中不长时间占用总线,避免仲裁超时。
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设备驱动的基础。在实际开发中,主发送器和主接收器模式通常组合使用(例如先写寄存器地址再读数据),而多主传输则需要更严谨的总线管理和错误处理策略。