news 2026/8/27 15:23:14

全志V3s MIPI CSI-2驱动移植与OV5640适配实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
全志V3s MIPI CSI-2驱动移植与OV5640适配实战

1. 从零开始:为什么我要折腾V3s的MIPI摄像头驱动

大家好,我是老张,一个在嵌入式圈子里摸爬滚打了十多年的老鸟。最近因为一个项目,我又一次和全志V3s这颗“国民级”芯片杠上了,目标很明确:给它接上一个MIPI接口的OV5640摄像头,让这个小小的开发板能“看见”世界。说实话,这事儿听起来简单,但真做起来,尤其是当你手头的开发板资料匮乏、官方支持约等于零的时候,那感觉就像是在一片漆黑的森林里找路。

我用的这块板子是在淘宝上随便淘的,价格很香,但配套的软件就有点“复古”了——用的是全志内部的老版本Linux,连设备树都没用上。更坑的是,它原配的摄像头模块贵得离谱,这谁受得了?于是我就把目光投向了OV5640,这玩意儿在市场上量大管饱,价格亲民,而且同时支持MIPI和DVP接口,灵活性很高。但问题来了,V3s的官方SDK里,MIPI CSI-2的驱动支持并不完善,或者说,对于非官方推荐的摄像头模组,基本就是“三不管”地带。

所以,这篇记录就是我在这个“三不管”地带里趟出来的一条路。整个过程,说白了就是三件事:找驱动、改设备树、调内核。我会把我踩过的每一个坑、绕过的每一个弯都详细写下来,目标就是让你看完之后,能拿着一块类似的板子和一个OV5640模组,一步步地把摄像头给点亮。我不讲太多高深的理论,就聊怎么动手把它做出来。咱们先从最头疼的“找驱动”开始。

2. 驱动源码的获取与整合:把“别人家”的驱动请进门

驱动是整个项目的基石。全志V3s虽然内置了MIPI CSI-2控制器,但内核源码里对应的驱动(sun6i-csi)可能功能不全或者对第三方传感器支持不好。我们需要一个更通用、更活跃的开源驱动。我在Github和Gitee上翻了好久,最终锁定了sun6i-mipi-csi这个项目。它最初应该是为全志A系列芯片写的,但经过社区维护,对V3s的兼容性也不错。

第一步,下载与解压。你可以从Gitee的镜像仓库获取,地址是https://gitee.com/prtemly/sun6i-mipi-csi。我下载下来的压缩包叫sun6i-mipi-csi-master.zip。假设你的内核源码目录在/home/linux/v3s/linux-zero-5.2.y,我们把它解压到媒体驱动相关的目录下:

unzip sun6i-mipi-csi-master.zip -d /home/linux/v3s/linux-zero-5.2.y/drivers/staging/media/sunxi

解压后会得到一个sun6i-mipi-csi-master文件夹。为了保持内核目录的命名一致性,我习惯把它改名为sun6i-csi

cd /home/linux/v3s/linux-zero-5.2.y/drivers/staging/media/sunxi mv sun6i-mipi-csi-master sun6i-csi

第二步,让内核构建系统认识我们的新驱动。这就像给家里新来的客人登记户口。需要修改两个文件:

  1. 修改Kconfig:编辑drivers/staging/media/sunxi/Kconfig文件。找到if VIDEO_SUNXIendif之间的区域,在里面添加一行:

    source "drivers/staging/media/sunxi/sun6i-csi/Kconfig"

    这行代码的作用是告诉上层的配置菜单,我们这个sun6i-csi目录下也有配置项需要被加载和显示。

  2. 修改Makefile:编辑同目录下的Makefile文件。在文件末尾附近,你会看到类似obj-$(CONFIG_VIDEO_SUNXI_CEDRUS) += cedrus/的行。我们在它后面添加新的一行:

    obj-$(CONFIG_VIDEO_SUN6I_MIPI_CSI) += sun6i-csi/

    这行代码是编译指令,意思是当配置选项CONFIG_VIDEO_SUN6I_MIPI_CSI被选中时,就去编译sun6i-csi目录下的代码。

做完这两步,驱动源码就算在 kernel 里安家了。但光安家还不够,我们还得告诉内核,在咱们这块具体的板子上,这个控制器和传感器具体连接在哪个引脚、用什么时钟、怎么供电。这就是接下来设备树(Device Tree)要干的活儿。设备树就像一份硬件的“地图”,内核启动时按图索骥,才能正确初始化设备。

3. 设备树配置详解:给硬件画一张精确的“地图”

设备树的修改是移植工作的核心,也是最容易出错的地方。我们需要修改两个主要的设备树文件:一个是针对具体开发板的.dts文件(比如我用的sun8i-v3s-licheepi-zero.dts),另一个是包含SoC通用定义的.dtsi文件(sun8i-v3s.dtsi)。修改的原则是:在.dtsi里补充SoC级别的通用节点定义(如果缺失的话),在.dts里启用和配置这些节点,并描述板级特有的连接。

