第二讲:Sensor驱动核心概念——I2C/SPI通信、寄存器模型与V4L2初探

各位同学,欢迎来到第二章。上一章我们聊了Sensor的基本工作原理,今天要进入真正的驱动开发环节了。

驱动开发说白了就三件事:怎么跟Sensor说话、Sensor内部长什么样、Linux怎么管这些设备。我当年刚接触Sensor驱动时,最头疼的就是这三件事搅在一起,理不清头绪。今天咱们一个一个拆开讲。

2.1 通信协议:I2C与SPI

Sensor是个小芯片,主控(SoC)要读写它的寄存器,就得通过某种总线。目前99%的Sensor都用I2C或SPI,少数用MIPI I3C(那是后话)。

2.1.1 I2C协议速览

I2C只有两根线:SCL(时钟)和SDA(数据)。主设备发起通信,从设备响应。每个从设备有唯一的7位或10位地址。

一个典型的I2C写操作流程是这样的:

  1. 主设备发START条件
  2. 主设备发从设备地址 + 写位(0)
  3. 从设备回ACK
  4. 主设备发寄存器地址(1字节或2字节)
  5. 从设备回ACK
  6. 主设备发数据(可连续发多字节)
  7. 每字节后从设备回ACK
  8. 主设备发STOP条件

关键点:I2C地址是7位的,但很多Sensor数据手册给的是8位地址(左移1位后的值)。比如手册写0x6C,实际7位地址是0x36。我踩过这个坑,调了两天才发现地址写反了。

读操作稍微复杂一点,需要先写寄存器地址,然后重新发START条件,再读数据:

// 伪代码:I2C读Sensor寄存器
uint8_t reg_addr = 0x03;
uint8_t value;

i2c_start();
i2c_send_byte(0x6C | 0);  // 写方向
i2c_wait_ack();
i2c_send_byte(reg_addr);   // 寄存器地址
i2c_wait_ack();
i2c_start();               // 重复START
i2c_send_byte(0x6C | 1);  // 读方向
i2c_wait_ack();
value = i2c_read_byte();
i2c_send_nack();           // 主机发NACK表示读完
i2c_stop();

经验之谈:I2C速率通常选100kHz(标准模式)或400kHz(快速模式)。别盲目上1MHz,很多Sensor的I2C接口扛不住,或者走线长了信号会畸变。我建议先按400kHz调通,再考虑提速。

2.1.2 SPI协议速览

SPI比I2C快,但线多:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。每个从设备独占一根CS线。

SPI有四种模式(CPOL和CPHA的组合),Sensor常用模式0(CPOL=0, CPHA=0)或模式3(CPOL=1, CPHA=1)。

特性 I2C SPI
线数 2(SCL, SDA) 4(SCLK, MOSI, MISO, CS)
速率 100kHz~3.4MHz 可达几十MHz
从设备选择 地址(7/10位) 片选(CS)
通信方式 半双工 全双工
适用场景 寄存器配置、低速 高速数据、图像传输

SPI写寄存器时,通常先发16位命令(包含读写位和地址),再发数据。读操作类似,但MISO线上会返回数据。

// 伪代码:SPI读Sensor寄存器(16位地址模式)
uint16_t cmd = (1 << 15) | (reg_addr & 0x7FFF);  // 最高位=1表示读
uint8_t tx_buf[3] = {cmd >> 8, cmd & 0xFF, 0x00};
uint8_t rx_buf[3];

spi_cs_low();
spi_transfer(tx_buf, rx_buf, 3);  // 发命令+读数据
spi_cs_high();

// rx_buf[2] 就是读到的寄存器值

注意:SPI的CS片选时序很关键。有些Sensor要求CS在整帧传输过程中保持低电平,不能每字节都拉高拉低。我见过有人用GPIO模拟CS,结果每发一个字节就拉高一次,Sensor直接不认数据。

2.2 Sensor寄存器模型

每个Sensor内部都有一堆寄存器,用来控制曝光、增益、帧率、输出格式等。理解寄存器模型,是驱动开发的基础。

2.2.1 寄存器分类

我习惯把Sensor寄存器分成几类:

  • 芯片ID寄存器:只读,用来验证I2C/SPI通信是否正常。比如OV5640的芯片ID是0x5640。
  • 模式控制寄存器:设置分辨率、帧率、输出格式等。
  • 模拟增益/数字增益寄存器:控制图像亮度。
  • 曝光时间寄存器:控制快门速度。
  • 输出格式寄存器:设置RAW10、RAW12、YUV等。
  • 测试模式寄存器:输出彩条、棋盘格等测试图案。

我的习惯:拿到一块新Sensor,第一件事就是读芯片ID。如果ID读对了,说明I2C/SPI通了,后面的事才有意义。如果ID读不对,先查硬件连接、电压、时钟。

