4. I2C数据传输模式:主发送/主接收、从发送/从接收、组合传输(复合帧)
I2C总线上的数据传输严格遵循主从架构,所有传输均由主设备发起并控制时钟信号(SCL)。根据数据传输方向的不同,I2C协议定义了四种基本传输模式:主发送、主接收、从发送、从接收。此外,实际应用中经常需要在一个传输周期内改变数据方向,这就构成了组合传输(复合帧)。理解这些模式是编写正确I2C设备驱动的核心基础。
4.1 四种基本传输模式
在I2C总线上,主设备通过控制SCL时钟和生成START/STOP条件来管理传输。数据线SDA上的每一位数据在SCL高电平时被采样。以下表格总结了四种基本模式下的角色与行为:
| 模式 | 主设备行为 | 从设备行为 | 方向(相对主设备) | 典型应用场景 |
|---|---|---|---|---|
| 主发送 | 生成START、时钟、发送从地址(写位)及数据 | 接收数据并回复ACK | 主 → 从 | 配置寄存器、写入传感器命令 |
| 主接收 | 生成START、时钟、发送从地址(读位),随后接收数据并回复ACK/NACK | 发送数据并等待主设备ACK | 主 ← 从 | 读取传感器数据、读取寄存器值 |
| 从发送 | 作为从设备,响应主设备的读请求,在SCL控制下发送数据 | 等待主设备发送读地址,然后发送数据 | 主 ← 从 | 从设备主动提供数据(如EERPOM读操作) |
| 从接收 | 作为从设备,响应主设备的写请求,在SCL控制下接收数据 | 等待主设备发送写地址,然后接收数据 | 主 → 从 | 从设备接收配置或数据(如DAC写操作) |
驱动开发提示: 在Linux内核中,主发送和主接收模式通常通过
i2c_master_send() 和 i2c_master_recv() 接口实现。而从设备模式则需要注册 struct i2c_algorithm 中的 master_xfer 回调(对于主设备控制器驱动)或 smbus_xfer 回调。
4.2 主发送模式详解
主发送模式是I2C通信中最基本的操作。主设备负责生成所有时序信号,并将数据字节逐个发送到从设备。其典型流程如下:
- 生成START条件: 主设备将SDA从高电平拉低,此时SCL为高电平。
- 发送从设备地址(7位或10位): 地址的最高位(LSB)为0,表示写操作。
- 等待从设备ACK: 从设备在第9个SCL周期将SDA拉低,表示应答。
- 发送数据字节: 主设备逐位发送数据,每个字节后等待从设备ACK。
- 生成STOP条件: 主设备将SDA从低电平释放至高电平,此时SCL为高电平。
关键时序约束: 主设备在发送每个字节后必须释放SDA总线,以便从设备在第9个时钟周期拉低SDA产生ACK。如果从设备未应答(NACK),主设备应生成STOP条件终止传输。
4.3 主接收模式详解
主接收模式用于从从设备读取数据。与主发送模式的关键区别在于:地址字节的LSB为1(读操作),且数据字节的ACK由主设备控制。
- 生成START条件。
- 发送从设备地址(读位): 地址字节的LSB为1。
- 等待从设备ACK。
- 接收数据字节: 主设备释放SDA,从设备驱动数据线。主设备在每个字节后发送ACK(表示继续接收)或NACK(表示最后一个字节)。
- 主设备发送NACK后生成STOP条件。
常见错误: 在主接收模式下,如果主设备在接收最后一个字节后仍然发送ACK而不是NACK,从设备会继续发送数据,导致总线冲突或数据错位。务必在最后一个字节前设置NACK标志。
4.4 组合传输(复合帧)
组合传输(Combined Transfer)是I2C协议中极为重要的特性,它允许在一个传输周期内,不产生STOP条件而改变数据传输方向。最常见的场景是:先向从设备写入一个寄存器地址(主发送),然后立即读取该寄存器的值(主接收),中间只使用一个重复起始条件(Repeated START)。
组合传输的优势: 避免了STOP后再START带来的总线竞争风险,保证了操作的原子性。在Linux内核中,组合传输通过
struct i2c_msg 数组实现,多个消息通过 i2c_transfer() 一次性提交。
组合传输的典型流程如下:
- START条件
- 发送从设备地址(写位)
- 等待ACK
- 发送寄存器地址(数据字节)
- 等待ACK
- 重复起始条件(Sr) — 不产生STOP
- 发送从设备地址(读位)
- 等待ACK
- 接收数据字节(主设备发送ACK/NACK)
- STOP条件
4.5 Linux内核中的组合传输实现
在Linux内核中,组合传输通过 struct i2c_msg 结构体数组和 i2c_transfer() 函数实现。每个 i2c_msg 代表一个独立的传输段,内核I2C核心层会自动处理重复起始条件。
#include <linux/i2c.h>
/* 定义两个消息:先写寄存器地址,再读数据 */
struct i2c_msg msg[2];
unsigned char reg_addr = 0x10;
unsigned char rx_buf[4];
/* 消息0:主发送模式,写入寄存器地址 */
msg[0].addr = 0x50; /* 从设备地址 */
msg[0].flags = 0; /* 写标志 */
msg[0].len = 1;
msg[0].buf = ®_addr;
/* 消息1:主接收模式,读取数据 */
msg[1].addr = 0x50; /* 从设备地址 */
msg[1].flags = I2C_M_RD; /* 读标志 */
msg[1].len = 4;
msg[1].buf = rx_buf;
/* 执行组合传输,内核自动插入重复起始条件 */
int ret = i2c_transfer(client->adapter, msg, 2);
if (ret < 0) {
dev_err(&client->dev, "组合传输失败: %d\n", ret);
} else {
/* 成功读取4字节数据到rx_buf */
}
驱动编写建议: 使用组合传输时,务必确保
i2c_msg 数组中的地址一致(除非是特殊的多地址访问场景)。内核I2C子系统在检测到连续消息的地址相同时,会自动生成重复起始条件;如果地址不同,则会生成STOP后再START,这可能导致非预期的总线状态。
4.6 四种模式对比总结
| 特性 | 主发送 | 主接收 | 从发送 | 从接收 |
|---|---|---|---|---|
| 时钟控制 | 主设备 | 主设备 | 主设备 | 主设备 |
| 数据驱动方 | 主设备 | 从设备 | 从设备 | 主设备 |
| ACK控制方 | 从设备 | 主设备 | 主设备 | 从设备 |
| 地址位LSB | 0 (写) | 1 (读) | 1 (读) | 0 (写) |
| 内核接口 | i2c_master_send() |
i2c_master_recv() |
从设备回调 | 从设备回调 |
| 典型用途 | 写寄存器/发送命令 | 读传感器/读寄存器 | 从设备主动上报 | 从设备接收配置 |
掌握这四种基本模式以及组合传输的机制,是理解I2C设备驱动中数据流控制的基础。在实际驱动开发中,组合传输是最常用的操作方式,因为它能够高效、原子地完成“写地址-读数据”这一经典操作序列。