3. Camera Sensor驱动基础

Camera Sensor 驱动是 V4L2 Subdev 体系中最底层的硬件抽象层。开发者必须掌握 Sensor 的物理接口、控制总线以及电源管理时序,才能正确初始化 Sensor 并输出符合 ISP 要求的图像数据。本章围绕这三个核心基础展开。

3.1 Sensor硬件接口:MIPI CSI-2 与 DVP

当前主流 Sensor 与 SoC/ISP 之间的数据传输接口分为两大类:MIPI CSI-2(Camera Serial Interface 2)和 DVP(Digital Video Port)。两者在信号线数量、速率、抗干扰能力和适用场景上有显著差异。

对比项 MIPI CSI-2 DVP
信号类型 差分串行(LVDS) 并行 CMOS 电平
数据线数量 1~4 Lane(每 Lane 2 线) 8~12 位并行数据线 + 同步信号
时钟 DDR 时钟(双边沿采样) 单端像素时钟
最大速率 每 Lane 可达 2.5 Gbps(DPHY v2.0) 通常 ≤ 150 MHz 像素时钟
抗干扰 强(差分共模抑制) 弱(易受 EMI 影响)
线缆/走线 柔性排线或同轴,走线少 排线宽,走线多
典型应用 手机、平板、车载摄像头 低分辨率、低成本、短距离
驱动开发提示:MIPI CSI-2 驱动需要配置 DPHY 参数(LP/HS 模式、均衡、摆幅),而 DVP 驱动主要关注像素时钟极性和同步信号极性。在设备树中通过 data-lanes 和 clock-noncontinuous 等属性描述 CSI-2 拓扑。
MIPI CSI-2 与 DVP 接口对比架构 MIPI CSI-2 接口 Sensor TX ISP RX D0+ / D0- D1+ / D1- CLK+ / CLK- DVP 接口 Sensor TX ISP RX D[0:7] (8位) HSYNC VSYNC PCLK

3.2 Sensor寄存器读写:I2C 与 SPI 控制总线

Sensor 的寄存器配置(曝光、增益、帧率、输出格式等)通过低速控制总线完成。绝大多数 Sensor 使用 I2C,少数高速或高分辨率 Sensor 使用 SPI。驱动开发的核心是封装正确的读写时序。

3.2.1 I2C 读写流程

I2C 是两线制(SCL + SDA)同步串行总线。Sensor 作为从设备,驱动通过主控器(I2C adapter)发起读写事务。标准流程如下:

  1. START 条件:SCL 高电平时 SDA 由高变低。
  2. 发送从设备地址 + 写位 (W):7 位地址 + 0。
  3. 等待 ACK:Sensor 拉低 SDA 确认。
  4. 发送寄存器地址:16 位或 8 位,取决于 Sensor 规格。
  5. 等待 ACK。
  6. 发送数据(写)或 重复 START + 读位(读)。
  7. STOP 条件:SCL 高电平时 SDA 由低变高。
内核 I2C 读写辅助函数:
// 单字节写
int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);

// 多字节读(自动处理地址递增)
int regmap_bulk_read(struct regmap *map, unsigned int reg,
                     void *val, size_t val_count);

// 使用 i2c_smbus 接口(适用于简单 Sensor)
int i2c_smbus_write_byte_data(struct i2c_client *client,
                              u8 command, u8 value);
int i2c_smbus_read_byte_data(struct i2c_client *client, u8 command);

3.2.2 SPI 读写流程

SPI 是四线制(SCLK、MOSI、MISO、CS)全双工同步总线。Sensor 通常使用 SPI 模式 0(CPOL=0, CPHA=0)或模式 3。读写操作通过 16 位或 24 位命令字完成:

位域 宽度 说明
RW 1 bit 1=读,0=写
ADDR 15 bits 寄存器地址(高位在前)
DATA 8 bits 写入数据(读时忽略)
// SPI 读写示例(使用 Linux SPI 框架)
static int sensor_spi_write(struct spi_device *spi, u16 reg, u8 val)
{
    u16 cmd = (0 << 15) | (reg & 0x7FFF);  // 写命令
    u8 tx_buf[3] = { cmd >> 8, cmd & 0xFF, val };
    struct spi_transfer t = {
        .tx_buf = tx_buf,
        .len = 3,
    };
    struct spi_message m;
    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spi_sync(spi, &m);
}

