v4l2驱动框架——ctrl_handler

文章目录

ctrl_handler

  1. V4L2控制框架(Control Framework)是Linux内核中为视频设备提供统一参数管理机制的子系统。它通过v4l2_ctrl_handler管理所有设备控制项(如曝光、增益、白平衡等),实现了用户空间与驱动之间的标准化参数交互接口。
  2. 每个控制项代表一个可调节参数,包含以下关键属性:
    • id:控制项标识符(如V4L2_CID_EXPOSURE)
    • type:数据类型(整数、布尔、菜单等)
    • flags:属性标志(只读、易失、可执行等)
    • minimum/maximum:取值范围
    • step:步进值
    • default_value:默认值
    • val/val64:当前值存储

ov13850的handler代码

c 复制代码
// 函数功能:初始化OV13850传感器的V4L2控制项(曝光、增益、消隐等)
// 参数:ov13850 - 指向OV13850传感器私有数据结构的指针
// 返回值:成功返回0,失败返回错误码
static int ov13850_initialize_controls(struct ov13850 *ov13850)
{
    // 定义局部变量:
    const struct ov13850_mode *mode;          // 指向当前分辨率模式的指针
    struct v4l2_ctrl_handler *handler;        // V4L2控制处理器(管理所有控制项)
    struct v4l2_ctrl *ctrl;                   // 单个控制项指针
    s64 exposure_max, vblank_def;             // 最大曝光时间,默认垂直消隐值(64位有符号整数)
    u32 h_blank;                              // 水平消隐值(32位无符号整数)
    int ret;                                  // 函数返回值(错误码)

    // 第1行:获取控制处理器指针(存储在ov13850结构体中)
    handler = &ov13850->ctrl_handler;
    
    // 第2行:获取当前传感器工作模式(分辨率、帧率等配置)
    mode = ov13850->cur_mode;
    
    // 第3-5行:初始化V4L2控制处理器,预分配8个控制项的空间
    // v4l2_ctrl_handler_init初始化控制处理器,第二个参数8表示预期会有8个控制项
    ret = v4l2_ctrl_handler_init(handler, 8);
    if (ret)  // 如果初始化失败
        return ret;  // 返回错误码
    
    // 第6行:设置控制处理器的互斥锁,使用传感器自己的mutex保证线程安全
    // 当用户空间同时修改多个控制项时,用这个锁防止竞争条件
    handler->lock = &ov13850->mutex;

    // 第7-10行:创建"MIPI链接频率"控制项(只读)
    // v4l2_ctrl_new_int_menu创建整数菜单类型控制项:
    // - V4L2_CID_LINK_FREQ: 控制项ID,表示MIPI接口的数据传输频率
    // - 0, 0: 最小索引和最大索引都是0,表示只有1个菜单项
    // - link_freq_menu_items: 菜单项数组,如{891000000}表示891MHz
    ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
                                  0, 0, link_freq_menu_items);
    if (ctrl)  // 如果控制项创建成功
        ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;  // 设置为只读,用户不能修改

    // 第11-12行:创建"像素率"控制项(只读)
    // v4l2_ctrl_new_std创建标准控制项:
    // - V4L2_CID_PIXEL_RATE: 控制项ID,表示每秒输出的像素数
    // - 0: 最小值(通常为0)
    // - OV13850_PIXEL_RATE: 最大值(如4208*3120*30≈394M像素/秒)
    // - 1: 步进值(每次调整的变化量)
    // - OV13850_PIXEL_RATE: 默认值(与最大值相同,固定值)
    v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE,
                      0, OV13850_PIXEL_RATE, 1, OV13850_PIXEL_RATE);

    // 第13行:计算水平消隐值
    // hts_def: 水平总时间(包括有效像素和消隐期)
    // width: 有效像素宽度
    // h_blank = 总时间 - 有效时间 = 消隐时间
    h_blank = mode->hts_def - mode->width;
    
    // 第14-17行:创建"水平消隐"控制项(只读)
    // V4L2_CID_HBLANK: 水平消隐控制项
    // 所有参数(最小值、最大值、默认值)都设为h_blank,表示这个值固定不可调
    ov13850->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
                h_blank, h_blank, 1, h_blank);
    if (ov13850->hblank)  // 如果控制项创建成功
        ov13850->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;  // 设置为只读

    // 第18行:计算默认垂直消隐值
    // vts_def: 垂直总行数(包括有效行和消隐行)
    // height: 有效图像高度(行数)
    // vblank_def = 总行数 - 有效行数 = 消隐行数
    vblank_def = mode->vts_def - mode->height;
    
    // 第19-23行:创建"垂直消隐"控制项(可调)
    // V4L2_CID_VBLANK: 垂直消隐控制项
    // &ov13850_ctrl_ops: 控制项操作集,当用户修改时会调用ov13850_set_ctrl()
    // vblank_def: 最小值(当前模式的默认消隐)
    // OV13850_VTS_MAX - mode->height: 最大值(传感器支持的最大总行数减去有效行数)
    // 1: 步进值(每次调整1行)
    // vblank_def: 默认值(当前模式的消隐值)
    ov13850->vblank = v4l2_ctrl_new_std(handler, &ov13850_ctrl_ops,
                V4L2_CID_VBLANK, vblank_def,
                OV13850_VTS_MAX - mode->height,
                1, vblank_def);

    // 第24行:计算最大曝光时间
    // 最大曝光时间 = 垂直总行数 - 4(留出4行余量,防止曝光时间过长影响下一帧)
    exposure_max = mode->vts_def - 4;
    
    // 第25-29行:创建"曝光时间"控制项(可调)
    // V4L2_CID_EXPOSURE: 曝光时间控制项(单位:行时间)
    // OV13850_EXPOSURE_MIN: 最小值(如1行时间)
    // exposure_max: 最大值(计算得出)
    // OV13850_EXPOSURE_STEP: 步进值(如1行)
    // mode->exp_def: 默认值(当前模式的默认曝光)
    ov13850->exposure = v4l2_ctrl_new_std(handler, &ov13850_ctrl_ops,
                V4L2_CID_EXPOSURE, OV13850_EXPOSURE_MIN,
                exposure_max, OV13850_EXPOSURE_STEP,
                mode->exp_def);

    // 第30-34行:创建"模拟增益"控制项(可调)
    // V4L2_CID_ANALOGUE_GAIN: 模拟增益控制项
    // OV13850_GAIN_MIN: 最小值(如1倍,对应寄存器值0x00)
    // OV13850_GAIN_MAX: 最大值(如16倍,对应寄存器值0xFF)
    // OV13850_GAIN_STEP: 步进值(如寄存器值增加1)
    // OV13850_GAIN_DEFAULT: 默认增益值
    ov13850->anal_gain = v4l2_ctrl_new_std(handler, &ov13850_ctrl_ops,
                V4L2_CID_ANALOGUE_GAIN, OV13850_GAIN_MIN,
                OV13850_GAIN_MAX, OV13850_GAIN_STEP,
                OV13850_GAIN_DEFAULT);

    // 第35-39行:创建"测试图案"控制项(菜单类型,可调)
    // V4L2_CID_TEST_PATTERN: 测试图案控制项
    // ARRAY_SIZE(ov13850_test_pattern_menu) - 1: 最大索引值(菜单项数-1)
    // 0: 最小索引值
    // 0: 默认索引值
    // ov13850_test_pattern_menu: 菜单字符串数组,如{"Disabled", "Color Bars", ...}
    ov13850->test_pattern = v4l2_ctrl_new_std_menu_items(handler,
                &ov13850_ctrl_ops, V4L2_CID_TEST_PATTERN,
                ARRAY_SIZE(ov13850_test_pattern_menu) - 1,
                0, 0, ov13850_test_pattern_menu);

    // 第40-45行:检查控制处理器是否有错误
    if (handler->error) {
        ret = handler->error;  // 获取错误码
        dev_err(&ov13850->client->dev,  // 打印错误信息到内核日志
            "Failed to init controls(%d)\n", ret);
        goto err_free_handler;  // 跳转到错误处理
    }

    // 第46行:将控制处理器关联到V4L2子设备
    // 这样用户空间就可以通过子设备节点访问这些控制项
    ov13850->subdev.ctrl_handler = handler;

    // 第47行:成功返回0
    return 0;

