I2C/SPI 通信协议基础:时序、原理与 Linux 内核操作
摄像头传感器通常通过 I2C 或 SPI 总线与主控 SoC 连接,用于寄存器配置、参数读写和固件加载。理解这两种协议的时序特征和内核操作方法是驱动开发的基础。本章将深入分析 I2C 与 SPI 的物理层时序、数据帧结构,并给出 Linux 内核中操作对应设备的标准化流程。
2.1 I2C 协议时序详解
I2C(Inter-Integrated Circuit)是一种同步、多主从、2 线制的串行通信协议。物理层由串行时钟线(SCL)和串行数据线(SDA)组成,两条线均需上拉电阻。
2.1.1 I2C 基本时序单元
| 时序阶段 | SCL 状态 | SDA 状态 | 说明 |
|---|---|---|---|
| 起始条件 (Start) | 高电平 | 下降沿 | SCL 高时 SDA 由高变低 |
| 数据位传输 | 低→高→低 | SCL 低时变化,高时采样 | 每时钟传输 1 位,MSB 优先 |
| 应答位 (ACK) | 第 9 个时钟 | 从机拉低 | 从机应答表示接收成功 |
| 非应答 (NACK) | 第 9 个时钟 | 主机释放(高) | 表示结束或错误 |
| 停止条件 (Stop) | 高电平 | 上升沿 | SCL 高时 SDA 由低变高 |
时序要点:I2C 数据在 SCL 高电平时被采样,因此 SDA 必须在 SCL 低电平时变化。起始和停止条件由 SDA 在 SCL 高电平时的跳变唯一标识,不可被误认为数据位。
2.2 SPI 协议时序详解
SPI(Serial Peripheral Interface)是一种同步、全双工、主从式的 4 线制串行通信协议。四条信号线分别为:串行时钟(SCLK)、主出从入(MOSI)、主入从出(MISO)、片选(CS)。
2.2.1 SPI 四种模式(CPOL / CPHA)
| 模式 | CPOL | CPHA | 时钟极性 | 采样沿 | 典型应用 |
|---|---|---|---|---|---|
| 模式 0 | 0 | 0 | 空闲低 | 上升沿采样 | 多数传感器、摄像头 |
| 模式 1 | 0 | 1 | 空闲低 | 下降沿采样 | 部分 ADC |
| 模式 2 | 1 | 0 | 空闲高 | 下降沿采样 | 某些存储器 |
| 模式 3 | 1 | 1 | 空闲高 | 上升沿采样 | SD 卡、W25Qxx |
驱动配置关键:摄像头传感器通常工作在 SPI 模式 0 或模式 3。在 Linux 设备树中通过
spi-cpol 和 spi-cpha 属性指定。务必核对传感器数据手册中的时序图,否则可能导致寄存器写入失败或数据错位。
2.3 Linux 内核中操作 I2C 设备
Linux 内核将 I2C 设备抽象为 struct i2c_client,通过 I2C 核心层提供的 API 进行寄存器读写。驱动开发人员通常使用 i2c_smbus_read_byte_data 和 i2c_smbus_write_byte_data 等封装函数。
2.3.1 I2C 设备驱动核心 API
| 函数 | 功能 | 参数 |
|---|---|---|
i2c_smbus_read_byte_data(client, reg) | 读取指定寄存器的 8 位值 | client: i2c_client 指针;reg: 寄存器地址 |
i2c_smbus_write_byte_data(client, reg, val) | 向指定寄存器写入 8 位值 | val: 待写入数据 |
i2c_smbus_read_word_data(client, reg) | 读取 16 位寄存器值(小端) | 返回 16 位数据 |
i2c_transfer(adap, msgs, num) | 执行自定义 I2C 消息传输 | msgs: 消息数组;num: 消息数量 |
2.3.2 I2C 驱动示例:摄像头传感器寄存器读写
#include <linux/i2c.h>
#include <linux/module.h>
static int sensor_read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
int ret;
struct i2c_msg msg[2];
u8 tx_buf = reg;
u8 rx_buf;
/* 写寄存器地址 */
msg[0].addr = client->addr;
msg[0].flags = 0; /* 写 */
msg[0].len = 1;
msg[0].buf = &tx_buf;
/* 读寄存器值 */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; /* 读 */
msg[1].len = 1;
msg[1].buf = &rx_buf;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2) {
*val = rx_buf;
return 0;
}
return -EIO;
}
static int sensor_write_reg(struct i2c_client *client, u8 reg, u8 val)
{
u8 buf[2] = {reg, val};
return i2c_smbus_write_byte_data(client, reg, val);
}
避坑提示:部分摄像头传感器要求连续地址写入时自动递增地址指针,此时应使用
i2c_master_send 一次性发送多字节数据,而非逐字节调用 i2c_smbus_write_byte_data,否则可能触发不必要的 Stop 条件,导致地址指针复位。
2.4 Linux 内核中操作 SPI 设备
SPI 设备在内核中表示为 struct spi_device,通过 SPI 核心提供的 spi_write_then_read 或 spi_sync_transfer 进行全双工通信。摄像头传感器通常使用 SPI 进行固件下载或高速寄存器批量配置。
2.4.1 SPI 设备核心 API
| 函数 | 功能 | 说明 |
|---|---|---|
spi_write_then_read(spi, txbuf, txlen, rxbuf, rxlen) | 先写后读(半双工) | 适用于先发命令再读数据的场景 |
spi_sync_transfer(spi, xfers, num_xfers) | 执行一组 SPI 传输 | 支持全双工、多段传输 |
spi_write(spi, buf, len) | 只写数据 | 封装了 spi_sync_transfer |
spi_read(spi, buf, len) | 只读数据 | 封装了 spi_sync_transfer |
2.4.2 SPI 驱动示例:摄像头传感器批量寄存器配置
#include <linux/spi/spi.h>
struct sensor_reg_val {
u16 reg;
u8 val;
};
static int sensor_spi_write_regs(struct spi_device *spi,
const struct sensor_reg_val *table, int count)
{
int ret, i;
u8 tx_buf[3]; /* 16位地址 + 8位数据 */
for (i = 0; i < count; i++) {
tx_buf[0] = (table[i].reg >> 8) & 0xFF; /* 地址高字节 */
tx_buf[1] = table[i].reg & 0xFF; /* 地址低字节 */
tx_buf[2] = table[i].val; /* 数据 */
ret = spi_write(spi, tx_buf, 3);
if (ret < 0) {
dev_err(&spi->dev, "SPI write reg 0x%04x failed\n", table[i].reg);
return ret;
}
}
return 0;
}
2.5 设备树配置要点
在 Linux 设备树中,I2C 和 SPI 设备节点需要正确描述总线频率、地址、模式等参数。以下为摄像头传感器设备树片段示例:
/* I2C 摄像头传感器节点 */
&i2c2 {
status = "okay";
clock-frequency = <400000>; /* 400kHz 快速模式 */
sensor0: camera@36 {
compatible = "sony,imx290";
reg = <0x36>; /* 7位 I2C 地址 */
reset-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>;
dvdd-supply = <®_1v2>;
};
};
/* SPI 摄像头传感器节点 */
&spi1 {
status = "okay";
cs-gpios = <&gpio4 10 GPIO_ACTIVE_LOW>;
sensor1: camera@0 {
compatible = "ovt,ov5640-spi";
reg = <0>; /* 片选索引 */
spi-max-frequency = <20000000>; /* 20MHz */
spi-cpol; /* 模式 2 或 3 */
spi-cpha;
};
};
调试建议:使用
i2cdetect -y <bus> 扫描 I2C 总线上的设备地址;使用 i2cget 和 i2cset 工具直接读写寄存器,快速验证硬件连接和时序是否正确。对于 SPI 设备,可使用 spidev_test 工具进行环回测试。
2.6 I2C 与 SPI 选型对比
| 对比维度 | I2C | SPI |
|---|---|---|
| 引脚数量 | 2 (SCL, SDA) | 4 (SCLK, MOSI, MISO, CS) |
| 最大速率 | 1MHz (快速模式+) | 50MHz+ (取决于控制器) |
| 通信方式 | 半双工 | 全双工 |
| 寻址方式 | 7位/10位地址 | 片选 (CS) 线 |
| 多设备支持 | 总线挂载多个设备 | 每个设备独立 CS |
| 硬件复杂度 | 需上拉电阻 | 无需上拉,但引脚多 |
| 典型摄像头应用 | 寄存器配置、参数调优 | 固件下载、高速数据流 |
工程实践:多数摄像头传感器同时支持 I2C 和 SPI 两种接口。I2C 用于运行时寄存器读写(速率要求低),SPI 用于上电初始化时的固件批量下载(速率要求高)。驱动开发时应根据传感器数据手册的时序要求,正确配置总线频率和 SPI 模式,避免因时序不匹配导致图像异常。