I2C/SPI 通信协议基础:时序、原理与 Linux 内核操作

摄像头传感器通常通过 I2C 或 SPI 总线与主控 SoC 连接,用于寄存器配置、参数读写和固件加载。理解这两种协议的时序特征和内核操作方法是驱动开发的基础。本章将深入分析 I2C 与 SPI 的物理层时序、数据帧结构,并给出 Linux 内核中操作对应设备的标准化流程。

I2C 与 SPI 协议对比架构 I2C 总线 2 线制:SCL + SDA 多主多从,地址寻址 速率:100kHz / 400kHz / 1MHz 数据帧:Start + 7bit地址 + R/W + ACK + 数据 + Stop 典型场景:寄存器读写、参数配置 优点:引脚少、支持多设备 缺点:速率较低、无片选 SPI 总线 4 线制:SCLK + MOSI + MISO + CS 一主多从,片选寻址 速率:10MHz ~ 50MHz+ 数据帧:CS拉低 + 全双工移位 + CS拉高 典型场景:固件下载、高速数据流 优点:高速、全双工、硬件简单 缺点:引脚多、无内置应答

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 写寄存器时序(Start + 设备地址 + 寄存器地址 + 数据 + Stop) SCL SDA S 设备地址(7bit) W ACK 寄存器地址(8bit) ACK 数据(8bit) ACK P
时序要点:I2C 数据在 SCL 高电平时被采样,因此 SDA 必须在 SCL 低电平时变化。起始和停止条件由 SDA 在 SCL 高电平时的跳变唯一标识,不可被误认为数据位。

2.2 SPI 协议时序详解

SPI(Serial Peripheral Interface)是一种同步、全双工、主从式的 4 线制串行通信协议。四条信号线分别为:串行时钟(SCLK)、主出从入(MOSI)、主入从出(MISO)、片选(CS)。

2.2.1 SPI 四种模式(CPOL / CPHA)

模式CPOLCPHA时钟极性采样沿典型应用
模式 000空闲低上升沿采样多数传感器、摄像头
模式 101空闲低下降沿采样部分 ADC
模式 210空闲高下降沿采样某些存储器
模式 311空闲高上升沿采样SD 卡、W25Qxx
SPI 模式 0 时序(CPOL=0, CPHA=0) CS CS 拉低选中从机 SCLK MOSI ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样 ↑ 采样
驱动配置关键:摄像头传感器通常工作在 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;
}
Linux 内核 I2C/SPI 设备驱动架构 用户空间 (Userspace) 内核空间 (Kernel Space) 摄像头传感器驱动 I2C 核心层 SPI 核心层 I2C 控制器驱动 SPI 控制器驱动 硬件 (I2C/SPI 总线 + 摄像头传感器)

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 = <&reg_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 选型对比

对比维度I2CSPI
引脚数量2 (SCL, SDA)4 (SCLK, MOSI, MISO, CS)
最大速率1MHz (快速模式+)50MHz+ (取决于控制器)
通信方式半双工全双工
寻址方式7位/10位地址片选 (CS) 线
多设备支持总线挂载多个设备每个设备独立 CS
硬件复杂度需上拉电阻无需上拉,但引脚多
典型摄像头应用寄存器配置、参数调优固件下载、高速数据流
工程实践:多数摄像头传感器同时支持 I2C 和 SPI 两种接口。I2C 用于运行时寄存器读写(速率要求低),SPI 用于上电初始化时的固件批量下载(速率要求高)。驱动开发时应根据传感器数据手册的时序要求,正确配置总线频率和 SPI 模式,避免因时序不匹配导致图像异常。