4. Sensor驱动核心配置

好,咱们进入第四章节。前面几章我们把Sensor驱动的基础框架和I2C通信搭好了,现在要动真格的了——配置Sensor的核心参数。

说白了,Sensor驱动最核心的工作就三件事:让Sensor输出你想要的图像尺寸和帧率、让Sensor能正确曝光和增益、让Sensor工作在HDR模式。这三件事搞不定,后面的3A算法全是空中楼阁。

我个人习惯把这三块称为Sensor驱动的“三驾马车”。咱们一个一个来拆。

4.1 分辨率切换与帧率控制

先说说分辨率切换。你想想看,一个Sensor通常支持多种输出尺寸:比如400万像素的Sensor,可能支持2688x1520、1920x1080、1280x720等等。每次切换分辨率,本质上是在修改Sensor内部的行有效像素数和帧有效行数。

瑞芯平台的做法是,在驱动里维护一个struct sensor_mode数组,每个元素对应一种分辨率模式。我见过不少新手直接把所有寄存器值写死在代码里,结果换一个Sensor型号就得重写一遍——这其实是不对的。

核心思路:分辨率切换的本质是修改Sensor的PLL配置、水平消隐(HBlank)和垂直消隐(VBlank)参数。帧率控制则通过调整VBlank来实现。

来看一个典型的配置结构:

// sensor_drv.c
static struct sensor_mode g_sensor_modes[] = {
    {
        .width = 2688,
        .height = 1520,
        .fps = 30,
        .reg_list = {
            {0x0100, 0x00}, // 先关流
            {0x0300, 0x04}, // PLL配置
            {0x0301, 0x00},
            {0x0302, 0x2C},
            {0x0303, 0x01},
            {0x0340, 0x05}, // 帧长 = 0x05DC = 1500
            {0x0341, 0xDC},
            {0x0342, 0x0B}, // 行长 = 0x0BB8 = 3000
            {0x0343, 0xB8},
            {0x0100, 0x01}, // 开流
            {REG_NULL, 0x00},
        },
    },
    // ... 其他模式
};

这里有个坑,我踩过好几次:切换分辨率前必须先关流(写0x0100=0x00),等所有寄存器配置完成后再开流。有些Sensor不关流也能切,但图像会花掉几帧——这在安防场景下是不能接受的。

注意:帧率计算公式:FPS = PCLK / (HTS × VTS)。其中HTS是行长(包括消隐),VTS是帧长(包括消隐)。调整VBlank(即VTS - 有效行数)是控制帧率最常用的手段。

帧率控制这块,我个人习惯在驱动里提供一个接口,允许上层动态调整帧率。比如从30fps降到25fps,只需要增加VBlank的值。但要注意:VBlank不能无限大,否则帧率太低会导致图像闪烁。

4.2 曝光与增益寄存器映射

接下来是曝光和增益。这是3A算法和Sensor驱动之间的“翻译官”。

3A算法算出来的曝光时间单位是微秒(us),增益是倍率(1x, 2x, 4x...)。但Sensor寄存器只认自己的值——比如曝光寄存器存的是行数,增益寄存器存的是模拟增益的code值。

所以驱动要做一层映射。我把它分成三步:

  1. 时间转行数:曝光行数 = 曝光时间(us) / 一行时间(us)。一行时间 = 1 / (PCLK / HTS)。
  2. 增益转code:不同Sensor的增益映射表不同,有的线性,有的分段。需要查datasheet。
  3. 写寄存器:把计算出的行数和code值写入对应的寄存器地址。

来看一个实际的映射函数:

// sensor_exposure.c
int sensor_set_exposure(struct sensor_dev *dev, uint32_t exp_us, uint32_t gain_x100)
{
    uint16_t line_length = dev->cur_mode->line_length; // 从当前模式获取
    uint32_t exp_lines;
    uint16_t gain_code;

    // 1. 曝光时间转行数
    exp_lines = exp_us * 1000 / (line_length * 1000 / dev->pclk_khz);
    // 简化后:exp_lines = exp_us * dev->pclk_khz / (line_length * 1000);
    // 注意:exp_lines 不能超过 VTS - 4(留余量)

    // 2. 增益转code(假设线性映射)
    // gain_x100 = 100 表示1x, 200表示2x
    if (gain_x100 <= 100) {
        gain_code = 0; // 最小增益
    } else if (gain_x100 <= 200) {
        gain_code = (gain_x100 - 100) * 16 / 100; // 1x~2x映射到0~16
    } else {
        gain_code = 16 + (gain_x100 - 200) * 8 / 100; // 2x以上斜率变缓
    }

    // 3. 写寄存器
    sensor_write_reg(dev, 0x0202, (exp_lines >> 8) & 0xFF); // 曝光高字节
    sensor_write_reg(dev, 0x0203, exp_lines & 0xFF);        // 曝光低字节
    sensor_write_reg(dev, 0x0204, (gain_code >> 8) & 0xFF); // 增益高字节
    sensor_write_reg(dev, 0x0205, gain_code & 0xFF);        // 增益低字节

    return 0;
}
经验之谈:我曾经在一个项目里发现曝光值写进去后图像亮度没变化,查了两天才发现是寄存器地址搞反了——高字节和低字节写反了。所以建议每次写完寄存器后,读回来校验一下。

增益映射这块,不同厂家差异很大。OV的Sensor喜欢用分段线性,SONY的Sensor很多用dB为单位。我建议在驱动初始化时,从Sensor的OTP里读取增益校准表,而不是硬编码。

4.3 HDR模式配置实战

HDR模式,说白了就是让Sensor同时输出亮部和暗部的细节。瑞芯微平台支持多种HDR方案:DOL( Digital Overlap)、Staggered HDR、Line Interleaved等。

我个人最常用的是DOL HDR,因为它对运动物体友好,不会出现鬼影。配置DOL HDR的核心是设置长短帧的曝光时间和增益。

来看一个DOL HDR的配置流程:

步骤 操作 寄存器示例 说明
1 使能HDR模式 0x0101 = 0x02 切换到HDR工作模式
2 配置长帧曝光 0x0202~0x0203 长帧曝光行数(通常为短帧的4~16倍)
3 配置短帧曝光 0x0222~0x0223 短帧曝光行数
4 配置长帧增益 0x0204~0x0205 长帧模拟增益
5 配置短帧增益 0x0224~0x0225 短帧模拟增益
6 配置HDR合成比例 0x0400~0x0401 长短帧融合权重

嗯,这里要注意:HDR模式下帧率会下降。因为Sensor需要输出两帧(长帧+短帧)才能合成一帧HDR图像。比如你配置30fps的HDR,Sensor实际内部帧率是60fps。

避坑指南:我曾经在一个项目中,HDR模式下图像出现条纹。查了半天发现是长短帧的VBlank不一致导致的。长帧和短帧的帧长必须相同,否则ISP无法正确对齐两帧数据。

另外,HDR模式的增益配置有个技巧:长帧增益尽量小,短帧增益尽量大。因为长帧曝光时间长,本身信号就强,再加增益容易饱和。短帧曝光时间短,信号弱,需要高增益来提亮暗部。

最后,我建议在驱动里提供一个HDR参数调试接口,允许在运行时动态调整长短帧的曝光比和增益比。这样3A算法可以根据场景亮度自适应调节HDR强度。

Sensor驱动核心配置知识体系 分辨率与帧率 曝光与增益映射 HDR模式配置 PLL配置 HBlank / VBlank 模式切换流程 时间→行数转换 增益→Code映射 寄存器写入校验 DOL / Staggered 长短帧曝光配置 融合权重设置 核心原则 • 分辨率切换前必须先关流,配置完成后再开流 • 曝光和增益映射必须与Sensor datasheet严格对齐 • HDR模式下长帧增益尽量小,短帧增益尽量大

好了,这一章的内容就这些。分辨率切换、曝光增益映射、HDR配置,这三块是Sensor驱动的核心。你把这些搞透了,后面3A算法调优就是水到渠成的事。