2. 传感器接口与驱动:MIPI CSI-2协议详解、I2C/SPI控制接口配置、Sensor驱动初始化流程

好,我们进入第二章。这一章讲的是传感器怎么跟主控芯片“说话”。说白了,就是物理层怎么连,数据怎么传,控制指令怎么下。我见过不少工程师,算法调得飞起,结果图像出不来,最后发现是接口时序没配对。嗯,这种坑咱们得绕开。

2.1 MIPI CSI-2 协议详解

MIPI CSI-2 是目前最主流的摄像头串行接口。它把并行数据转成高速差分信号,用很少的线就能传高分辨率图像。我个人习惯把它分成三层来看:物理层、协议层、应用层。

核心优势: 差分信号抗干扰强,线束少,支持多通道(Lane)扩展,最高可到 4 Lane,每 Lane 速率从 400 Mbps 到 2.5 Gbps 甚至更高。

2.1.1 物理层(D-PHY)

D-PHY 是 CSI-2 的物理承载。它用一对差分线(DP/DN)构成一个 Lane。时钟 Lane 独立,数据 Lane 可以 1-4 条。我调试时最常遇到的问题是:PCB 走线不等长,导致 Lane 之间 skew 超标,图像出现条纹。

  • HS(高速)模式: 传数据时用,电压摆幅小(约 200mV),速率高。
  • LP(低功耗)模式: 传控制信号或空闲时用,电压摆幅大(1.2V),速率低。
  • ULPS(超低功耗模式): 省电用的,进入后几乎不耗电。

避坑指南: 我曾经遇到过一块板子,MIPI 信号眼图看着还行,但就是偶尔丢帧。查了三天,发现是 D-PHY 的 LP 模式退出时序没配好,导致 HS 进入时 PLL 没锁住。后来在初始化序列里加了 100us 的延迟,问题解决。

2.1.2 协议层与数据包结构

CSI-2 的数据以包为单位传输。每个包分为:

包类型 内容 说明
SoT(Start of Transmission) 同步序列 接收端用它来锁定时钟和数据对齐
PH(Packet Header) 数据类型 + 字计数 + ECC 告诉接收端这是 RAW8/RAW10/YUV 等格式,以及本包长度
Payload 图像数据 按像素排列,行有效数据
PF(Packet Footer) CRC 校验 可选,用于检测传输错误

你想想看,如果 PH 里的 ECC 校验失败,整行数据都会被丢弃。这就是为什么有时候图像会出现整行缺失——不是 sensor 没输出,是接收端认为数据不可靠。

2.1.3 虚拟通道(Virtual Channel)

CSI-2 支持最多 4 个虚拟通道(VC0~VC3)。这意味着一条 MIPI 总线可以同时传多路图像数据。比如双摄模组,一个 sensor 走 VC0,另一个走 VC1,共用同一组 Lane。我在做多摄项目时经常用这个特性,省了不少 GPIO。

MIPI CSI-2 协议分层与数据流 应用层(Application Layer) 像素格式转换、数据打包、帧/行控制 协议层(Protocol Layer) 包结构(PH + Payload + PF)、ECC/CRC、虚拟通道管理 物理层(D-PHY) 差分信号、HS/LP 模式、Lane 管理、时钟恢复 数据流向 Sensor → D-PHY → 协议解析 → 应用层输出 常见问题 Lane skew、ECC 错误、VC 冲突

2.2 I2C/SPI 控制接口配置

MIPI 是传数据的,那控制 sensor 用什么?答案是 I2C 或 SPI。这两个接口负责写寄存器,比如设置曝光时间、增益、帧率、输出格式等。

2.2.1 I2C 接口

I2C 是两线制:SCL(时钟)和 SDA(数据)。sensor 作为从设备,主控通过设备地址(7位或10位)来寻址。我常用的速率是 400 kHz(Fast Mode),有些 sensor 支持 1 MHz(Fast Mode Plus)。

注意: I2C 是开漏输出,必须上拉电阻。阻值选错了,信号上升沿会变慢,导致通信失败。我一般选 4.7kΩ,如果总线电容大,就降到 2.2kΩ。

典型的 I2C 写操作流程:

  1. 主控发送起始条件(SDA 在 SCL 高电平时拉低)。
  2. 发送 7 位设备地址 + 写位(0)。
  3. 等待 sensor 应答(ACK)。
  4. 发送寄存器地址(高字节 + 低字节)。
  5. 发送要写入的数据。
  6. 发送停止条件(SDA 在 SCL 高电平时释放)。
// I2C 写寄存器示例(伪代码)
int i2c_write_reg(uint8_t dev_addr, uint16_t reg_addr, uint8_t data) {
    i2c_start();
    i2c_send_byte(dev_addr << 1 | 0);  // 写操作
    if (!i2c_wait_ack()) return -1;
    i2c_send_byte((reg_addr >> 8) & 0xFF);  // 寄存器高字节
    if (!i2c_wait_ack()) return -1;
    i2c_send_byte(reg_addr & 0xFF);           // 寄存器低字节
    if (!i2c_wait_ack()) return -1;
    i2c_send_byte(data);
    if (!i2c_wait_ack()) return -1;
    i2c_stop();
    return 0;
}