static int sensor_spi_read(struct spi_device *spi, u16 reg, u8 *val)
{
    u16 cmd = (1 << 15) | (reg & 0x7FFF);  // 读命令
    u8 tx_buf[3] = { cmd >> 8, cmd & 0xFF, 0 };
    u8 rx_buf[3] = {0};
    struct spi_transfer t = {
        .tx_buf = tx_buf,
        .rx_buf = rx_buf,
        .len = 3,
    };
    struct spi_message m;
    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    int ret = spi_sync(spi, &m);
    if (ret == 0)
        *val = rx_buf[2];
    return ret;
}
常见陷阱:部分 Sensor 的 I2C 地址会随 VSYNC 或电源状态变化;SPI 模式下 CS 片选必须在整个事务期间保持低电平,否则 Sensor 会丢弃数据。务必查阅数据手册的时序图。

3.3 Sensor上电时序与复位

Sensor 对电源上电顺序和复位信号有严格的时间要求。错误的时序可能导致 Sensor 无法识别、输出花屏甚至损坏。典型的 Sensor 电源域包括:

  • AVDD(模拟电源,通常 2.8V)
  • DVDD(数字核心电源,通常 1.2V 或 1.8V)
  • IOVDD(I/O 接口电源,通常 1.8V)

3.3.1 标准上电时序步骤

  1. 拉低 RESET 引脚(确保 Sensor 处于复位状态)。
  2. 依次使能电源:通常先 AVDD,再 DVDD,最后 IOVDD。间隔至少 100 μs。
  3. 等待电源稳定:各电源达到标称值后等待 1~10 ms。
  4. 释放 RESET:拉高 RESET 引脚,保持高电平。
  5. 等待内部初始化:Sensor 内部 PLL 和寄存器加载需要 5~20 ms。
  6. 通过 I2C/SPI 写入初始化序列。
Sensor 上电时序图 时间 AVDD 2.8V DVDD 1.2V IOVDD 1.8V RESET 低(复位) 高(释放) I2C 写寄存器 t0 t1 t2 t3 t4

3.3.2 驱动中实现上电时序

在 Linux 内核中,Sensor 驱动通常通过 struct regulator 和 struct gpio_desc 控制电源和复位。推荐在 sensor_power_on() 函数中实现完整时序:

static int sensor_power_on(struct device *dev)
{
    struct sensor_dev *sensor = dev_get_drvdata(dev);
    int ret;

    // 1. 拉低复位
    gpiod_set_value_cansleep(sensor->reset_gpio, 0);

    // 2. 依次使能电源
    ret = regulator_enable(sensor->avdd);   // AVDD 2.8V
    if (ret) return ret;
    usleep_range(200, 300);                 // t0->t1 间隔

    ret = regulator_enable(sensor->dvdd);   // DVDD 1.2V
    if (ret) goto err_dvdd;
    usleep_range(200, 300);                 // t1->t2 间隔

    ret = regulator_enable(sensor->iovdd);  // IOVDD 1.8V
    if (ret) goto err_iovdd;
    msleep(5);                              // t2->t3 电源稳定

    // 3. 释放复位
    gpiod_set_value_cansleep(sensor->reset_gpio, 1);
    msleep(10);                             // t3->t4 内部初始化

    // 4. 写入初始化寄存器序列
    ret = sensor_init_regs(sensor);
    if (ret) goto err_init;

    return 0;

err_init:
    regulator_disable(sensor->iovdd);
err_iovdd:
    regulator_disable(sensor->dvdd);
err_dvdd:
    regulator_disable(sensor->avdd);
    return ret;
}
调试技巧:使用 dev_dbg() 打印每个电源使能后的电压值(通过 regulator_get_voltage()),配合示波器或逻辑分析仪验证时序。部分 Sensor 要求 RESET 低电平持续时间 ≥ 1 ms,务必在数据手册中确认。

3.4 本章小结