2.2.2 寄存器操作套路

Sensor寄存器操作有个通用套路:

  1. 先写初始化序列(一堆寄存器配置),让Sensor进入工作状态
  2. 再写流控寄存器(stream on/off),控制数据输出
  3. 运行时动态调整曝光/增益

举个例子,某款Sensor的初始化序列大概长这样:

static struct sensor_reg init_regs[] = {
  {0x0103, 0x01},  // 软件复位
  {0x0100, 0x00},  // 进入待机模式
  {0x0305, 0x04},  // PLL配置
  {0x0307, 0x38},
  {0x030B, 0x01},
  {0x030D, 0x5A},
  {0x3620, 0x43},  // 模拟电路配置
  {0x3621, 0xE0},
  {0x3800, 0x00},  // 窗口裁剪
  {0x3801, 0x00},
  {0x3802, 0x00},
  {0x3803, 0x08},
  {0x3804, 0x0A},
  {0x3805, 0x3F},
  // ... 省略几十行
  {0x0100, 0x01},  // 退出待机,开始输出
};

避坑指南:我曾经在初始化序列里漏写了一组PLL配置,结果Sensor输出的帧率只有标称值的一半。查了两天,最后用示波器量MCLK和PCLK才发现问题。所以,初始化序列一定要对照数据手册逐条核对,别偷懒。

2.3 V4L2框架初探

V4L2(Video for Linux 2)是Linux内核里处理视频设备的框架。Sensor驱动最终要注册到V4L2里,让上层应用(如GStreamer、FFmpeg)能调用。

2.3.1 V4L2的核心概念

V4L2把视频设备抽象成几个关键对象:

  • video_device:代表一个视频节点,比如/dev/video0
  • v4l2_subdev:代表一个子设备,比如Sensor、ISP、MIPI桥接芯片
  • media_entity:媒体实体,用于描述设备间的连接关系
  • v4l2_ctrl:控制接口,比如曝光、增益、白平衡等

Sensor驱动通常实现为一个v4l2_subdev,挂载到CSI(摄像头串行接口)控制器下面。

2.3.2 Sensor驱动的V4L2回调函数

一个典型的Sensor subdev需要实现这些回调:

回调函数 作用
s_power 上电/下电控制
s_stream 启动/停止数据流
enum_mbus_code 枚举支持的输出格式
s_fmt / get_fmt 设置/获取当前格式
s_ctrl 处理用户空间的控制请求

我写驱动时,最常打交道的就是s_stream和s_ctrl。前者控制Sensor是否输出图像,后者处理曝光、增益的实时调整。

// 简化的s_stream回调示例
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    struct sensor_dev *sensor = to_sensor_dev(sd);

    if (enable) {
        // 写寄存器启动MIPI输出
        sensor_write(sensor, 0x0100, 0x01);
        // 启动定时器或中断,开始采集
    } else {
        // 停止输出
        sensor_write(sensor, 0x0100, 0x00);
    }

    return 0;
}

注意:V4L2的s_stream回调可能会被多次调用。比如应用层先stop再start,你的驱动必须能正确处理这种状态切换。我见过有人没做幂等处理,连续两次s_stream(1)导致Sensor死锁。

2.4 本章知识结构图

下面这张图把本章的核心知识点串起来了。你可以看到,从物理层的I2C/SPI,到Sensor内部的寄存器模型,再到Linux内核的V4L2框架,是一条完整的链路。

Sensor驱动核心概念结构图 Sensor驱动核心概念结构图 物理层通信 I2C / SPI 寄存器模型 芯片ID / 控制 / 状态 V4L2框架 subdev / ctrl / video I2C实现 地址 / ACK / 速率 START / STOP / 重复START 寄存器操作 初始化序列 曝光 / 增益 / 流控 V4L2回调 s_power / s_stream s_fmt / s_ctrl Sensor驱动完整实现 通信 + 寄存器 + V4L2 = 可用的摄像头设备 💡 学习路径建议: 先调通I2C/SPI通信(读芯片ID)→ 再写寄存器初始化序列 → 最后挂到V4L2框架里

2.5 本章小结

这一章我们覆盖了三个核心知识点:

  • I2C和SPI:两种最常用的Sensor通信协议,各有优劣。I2C线少但慢,SPI快但线多。
  • 寄存器模型:Sensor内部的控制中心,驱动开发就是跟寄存器打交道。初始化序列、曝光增益、流控,全在这里。
  • V4L2框架:Linux下视频设备的抽象层。Sensor驱动以subdev的形式注册进去,通过回调函数与内核交互。

个人建议:初学者别急着写完整的V4L2驱动。先写一个简单的I2C/SPI读写工具,把Sensor的寄存器摸透了,再套V4L2框架。这样出问题时,你能快速定位是通信问题、寄存器配置问题,还是框架对接问题。