4. MIPI CSI-2与Sensor驱动实战

好,咱们进入第四章。这一章是硬骨头,也是真正上手干活的地方。前面几章讲了Camera的整体架构、Android HAL层的设计思路,但说一千道一万,最终你得让Sensor出图。而Sensor和主控之间的桥梁,就是MIPI CSI-2。

我个人习惯把这一章叫做「打通物理层」。因为不管你上层算法多牛,HAL层设计得多巧妙,如果MIPI物理层没配好,出来的图像就是花的、黑的、或者干脆没信号。我当年第一次调OV5640时,就吃过这个亏——上电时序搞反了,折腾了两天才发现是reset引脚拉早了。

4.1 MIPI联盟与CSI-2协议规范

MIPI联盟制定了一整套移动设备内部接口标准。CSI-2(Camera Serial Interface 2)是其中最核心的摄像头接口协议。说白了,它定义了Sensor怎么把图像数据打包,然后通过差分信号线传给主控。

4.1.1 D-PHY vs C-PHY

目前主流还是D-PHY,C-PHY相对新一些。我列个对比表,你一看就明白:

参数 D-PHY C-PHY
信号线 差分对(Dp/Dn) 三线制(A/B/C)
每lane速率 最高4.5 Gbps 最高约5.7 Gbps(等效)
时钟线 独立时钟lane 时钟嵌入数据
功耗 较低 略高
应用场景 绝大多数手机、平板 高端旗舰、高帧率需求

嗯,这里要注意:C-PHY虽然速率高,但调试起来比D-PHY麻烦。我建议初学者先从D-PHY入手,把基础打牢。

4.1.2 Lane数与带宽计算

Lane数决定了带宽。公式很简单:

总带宽 = 每lane速率 × lane数 × (8/10)  // 8b/10b编码开销

举个例子:OV5640输出1080p@30fps,每个像素10bit,大概需要:

1920 × 1080 × 30 × 10 = 622 Mbps
加上blanking开销,约800 Mbps
用2-lane D-PHY,每lane 500 Mbps,总带宽1 Gbps,够用

我在项目中遇到过有人非要用4-lane接一个VGA的Sensor,纯属浪费。选lane数时,留20%-30%余量就够了。

4.1.3 Data Type与帧格式

CSI-2用Data Type(DT)来标识数据类型。常见的:

  • 0x2B:RAW8
  • 0x2C:RAW10
  • 0x2D:RAW12
  • 0x1E:YUV420 8bit
  • 0x1F:YUV420 10bit

每个帧由以下部分组成:

  1. 帧起始包(SoF, DT=0x00)
  2. 行数据包(每行一个,包含行号)
  3. 帧结束包(EoF, DT=0x01)

我曾经调试一个Sensor,发现图像下半部分是花的。用逻辑分析仪抓包一看,原来是EoF包提前发送了,导致主控认为帧提前结束。最后发现是Sensor的VTS(垂直同步)寄存器配错了。

4.2 Sensor上电时序与初始化序列

这是新手最容易翻车的地方。Sensor的上电时序,说白了就是「先给谁供电、等多久、再拉哪个引脚」。每个Sensor的datasheet里都有时序图,必须严格遵循。

4.2.1 典型上电时序

以OV5640为例:

1. 拉低RESET和PWDN引脚
2. 给DVDD(数字核心)供电:1.5V
3. 等待至少1ms
4. 给AVDD(模拟)供电:2.8V
5. 等待至少1ms
6. 给DOVDD(IO)供电:1.8V
7. 等待至少5ms
8. 拉高RESET,释放复位
9. 等待至少20ms
10. 通过I2C写入初始化序列
注意:不同Sensor的时序要求差异很大。有的Sensor要求RESET拉高后必须等100ms才能开始I2C通信。我曾经因为赶进度,把等待时间从20ms改到5ms,结果Sensor死活不响应I2C。查了一天才发现是这个问题。

4.2.2 I2C/SPI通信

绝大多数Sensor用I2C做控制通道。OV5640的I2C地址是0x3C(写)/0x3D(读)。初始化序列通常是一串寄存器地址+值的列表:

