第一章:Camera驱动基础:V4L2框架概述、Camera硬件接口与设备树配置

大家好,我是你们的嵌入式Linux驱动讲师。今天咱们开始第一节课,聊聊Camera驱动的基础。说实话,我做了这么多年驱动开发,Camera这块儿一直是比较有意思但也容易踩坑的地方。尤其是热插拔和动态检测,很多新手一上来就被搞懵了。

不过别急,咱们一步步来。这一章先把地基打牢——V4L2框架、硬件接口、设备树配置。这三样东西搞明白了,后面热插拔的实现才能顺风顺水。

1.1 V4L2框架:Camera驱动的灵魂

V4L2,全称Video for Linux 2。说白了,它就是Linux内核里给视频设备准备的一套标准接口。你想想看,如果没有V4L2,每个摄像头驱动都得自己定义一套API,那应用程序得疯掉——换个摄像头就得重写代码。

V4L2框架的核心思想就四个字:统一抽象。它把摄像头、电视卡、视频采集卡这些设备都抽象成“视频设备节点”,通常就是/dev/videoX这样的文件。应用程序通过open()ioctl()mmap()这些标准系统调用来操作它。

V4L2框架的核心组件:

  • 视频设备节点/dev/videoX,用户空间的操作入口
  • 视频采集:从摄像头抓取帧数据
  • 视频输出:向显示设备输出帧数据(咱们做Camera的用得少)
  • 控制接口:调节亮度、对比度、曝光等参数
  • 事件机制:检测热插拔、错误等异步事件——这个后面章节会重点讲

我记得刚接触V4L2时,最让我头疼的是它的ioctl命令太多了。光VIDIOC_QUERYCAPVIDIOC_ENUM_FMTVIDIOC_S_FMT这些基础命令就够记一阵子。但后来我发现,其实核心流程就那么几步:

// V4L2视频采集的标准流程(伪代码)
1. open("/dev/video0")          // 打开设备
2. ioctl(VIDIOC_QUERYCAP)       // 查询设备能力
3. ioctl(VIDIOC_S_FMT)          // 设置采集格式(分辨率、像素格式)
4. ioctl(VIDIOC_REQBUFS)        // 申请帧缓冲区
5. ioctl(VIDIOC_QBUF)           // 将缓冲区入队
6. ioctl(VIDIOC_STREAMON)       // 开始采集
7. while(1) {
     ioctl(VIDIOC_DQBUF)        // 取出已填满的缓冲区
     // 处理帧数据...
     ioctl(VIDIOC_QBUF)         // 将缓冲区重新入队
   }
8. ioctl(VIDIOC_STREAMOFF)      // 停止采集
9. close(fd)                    // 关闭设备

嗯,这里要注意:V4L2支持两种I/O模式——read/writestreaming。实际项目中99%都用streaming模式,因为效率高。read/write模式每次都要在内核和用户空间之间拷贝数据,性能太差了。

1.2 Camera硬件接口:MIPI、DVP、USB

搞Camera驱动,不懂硬件接口可不行。我见过不少软件工程师,代码写得飞起,结果连MIPI和DVP的差分信号都分不清,调试时抓瞎。咱们简单过一下三种主流接口。

1.2.1 MIPI CSI(最常用)

MIPI CSI是当前手机和嵌入式设备的主流选择。它使用差分信号传输,抗干扰能力强,速率高。一个MIPI CSI接口通常包含:

  • 时钟线:一对差分时钟(CLK+ / CLK-)
  • 数据线:1~4对差分数据线(DATA0+ / DATA0- 等)
  • 控制线:I2C用于配置摄像头寄存器,还有复位、电源使能等GPIO

我个人习惯在设备树里把MIPI的lane数配成和硬件一致。有一次我调试一个4 lane的摄像头,设备树里只配了2 lane,结果图像只有一半是正常的——上半部分清晰,下半部分全是噪点。排查了半天才发现是lane数配错了。

小技巧:MIPI CSI的速率可以通过clk_get_rate()查看。如果图像有横纹或闪烁,多半是时钟频率和传感器不匹配。我一般先用示波器量一下时钟波形,再调整PLL配置。

1.2.2 DVP(并行接口)

DVP是并行接口,用多根数据线同时传输像素数据。优点是简单、延迟低,缺点是线多、抗干扰差、速率上不去。现在基本只用在低分辨率或低成本方案里。