首先,在板级设备树文件(sun8i-v3s-licheepi-zero.dts)中添加内容。

  1. 在根节点下添加CSI控制器和电源管理节点。打开文件,在根节点/{ ... };的大括号内添加。这里我们主要添加两个csi控制器节点(V3s有两个CSI接口)和三个为OV5640供电的稳压器节点。注意,这些节点的status默认都是"disabled",我们稍后再启用。

    // 添加CSI控制器 csi0: csi@01cb0000 { compatible = "allwinner,sun8i-v3s-csi"; reg = <0x01cb0000 0x3000>; interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>; clocks = <&ccu CLK_BUS_CSI>, <&ccu CLK_CSI1_SCLK>, <&ccu CLK_DRAM_CSI>, <&ccu CLK_MIPI_CSI>; clock-names = "bus", "mod", "ram", "dphy"; resets = <&ccu RST_BUS_CSI>; status = "disabled"; }; // 添加OV5640所需的三个固定电压稳压器 dovdd: regulator-dovdd { compatible = "regulator-fixed"; regulator-name = "DOVDD"; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; enable-active-high; }; // ... 类似地添加 dvdd (1.8V) 和 avdd (2.8V) 节点

    关键点解读

    • compatible:驱动匹配的关键字,必须和驱动源码里定义的一致。
    • reg:控制器在内存中的映射地址和范围,来自芯片手册。
    • clocksclock-names:指定控制器需要的所有时钟源。dphy是MIPI D-PHY的时钟,对于MIPI模式至关重要。
    • 三个稳压器(dovdd,dvdd,avdd)对应OV5640模组需要的三种电压(通常是1.8V、1.8V、2.8V)。如果你的板子有专门的电源管理芯片供电,这里可能需要引用已有的稳压器节点,而不是新建regulator-fixed
  2. 在非根节点下启用并连接设备。接着,在文件的后面部分(通常是在各个节点引用的地方),我们来启用和配置这些设备。

    // 启用并配置CSI0控制器 &csi0 { status = "okay"; allwinner,mipi-csi-bps = <400000000>; // MIPI通道速率,单位比特每秒 port { csi0_ep: endpoint { remote-endpoint = <&ov5640_0>; // 指向摄像头传感器端点 clock-lanes = <0>; // 使用第0条时钟通道 >/ { soc { pio: pinctrl@1c20800 { // ... 其他引脚组定义 ... i2c1_pins: i2c1-pins { pins = "PE21", "PE22"; // I2C1的SDA和SCL引脚 function = "i2c1"; }; }; i2c1: i2c@1c2b000 { compatible = "allwinner,sun6i-a31-i2c"; reg = <0x01c2b000 0x400>; interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>; clocks = <&ccu CLK_BUS_I2C1>; resets = <&ccu RST_BUS_I2C1>; pinctrl-names = "default"; pinctrl-0 = <&i2c1_pins>; // 引用上面定义的引脚组 clock-frequency = <100000>; // I2C总线速度,100kHz status = "disabled"; #address-cells = <1>; #size-cells = <0>; }; }; };

    这里添加了i2c1_pins引脚组定义,并在i2c1节点中通过pinctrl-0引用它,同时设置了clock-frequency。这些补充确保了I2C控制器能正确工作。

    设备树修改是个精细活,一个标点符号错了都可能导致内核无法启动或者设备无法识别。改完后,强烈建议用dtc(设备树编译器)检查一下语法:dtc -I dts -O dtb -o /dev/null your_dts_file.dts。没有报错再继续。

    4. 内核配置与编译:勾选正确的选项

    设备树的“地图”画好了,接下来就要告诉内核,我们需要把哪些“功能模块”编译进去。这里主要通过make menuconfig进行图形化配置。

    1. 进入内核配置界面:

      cd /home/linux/v3s/linux-zero-5.2.y make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
    2. 配置MIPI CSI驱动:按照以下路径找到选项:

      Device Drivers ---> Staging drivers ---> [*] Media staging drivers ---> [*] Allwinner sunXi family Video Devices ---> <*> Allwinner V3s Camera Sensor Interface driver with MIPI-CSI2 support

      注意,这个选项的名字CONFIG_VIDEO_SUN6I_MIPI_CSI应该和我们之前在Makefile里添加的obj-$(CONFIG_VIDEO_SUN6I_MIPI_CSI)完全对应。把它编译进内核(<*>),而不是模块(<M>),对于启动时自动加载更省心。

    3. 配置OV5640传感器驱动:这个驱动不在Staging目录下,在主线的多媒体支持里:

      Device Drivers ---> <*> Multimedia support ---> [ ] Autoselect ancillary drivers (tuners, sensors, i2c, spi, frontends) // 注意:先**取消选中**这一项! -*- I2C Encoders, decoders, sensors and other helper chips ---> <*> OmniVision OV5640 sensor support

      这里有个大坑:默认情况下,Autoselect ancillary drivers是选中的,它会自动隐藏很多传感器驱动选项。你必须先把它取消选中,然后进入I2C Encoders...子菜单,才能看到OmniVision OV5640 sensor support这个选项。把它也选为<*>编译进内核。

    4. 保存并编译:配置完成后,保存退出。然后就是漫长的编译过程:

      make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)

      编译成功后,你会得到新的内核镜像(如arch/arm/boot/zImage)和设备树二进制文件(如arch/arm/boot/dts/sun8i-v3s-licheepi-zero.dtb)。把它们烧录到开发板,或者通过TFTP等方式加载启动。

    5. 系统启动与功能验证:点亮摄像头的那一刻

    如果一切顺利,开发板启动后,摄像头驱动应该已经成功加载。我们可以通过一系列命令来验证。

    1. 检查设备节点:首先登录到开发板的Linux系统。驱动加载成功后,在/dev目录下应该会出现视频设备节点,通常是/dev/video0。同时,V4L2子系统还会创建subdev节点,位于/dev/v4l-subdev*。我们可以用v4l2-ctl这个强大的工具来查看:

      v4l2-ctl --list-devices

      这个命令会列出所有V4L2设备。你期望看到类似这样的输出,其中包含sun6i-csiov5640的字样:

      sun6i-csi (platform:1cb0000.csi): /dev/video0 /dev/v4l-subdev0 ov5640 3-003c (platform:1c2b000.i2c): /dev/v4l-subdev1

      这证明CSI控制器和OV5640传感器都被内核正确识别并注册了。

    2. 获取摄像头能力信息:进一步,我们可以查看/dev/video0支持哪些格式和分辨率:

      v4l2-ctl --device=/dev/video0 --list-formats-ext

      这条命令会输出一长串信息,显示设备支持的像素格式(如NV12YUYV等)以及每种格式下支持的分辨率(如640x4801920x1080等)。OV5640通常支持多种分辨率,驱动会把这些信息上报。

    3. 捕获第一张图片(最激动人心的步骤):确认设备存在后,我们就可以尝试拍照了。这里我们用v4l2-ctl--stream-mmap模式(内存映射)来捕获一帧图像:

      v4l2-ctl --device=/dev/video0 \ --set-fmt-video=width=640,height=480,pixelformat=NV12 \ --stream-mmap \ --stream-to=output.yuv \ --stream-count=1
      • 参数解释:
        • --set-fmt-video:设置捕获的视频格式。这里我们选择NV12(一种YUV格式),分辨率640x480
        • --stream-mmap:使用内存映射方式进行图像捕获,效率较高。
        • --stream-to:指定输出文件名。
        • --stream-count:指定捕获的帧数,这里设为1,就是拍一张照片。 执行成功后,当前目录下会生成一个output.yuv文件。这个文件是原始的YUV数据,不能直接用普通图片查看器打开。
    4. 在电脑上查看YUV图片:output.yuv文件从开发板拷贝到你的Ubuntu主机上(可以用SCP、NFS共享等方式)。然后使用ffplay(FFmpeg的一部分)来播放这个原始数据文件:

      # 首先确保电脑上安装了ffmpeg: sudo apt install ffmpeg ffplay -video_size 640x480 -pixel_format nv12 -framerate 10 -i output.yuv
      • 参数解释:
        • -video_size:必须和捕获时设置的分辨率一致。
        • -pixel_format:必须和捕获时设置的格式一致(nv12)。
        • -framerate:由于我们只捕获了一帧,这里随便设一个值(如10)即可。
        • -i:指定输入文件。 如果一切正常,屏幕上会短暂弹出一个窗口,显示你捕获到的图像!如果图像是彩色的、没有严重扭曲,那么恭喜你,驱动移植和适配基本成功了。如果图像是绿色的、花屏的或者根本打不开,那可能还需要回头检查数据通道配置、时钟频率或者电源时序。

    6. 避坑指南与深度调试

    在实际操作中,一次成功的概率不大。下面是我遇到过的几个典型问题及排查思路:

    问题一:内核启动时找不到设备,/dev/video0不存在。

    • 检查设备树:用cat /proc/device-tree/相关路径或dtc反编译DTB文件,确认csi0i2c1节点的status是否为"okay"compatible属性是否正确。
    • 检查内核日志dmesg | grep -E \"(csi|ov5640|i2c)\"。重点关注是否有驱动探测(probe)成功的消息,或者是否有错误提示(比如寄存器读写失败、时钟获取失败、供电问题等)。
    • 检查I2C通信:在系统启动后,用i2cdetect -y 1(假设I2C总线编号是1)扫描I2C总线,看地址0x3c上是否有设备响应。如果没有,检查硬件连接、上拉电阻、以及设备树中I2C引脚的配置。

    问题二:有/dev/video0,但v4l2-ctl --list-formats-ext没有输出或格式不对。

    • 检查MIPI数据通道和时钟:确认设备树中data-lanesclock-lanes的设置与物理连接完全匹配。OV5640的MIPI接口可能需要特定的上电和初始化序列,检查驱动源码里的ov5640.c,看其s_powers_stream操作是否正常。
    • 检查时钟和电源:用示波器或逻辑分析仪测量OV5640的XCLK(24MHz)是否正常,DOVDDDVDDAVDD电压是否到位,PWDNRESET引脚的电平时序是否符合传感器手册要求。很多时候问题出在硬件上。

    问题三:能捕获图像,但图像颜色异常、有条纹或错位。

    • 调整MIPI速率:设备树中的allwinner,mipi-csi-bps属性可能需要调整。OV5640在不同的分辨率下,所需的MIPI通道速率不同。可以尝试降低这个值(比如从4亿降到3亿)。
    • 检查像素格式:确保v4l2-ctl设置的pixelformat与传感器实际输出的格式、以及你查看工具指定的格式完全一致。除了NV12,还可以尝试YUYV等。
    • 深入V4L2调试:可以打开内核的V4L2调试信息,重新编译内核。在make menuconfig中,Device Drivers -> Multimedia support -> Media core drivers下,可以启用Media drivers debug等相关选项。更详细的日志有助于定位问题在驱动链的哪个环节。

    整个移植过程,就是不断地在软件配置和硬件信号之间来回验证。耐心和细致的排查是关键。当你第一次在屏幕上看到来自OV5640的清晰图像时,那种成就感,绝对是驱动开发者最好的奖励。这份记录希望能帮你少走些弯路,如果遇到新问题,多查查内核文档、芯片手册和传感器数据手册,社区的讨论也很有价值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 17:03:24

