第二章:摄像头硬件基础
做V4L2驱动开发,不懂硬件基础,那就是在黑暗中摸索。我见过太多工程师,上来就啃datasheet,结果连MIPI的lane数都搞不清楚,调了三天发现是硬件接错了。
这一章,咱们把摄像头模组拆开看看。从物理组成到接口协议,再到最底层的寄存器操作。嗯,这些都是基本功,但也是你后面打通数据流的基石。
2.1 摄像头模组组成
一个典型的摄像头模组,说白了就是几块东西拼在一起。我拆过不少模组,给大家画个结构图。
从图上你能看到,数据流是从左到右的:镜头采集光线,传感器转换成电信号,ISP做图像处理,最后通过MIPI接口送出去。而I2C总线,则是从上往下控制这些器件。
我当年第一次调摄像头驱动,就是没搞懂这个数据流向。以为直接操作寄存器就能出图,结果折腾了两天,发现是ISP的配置没跟上。嗯,这个坑我替你们踩过了。
2.2 MIPI CSI接口
MIPI CSI,全称是Camera Serial Interface。说白了,就是摄像头和主控芯片之间的高速公路。
它有几个关键参数,你必须要懂:
| 参数 | 说明 | 常见值 |
|---|---|---|
| Lane数 | 数据通道数量,越多带宽越大 | 1/2/4 Lane |
| 每Lane速率 | 单通道数据传输速率 | 400Mbps - 2.5Gbps |
| 时钟模式 | DDR双沿采样 | Continuous/Non-Continuous |
| 数据类型 | RAW8/RAW10/RAW12/YUV等 | RAW10最常用 |
我的经验:选Lane数时,别光看理论带宽。我遇到过4 Lane跑2.5Gbps,结果PCB走线太长,信号质量不行。后来降到2 Lane,反而稳定了。硬件设计时,Lane数够用就行,别盲目追求高配。
MIPI CSI的物理层,用的是差分信号对。一对时钟线,若干对数据线。每对线叫一个Lane。你想想看,高速信号为什么要用差分?抗干扰能力强啊。
协议层上,MIPI CSI用包传输。每个包有包头、数据、包尾。包头里包含了数据类型、虚拟通道号等信息。驱动里解析这些包,就能拿到完整的图像帧。
2.3 I2C控制总线
I2C总线,是摄像头模组的控制神经。Sensor的寄存器读写、ISP的参数配置,全走这条线。
I2C只有两根线:SCL(时钟)和SDA(数据)。但别小看它,协议挺讲究的。
标准I2C时序是这样的:
// I2C写操作时序
START -> 设备地址(7bit) + 写位(0) -> ACK ->
寄存器地址(8bit) -> ACK ->
数据(8bit) -> ACK -> STOP
// I2C读操作时序
START -> 设备地址(7bit) + 写位(0) -> ACK ->
寄存器地址(8bit) -> ACK ->
RESTART -> 设备地址(7bit) + 读位(1) -> ACK ->
读取数据(8bit) -> NACK -> STOP
实际项目中,I2C速率一般跑100KHz或400KHz。有些Sensor支持1MHz,但我建议别轻易用。为什么?因为走线长了,高速I2C容易出错。我曾经在量产时发现,100台设备里有3台I2C通信失败,查了两天才发现是PCB走线过长导致信号反射。
注意:I2C地址别搞错!很多Sensor有多个地址可选,通过引脚电平配置。我见过有人把0x30写成了0x60,结果读寄存器全是0xFF,折腾了一天才发现是地址左移了一位。嗯,I2C地址是7位,但Linux驱动里用的是8位地址(左移1位),这个细节很容易忽略。
2.4 Sensor寄存器操作基础
Sensor的寄存器,说白了就是一堆开关和旋钮。你通过I2C读写这些寄存器,就能控制Sensor的行为。
常见的寄存器类型:
- 控制寄存器:启动/停止、复位、模式选择
- 配置寄存器:分辨率、帧率、增益、曝光时间
- 状态寄存器:帧同步、错误标志、温度
- 数据寄存器:一些Sensor会把ID信息放在这里
我给大家看一个实际例子。这是OV5640的初始化代码片段:
// OV5640 初始化序列示例
static struct regval_list ov5640_init_regs[] = {
{0x3103, 0x11}, // 系统时钟配置
{0x3008, 0x82}, // 软件复位
{0x3008, 0x42}, // 退出复位
{0x3808, 0x0A}, // 输出高度高字节
{0x3809, 0x20}, // 输出高度低字节 (2592)
{0x380A, 0x07}, // 输出宽度高字节
{0x380B, 0x98}, // 输出宽度低字节 (1944)
{0x3035, 0x11}, // PLL配置
{0x3036, 0x46}, // PLL倍频
{0x3037, 0x12}, // PLL分频
{0x3C07, 0x08}, // 帧率控制
{0x3820, 0x41}, // 镜像翻转
{0x3821, 0x01}, // 垂直翻转
{0xFFFF, 0xFF}, // 结束标记
};
写驱动时,我习惯把寄存器配置做成表。这样方便调试,也方便后续修改。你想想看,如果每个寄存器都硬编码在代码里,换一个Sensor型号就得重写一遍,多麻烦。
调试技巧:我建议你在驱动里加一个寄存器dump函数。遇到问题时,先读一遍关键寄存器的值,跟datasheet对比。很多时候,问题就出在某个寄存器没写进去。我曾经靠这个技巧,半小时就定位了一个ISP配置错误。
寄存器操作有几个坑要注意:
- 寄存器地址长度:有的Sensor用8位地址,有的用16位。搞错了读出来的数据全是错的。
- 字节序:多字节寄存器,是高字节在前还是低字节在前?这个要看datasheet。
- 保留位:写寄存器时,保留位一定要保持原值。我见过有人把保留位清零了,Sensor直接罢工。
- 时序要求:有些寄存器写完后需要等待一段时间才能生效。比如软复位后,要等几毫秒才能继续配置。
嗯,这一章的内容就这些。硬件基础打牢了,后面讲V4L2驱动框架时,你才能理解为什么驱动要那样写。记住一句话:驱动是硬件的影子,不懂硬件,驱动永远写不透。
核心要点回顾:
- 摄像头模组 = 镜头 + Sensor + ISP + MIPI接口
- MIPI CSI用差分信号传输,Lane数决定带宽
- I2C是控制总线,注意地址和时序
- 寄存器操作要细心,保留位不能乱改