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。
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 写操作流程:
- 主控发送起始条件(SDA 在 SCL 高电平时拉低)。
- 发送 7 位设备地址 + 写位(0)。
- 等待 sensor 应答(ACK)。
- 发送寄存器地址(高字节 + 低字节)。
- 发送要写入的数据。
- 发送停止条件(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 通后再切到正常模式。