解决cosyvoice error: could not open requirements file的实战指南

最近在折腾一个语音合成的项目&#xff0c;用到了 CosyVoice 这个工具包&#xff0c;结果在安装依赖这一步就卡住了&#xff0c;遇到了一个挺典型的错误&#xff1a;cosyvoice error: could not open requirements file: [errno 2] 没有那个文件或。这个错误信息虽然直白&#…

作者头像 李华
网站建设 2026/7/14 17:03:23

SmolVLA赋能前端开发:JavaScript交互与实时预览实现

SmolVLA赋能前端开发&#xff1a;JavaScript交互与实时预览实现 最近和几个做前端的朋友聊天&#xff0c;大家不约而同地聊到一个话题&#xff1a;现在AI能力这么强&#xff0c;能不能直接在前端项目里用起来&#xff1f;比如&#xff0c;用户输入一段话&#xff0c;页面实时就…

作者头像 李华
网站建设 2026/7/14 17:03:21

RevokeMsgPatcher:让消息撤回功能彻底失效的终极方案

RevokeMsgPatcher&#xff1a;让消息撤回功能彻底失效的终极方案 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitcode.com…

作者头像 李华
网站建设 2026/7/14 17:03:23

智能小红书数据采集工具:高效Python实现与应用指南

智能小红书数据采集工具&#xff1a;高效Python实现与应用指南 【免费下载链接】xhs 基于小红书 Web 端进行的请求封装。https://reajason.github.io/xhs/ 项目地址: https://gitcode.com/gh_mirrors/xh/xhs 在数字化营销与市场分析领域&#xff0c;获取高质量的社交平台…

作者头像 李华
网站建设 2026/7/14 17:03:22

Camera Shakify技术原理与实战应用:打造电影级相机抖动效果

Camera Shakify技术原理与实战应用&#xff1a;打造电影级相机抖动效果 【免费下载链接】camera_shakify 项目地址: https://gitcode.com/gh_mirrors/ca/camera_shakify 一、场景价值&#xff1a;为何相机抖动是视觉叙事的隐形语言&#xff1f; 在数字动画创作中&…

作者头像 李华
网站建设 2026/7/14 17:03:27

提升三倍效率,用快马平台自动化完成openclaw安装与配置

最近在团队里负责一批新服务器的环境搭建&#xff0c;其中就包括openclaw的安装。说实话&#xff0c;第一次手动装的时候&#xff0c;那感觉真是“痛并快乐着”。快乐是因为终于搞定了&#xff0c;痛是因为过程太磨人了&#xff1a;各种依赖包、环境变量、编译选项&#xff0c;…

作者头像 李华