2.2.2 SPI 接口

SPI 比 I2C 快,但线多:SCLK、MOSI、MISO、CS。sensor 一般用 SPI 做批量寄存器写入,或者传固件。我遇到过一个 sensor,它的 tuning 参数表有 2000 多个寄存器,用 I2C 写要好几秒,换成 SPI 不到 100ms 搞定。

特性 I2C SPI
线数 2(SCL + SDA) 4(SCLK + MOSI + MISO + CS)
速率 通常 400 kHz ~ 1 MHz 通常 10 MHz ~ 50 MHz
寻址方式 设备地址 + 寄存器地址 片选(CS)选择设备
多设备 总线共享,地址不冲突即可 每个设备独立 CS
适用场景 配置寄存器、读取状态 批量写入、固件下载

2.3 Sensor 驱动初始化流程

这部分是实战核心。一个 sensor 从上电到出图,中间有一整套流程。我把它总结为五个阶段:

初始化五步走: 上电复位 → 硬件检测 → 时钟配置 → 寄存器序列 → 输出使能。

2.3.1 上电与复位

先看 datasheet 里的上电时序。不同 sensor 对电源上电顺序有严格要求。比如 AVDD(模拟)要先于 DVDD(数字)上电,或者要求间隔 1ms。我吃过这个亏:有一次 AVDD 和 DVDD 同时上电,sensor 能工作但暗电流异常大,后来发现是上电顺序反了,内部 LDO 没建立好。

  • 硬件复位: 拉低 XSHUT 或 RESET 引脚,保持至少 1ms,再释放。
  • 软件复位: 写寄存器 0x0103(R/W)为 0x01,sensor 会复位到默认状态。

2.3.2 硬件检测与 ID 读取

复位后,第一件事是读 sensor 的 Chip ID 寄存器。这步是为了确认 I2C/SPI 通信正常,以及 sensor 型号正确。

// 读取 Sensor ID 示例
uint16_t sensor_id;
sensor_id = i2c_read_reg(0x20, 0x0000) << 8;
sensor_id |= i2c_read_reg(0x20, 0x0001);
if (sensor_id != 0x5640) {
    printf("Error: Sensor ID mismatch! Got 0x%04X\n", sensor_id);
    return -1;
}

个人经验: 我习惯在驱动里加一个“ID 重试机制”。因为刚上电时 sensor 可能还没准备好,读 ID 会失败。重试 3 次,每次间隔 10ms,成功率会高很多。

2.3.3 时钟配置

sensor 需要外部主时钟(MCLK)或内部 PLL。MCLK 频率通常是 6 MHz、12 MHz、24 MHz 或 27 MHz。然后通过 sensor 内部的 PLL 倍频到像素时钟。

举个例子:一个 1080p 30fps 的 sensor,像素时钟大约 74.25 MHz。如果 MCLK 是 24 MHz,PLL 倍频系数就是 74.25 / 24 ≈ 3.09。实际要配成整数比,比如 3 倍频到 72 MHz,然后调整行消隐来匹配帧率。

2.3.4 寄存器序列写入

这是最繁琐的部分。sensor 厂商会提供一份初始化序列,少则几十行,多则上千行。包括:

  • 模式设置: 分辨率、帧率、输出格式(RAW8/RAW10/YUV)。
  • 模拟增益: 默认增益值,通常设为 1x(0 dB)。
  • 黑电平校准: 使能 OB(Optical Black)钳位。
  • MIPI 配置: Lane 数、速率、VC 分配。
  • 测试模式: 输出彩条或棋盘格,用于调试。

注意: 写入寄存器序列时,有些寄存器需要按特定顺序写,或者写完后要等待几帧(Frame)才能生效。我曾经跳过了一个“等待帧同步”的步骤,结果图像一直花屏,后来发现是 sensor 内部状态机还没切换过来。

2.3.5 输出使能与验证

最后一步是写寄存器使能 MIPI 输出。通常是一个叫“Streaming ON”的寄存器,比如写 0x0100 为 0x01。之后 sensor 就开始通过 MIPI 发数据了。

验证方法:用示波器看 MIPI 时钟 Lane 有没有波形,或者用逻辑分析仪抓 I2C 看 sensor 有没有返回 NACK。我一般先让 sensor 输出测试图,确认 MIPI 通后再切到正常模式。

Sensor 驱动初始化流程 1. 上电与复位 2. 硬件检测(读 ID) 3. 时钟与 PLL 配置 4. 寄存器序列写入 5. 输出使能(Streaming ON) 关键检查点 • 电源时序 • ID 匹配 • PLL 锁定状态