// OV5640初始化片段
static struct regval_list ov5640_init_regs[] = {
    {0x3103, 0x11},  // 系统时钟配置
    {0x3008, 0x82},  // 软件复位
    {0x3008, 0x42},  // 退出复位
    {0x3103, 0x03},  // 时钟分频
    {0x3034, 0x1A},  // PLL配置
    {0x3035, 0x21},  // PLL倍频
    // ... 省略约200行
};

嗯,这里有个技巧:很多Sensor的datasheet会提供「推荐初始化序列」,但那是针对他们自己的测试板。你实际用的时候,要根据自己的主控和时钟频率微调PLL配置。我一般会先读回Sensor的ID寄存器,确认I2C通信正常,再开始写初始化序列。

4.3 Sensor寄存器配置实战

这部分是核心中的核心。你配好了上电时序,Sensor能出图了,但图像质量好不好、帧率对不对、HDR能不能开,全看寄存器配置。

4.3.1 曝光与增益

曝光控制分两种:

  • 行曝光(Line-based):通过调整曝光行数来控制
  • 帧曝光(Frame-based):整帧同时曝光,常见于全局快门Sensor

OV5640是卷帘快门,用行曝光:

// 设置曝光行数
reg_write(0x3500, (exp >> 16) & 0x0F);  // 高4位
reg_write(0x3501, (exp >> 8) & 0xFF);   // 中8位
reg_write(0x3502, exp & 0xFF);          // 低8位

// 设置增益
reg_write(0x350A, gain >> 8);           // 高8位
reg_write(0x350B, gain & 0xFF);         // 低8位
经验之谈:增益不要超过64倍(模拟增益),否则噪声会爆炸。我一般把模拟增益上限设在16倍,再往上就用数字增益,但数字增益会损失动态范围。

4.3.2 帧率控制

帧率由VTS(垂直同步)和HTS(水平同步)决定:

帧率 = 主时钟频率 / (HTS × VTS × 每行时钟数)

// OV5640 1080p@30fps典型配置
reg_write(0x380C, 0x0A);  // HTS高8位
reg_write(0x380D, 0x50);  // HTS低8位  -> 0xA50 = 2640
reg_write(0x380E, 0x07);  // VTS高8位
reg_write(0x380F, 0xD0);  // VTS低8位  -> 0x7D0 = 2000

我遇到过一个问题:配好帧率后,实际测出来只有25fps。查了半天,发现是主控的MIPI接收端时钟没配对,导致数据流阻塞。所以帧率问题不一定是Sensor的问题,也可能是接收端。

4.3.3 HDR模式

OV5640支持多种HDR模式,最常用的是行交错HDR:

// 开启HDR模式
reg_write(0x3A00, 0x01);  // 使能HDR
reg_write(0x3A02, 0x10);  // 短曝光行数
reg_write(0x3A03, 0xE0);  // 长曝光行数
reg_write(0x3A08, 0x08);  // HDR增益比例

HDR模式下,Sensor会交替输出长曝光和短曝光行,主控端需要做融合处理。嗯,这里要注意:HDR开启后,帧率会下降一半,因为每帧实际上包含了两帧的数据。

4.4 MIPI CSI-2接收端配置

Sensor端配好了,主控端也得配。接收端配置主要在SoC的ISP或Camera控制器里。

4.4.1 D-PHY参数配置

主要配以下几个参数:

  • HS时钟频率:与Sensor输出匹配
  • HS-TX准备时间:通常设为2-4个UI
  • HS-TX退出时间:通常设为4-6个UI
  • CLK-POST时间:时钟后稳定时间
// 以某款SoC为例
mipi_cfg.hs_clk_hz = 500000000;  // 500 MHz
mipi_cfg.lane_num = 2;
mipi_cfg.hs_prepare = 3;         // 3个UI
mipi_cfg.hs_exit = 5;           // 5个UI
mipi_cfg.clk_post = 8;          // 8个UI
避坑指南:我曾经把HS-TX准备时间设得太短,导致数据眼图闭合,图像出现随机噪点。用示波器一看,信号建立时间不够。后来把prepare从2改成4,问题解决。

4.4.2 LP/HS模式切换

MIPI总线有两种模式:

  • LP(Low Power):低功耗模式,用于控制信号
  • HS(High Speed):高速模式,用于传输数据

切换时序必须精确:

// LP -> HS 切换流程
1. 所有lane进入LP-11状态
2. 时钟lane进入HS准备
3. 数据lane进入HS准备
4. 发送SoF包
5. 开始传输数据

// HS -> LP 切换流程
1. 发送EoF包
2. 数据lane进入LP-11
3. 时钟lane进入LP-11

我见过一个案例:某工程师把LP-11到HS的切换时间设错了,导致第一个数据包总是丢失。最后发现是SoC的MIPI控制器里有个「LP到HS延迟」寄存器,默认值是0,需要根据Sensor的datasheet改成5us。

4.5 实战:编写OV5640 Sensor驱动

好,理论讲完了,咱们动手写一个简化版的OV5640驱动。我会把核心函数列出来,完整的驱动代码在课程资料里。

4.5.1 驱动框架

struct ov5640_sensor {
    struct i2c_client *client;
    struct v4l2_subdev subdev;
    struct mutex lock;
    int power_count;
    int streaming;
    struct ov5640_mode *cur_mode;
};

// 核心函数
static int ov5640_power_on(struct ov5640_sensor *sensor);
static int ov5640_power_off(struct ov5640_sensor *sensor);
static int ov5640_init_sensor(struct ov5640_sensor *sensor);
static int ov5640_start_stream(struct ov5640_sensor *sensor);
static int ov5640_stop_stream(struct ov5640_sensor *sensor);

4.5.2 上电与初始化

static int ov5640_power_on(struct ov5640_sensor *sensor)
{
    // 1. 拉低RESET
    gpio_set_value(sensor->reset_gpio, 0);
    // 2. 供电
    regulator_enable(sensor->dvdd);
    usleep_range(1000, 2000);
    regulator_enable(sensor->avdd);
    usleep_range(1000, 2000);
    regulator_enable(sensor->dovdd);
    usleep_range(5000, 6000);
    // 3. 释放复位
    gpio_set_value(sensor->reset_gpio, 1);
    usleep_range(20000, 25000);
    return 0;
}

static int ov5640_init_sensor(struct ov5640_sensor *sensor)
{
    int ret;
    // 读取ID确认通信
    ret = ov5640_read_reg(sensor, 0x300A);
    if (ret != 0x56) {
        dev_err(&sensor->client->dev, "Sensor ID mismatch\n");
        return -ENODEV;
    }
    // 写入初始化序列
    return ov5640_write_regs(sensor, ov5640_init_regs);
}

4.5.3 流开关控制

static int ov5640_start_stream(struct ov5640_sensor *sensor)
{
    // 配置MIPI
    ov5640_write_reg(sensor, 0x3008, 0x02);  // 使能MIPI输出
    usleep_range(10000, 15000);
    // 开始传输
    ov5640_write_reg(sensor, 0x3008, 0x42);  // 开始流
    return 0;
}

static int ov5640_stop_stream(struct ov5640_sensor *sensor)
{
    ov5640_write_reg(sensor, 0x3008, 0x82);  // 停止流
    usleep_range(10000, 15000);
    ov5640_write_reg(sensor, 0x3008, 0x42);  // 进入待机
    return 0;
}

核心要点总结:

  • MIPI CSI-2是Sensor和主控之间的数据通道,D-PHY是主流
  • 上电时序必须严格遵循datasheet,差1ms都可能出问题
  • 寄存器配置要理解曝光、增益、帧率的计算公式
  • 接收端配置要和Sensor端匹配,特别是时钟和时序参数
  • 调试时先用示波器看MIPI信号,再用逻辑分析仪抓I2C

嗯,这一章内容不少。你如果能把OV5640的驱动调通,那市面上绝大多数Sensor对你来说都不会太难。记住:调试Sensor驱动,80%的时间花在上电时序和MIPI配置上,剩下20%才是图像质量调优。别问我怎么知道的——当年我为了调一个GC8034的驱动,连续熬了三个通宵,最后发现是上电时序里少了一个1ms的延时。

下一章我们会讲V4L2框架,那是Linux下Camera驱动的标准接口。到时候你会看到,我们写的这个Sensor驱动,怎么挂到V4L2的框架里,让上层应用能通过标准的open/ioctl来操作摄像头。


公众号:蓝海资料掘金营,微信deep3321