// 第49-53行:错误处理标签
err_free_handler:
    // 释放控制处理器及其所有控制项
    v4l2_ctrl_handler_free(handler);

    // 第52行:返回错误码
    return ret;
}
//vm149c的对焦
static int vm149c_init_controls(struct vm149c_device *dev_vcm)
{
	struct v4l2_ctrl_handler *hdl = &dev_vcm->ctrls_vcm;
	const struct v4l2_ctrl_ops *ops = &vm149c_vcm_ctrl_ops;

	v4l2_ctrl_handler_init(hdl, 1);

	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
			  0, VCMDRV_MAX_LOG, 1, VCMDRV_MAX_LOG);

	if (hdl->error)
		dev_err(dev_vcm->sd.dev, "%s fail error: 0x%x\n",
			__func__, hdl->error);
	dev_vcm->sd.ctrl_handler = hdl;
	return hdl->error;
}

应用层使用对焦例子

  1. 命令行,对焦手动

    sh 复制代码
    v4l2-ctl -d /dev/v4l-subdev3 --set-ctrl focus_absolute=32
    这里为什么是3,是因为子设备注册时候的顺序是csi2,phy,ov13850,vm149c(从0开始这个刚好就是3)
  2. subdev设备文件

    c 复制代码
    int set_focus_absolute(int fd, int value) {
        struct v4l2_control ctrl;
        
        ctrl.id = V4L2_CID_FOCUS_ABSOLUTE;
        ctrl.value = value;
        
        if (ioctl(fd, VIDIOC_S_CTRL, &ctrl) < 0) {
            perror("set_focus_absolute failed");
            return -1;
        }
        
        printf("Focus set to %d\n", value);
        return 0;
    }
    
    int main() {
        // 打开子设备节点(通常是 /dev/v4l-subdevX 或 /dev/v4l2-sensor)
        // 打开VM149C设备节点(需要先确认正确的节点)
        int fd = open("/dev/v4l-subdev3", O_RDWR);
        if (fd < 0) {
            perror("Failed to open VM149C");
            return -1;
        }
        
        // 设置焦距(范围0-VCMDRV_MAX_LOG)
        set_focus_absolute(fd, 0);
        
        close(fd);
        return 0;
    }