DVP接口的关键信号:

  • PCLK:像素时钟
  • HSYNC:行同步信号
  • VSYNC:帧同步信号
  • DATA[0:7]或DATA[0:9]:并行数据线

我曾经在一个老项目里用过DVP接口的OV7670摄像头。那玩意儿对时序要求特别严格,稍微有点抖动就出花屏。后来我加了RC滤波,又在驱动里做了deglitch处理,才算稳定下来。

1.2.3 USB摄像头

USB摄像头走的是UVC(USB Video Class)协议,Linux内核已经内置了uvcvideo驱动。说白了,大部分USB摄像头插上就能用,不需要自己写驱动。

但USB摄像头有个问题——热插拔虽然方便,但稳定性不如MIPI。我遇到过好几次USB摄像头在长时间运行后掉线的情况,原因往往是USB供电不足或者线缆质量差。

避坑指南:我曾经在一个工业项目里用USB摄像头做实时检测,结果设备运行两天后摄像头就丢了。排查发现是USB口的5V电源纹波太大,导致摄像头内部LDO工作异常。后来加了独立的电源滤波电路才解决。所以,工业场景下我建议优先用MIPI接口。

1.3 设备树配置基础

设备树(Device Tree)是嵌入式Linux里描述硬件信息的方式。对于Camera驱动来说,设备树里主要描述三件事:

  1. 摄像头传感器本身:挂在哪个I2C总线上、地址是多少
  2. MIPI CSI或DVP控制器:使用哪个接口、lane数、时钟频率
  3. 电源和复位:供电电压、GPIO控制

下面是一个典型的MIPI摄像头设备树配置示例:

// 摄像头传感器节点
&i2c2 {
    status = "okay";
    clock-frequency = <400000>;

    ov5640: camera@3c {
        compatible = "ovti,ov5640";
        reg = <0x3c>;
        clocks = <&clks IMX8MM_CLK_CLKO2>;
        clock-names = "xvclk";
        clock-frequency = <24000000>;

        // 电源配置
        DOVDD-supply = <®_1v8>;    // 数字IO电压
        AVDD-supply = <®_2v8>;     // 模拟电压
        DVDD-supply = <®_1v5>;     // 核心电压

        // 复位和电源使能GPIO
        reset-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
        powerdown-gpios = <&gpio1 20 GPIO_ACTIVE_HIGH>;

        // MIPI CSI端口配置
        port {
            ov5640_to_mipi: endpoint {
                remote-endpoint = <&mipi_csi_ep>;
                data-lanes = <1 2>;  // 使用2 lane
                clock-lanes = <0>;
            };
        };
    };
};

// MIPI CSI控制器节点
&csi {
    status = "okay";

    port {
        mipi_csi_ep: endpoint {
            remote-endpoint = <&ov5640_to_mipi>;
        };
    };
};

这里有几个关键点我得提醒你:

  • compatible属性必须和驱动里的of_match_table匹配,否则驱动加载不了。我见过有人把"ovti,ov5640"写成"ovti,ov5640a",结果驱动死活不认。
  • data-lanes的顺序很重要。有些SoC要求lane从1开始编号,有些从0开始。最好查一下芯片手册。
  • 时钟频率要精确。摄像头传感器对输入时钟有严格要求,比如OV5640需要24MHz,给个27MHz它就不工作。

设备树调试小技巧:如果摄像头驱动加载失败,先检查/sys/firmware/devicetree/base/下的设备树内容是否正确。我经常用dtc -I fs -O dts /sys/firmware/devicetree/base/把当前设备树反编译出来,看看实际生效的配置是不是我想要的。

小结

这一章咱们把V4L2框架、硬件接口和设备树配置的基础过了一遍。说白了,Camera驱动开发就是三件事:理解V4L2的抽象模型、搞清楚硬件接口的物理特性、把设备树配对。这三样搞定了,后面热插拔和动态检测的实现就有了根基。

下一章咱们会深入V4L2的ioctl命令集,重点讲VIDIOC_SUBSCRIBE_EVENTVIDIOC_DQEVENT——这两个是热插拔检测的核心。到时候我会拿一个实际项目里的热插拔代码来拆解,保证让你看完就能上手。

好,今天就到这儿。有什么问题欢迎在评论区留言,或者到公众号找我。咱们下节课见!

课后思考:如果你用的摄像头是USB接口,设备树里需要配置什么?为什么?