目录

前言

一、sensor移植适配

原理图

RK3588硬件资源介绍

编写配置设备树

内核和驱动配置

调试方法

遇到的问题及解决办法

二、低延迟demo运行与原理分析

简化的流程图如下

原理

VO模块的作用

VCNT 机制的作用

1,同步显示刷新与数据更新:

2,动态调节显示时序:

3,支持多种显示接口:

VCNT 与时钟调整

1,捕捉帧率差异(Delta)

2,判断同步状态

3,调整时钟频率



前言

这篇文章主要介绍了RK3588S sensor移植过程然后在NVR平台下运行RK低延迟demo 实现本地预览延迟50ms 及该demo的原理分析与实现


一、sensor移植适配

这个项目主要用的是mipi接口的摄像头,客户那边提供了几个型号的模组 sc132gs,og02b10,os05a20,这边只负责将摄像头点亮

原理图

RK3588硬件资源介绍

1. rk3588支持两个dcphy,节点名称分别为csi2_dcphy0/csi2_dcphy1。每个dcphy硬件支持RX/TX 同时使用,对于camera输入使用的是RX。支持DPHY/CPHY协议复用;需要注意的是同一个dcphy的TX/RX 只能同时使用DPHY或同时使用CPHY。其他dcphy参数请查阅rk3588数据手册。
2. rk3588支持2个dphy硬件,这里我们称之为dphy0_hw/dphy1_hw,两个dphy硬件都可以工作在full mode 和split mode两种模式下。
这部分RK的手册文档中均有描述,具体可以参考文档,这里不过多赘述,链路具体怎么配置需要看原理图硬件如何设计。

编写配置设备树

这里以os05a20为参考

硬件设计用到了两路mipi摄像头 我们需要关注的是以下几点:
①mipi数据和时钟接口,看上述硬件设计原理图

②摄像头挂在哪路 i2c总线下,以及i2c地址,I2C地址这个需要看sensor规格书

规格书中描述的地址均为8位地址,在dts当中对应的7位地址为0x36和0x16

③RST复位引脚,PWDN引脚以及POWER引脚要接对,需要保证供电电源都正常,这个非常重要

结合上述RK文档中的链路示意图,我们就可以开始编写设备树了,其实官方提供了很多类似的设备树配置给我们参考。

配置好的dts如下:

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)

/*

 * Copyright (c) 2021 Rockchip Electronics Co., Ltd.

 *

 */

/ {

    vcc_mipidphy0: vcc-mipidphy0-regulator {

        compatible = "regulator-fixed";

        gpio = <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;

        pinctrl-names = "default";

        pinctrl-0 = <&mipidphy0_pwr>;

        regulator-name = "vcc_mipidphy0";

        enable-active-high;

         regulator-boot-on;

         regulator-always-on;

    };

    vcc_mipidcphy0: vcc-mipidcphy0-regulator {

        compatible = "regulator-fixed";

        gpio = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;

        pinctrl-names = "default";

        pinctrl-0 = <&mipidcphy0_pwr>;

        regulator-name = "vcc_mipidcphy0";

        enable-active-high;

         regulator-boot-on;

         regulator-always-on;

    };

};

&csi2_dphy0 {

    status = "okay";

    ports {

        #address-cells = <1>;

        #size-cells = <0>;

        port@0 {

            reg = <0>;

            #address-cells = <1>;

            #size-cells = <0>;

            mipidphy0_in_ucam0: endpoint@1 {

                reg = <1>;

                remote-endpoint = <&os05a20_out0>;

                data-lanes = <1 2>;

            };

        };

        port@1 {

            reg = <1>;

            #address-cells = <1>;

            #size-cells = <0>;

            csidphy0_out: endpoint@0 {

                reg = <0>;

                remote-endpoint = <&mipi2_csi2_input>;

            };

        };

    };

};

&csi2_dphy0_hw {

    status = "okay";

};

&csi2_dcphy0 {

    status = "okay";

    ports {

        #address-cells = <1>;

        #size-cells = <0>;

        port@0 {

            reg = <0>;

            #address-cells = <1>;

            #size-cells = <0>;

            mipi_in_ucam0: endpoint@1 {

                reg = <1>;

                remote-endpoint = <&os05a20_out1>;

                data-lanes = <1 2>;

            };

        };

        port@1 {

            reg = <1>;

            #address-cells = <1>;

            #size-cells = <0>;

            csidcphy0_out: endpoint@0 {

                reg = <0>;

                remote-endpoint = <&mipi0_csi2_input>;

            };

        };

    };

};

&mipi_dcphy0 {

    status = "okay";

};

&i2c5 {

    status = "okay";

    pinctrl-names = "default";

    pinctrl-0 = <&i2c5m3_xfer>;

    os05a20_0: os05a20@36 {

        compatible = "ovti,os05a20";

        reg = <0x36>;

        clocks = <&cru CLK_MIPI_CAMARAOUT_M1>;

        clock-names = "xvclk";

        pinctrl-names = "default";

        pinctrl-0 = <&mipim1_camera1_clk>;

        power-domains = <&power RK3588_PD_VI>;

        power-gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;

        reset-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_LOW>;

        pwdn-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>;

        avdd-supply = <&vcc_mipidphy0>;

        dovdd-supply = <&vcc_mipidphy0>;

        dvdd-supply = <&vcc_mipidphy0>;

        rockchip,camera-module-index = <0>;

        rockchip,camera-module-facing = "back";

        rockchip,camera-module-name = "CMK-OT2022-PX1";

        rockchip,camera-module-lens-name = "IR0147-50IRC-8M-F20";

        port {

            os05a20_out0: endpoint {

                remote-endpoint = <&mipidphy0_in_ucam0>;

                data-lanes = <1 2>;

            };

        };

    };

};

&i2c8 {

    status = "okay";

    pinctrl-names = "default";

    pinctrl-0 = <&i2c8m2_xfer>;

    os05a20_1: os05a20@36 {

        compatible = "ovti,os05a20";

        reg = <0x36>;

        clocks = <&cru CLK_MIPI_CAMARAOUT_M0>;

        clock-names = "xvclk";

        pinctrl-names = "default";

        pinctrl-0 = <&mipim1_camera0_clk>;

        power-domains = <&power RK3588_PD_VI>;

        power-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;

        reset-gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>;

        pwdn-gpios = <&gpio1 RK_PA6 GPIO_ACTIVE_LOW>;

        avdd-supply = <&vcc_mipidcphy0>;        

        dovdd-supply = <&vcc_mipidcphy0>;

        dvdd-supply = <&vcc_mipidcphy0>;

        rockchip,camera-module-index = <1>;

        rockchip,camera-module-facing = "front";

        rockchip,camera-module-name = "CMK-OT2022-PX1";

        rockchip,camera-module-lens-name = "IR0147-50IRC-8M-F21";

        port {

            os05a20_out1: endpoint {

                remote-endpoint = <&mipi_in_ucam0>;

                data-lanes = <1 2>;

            };

        };

    };

};

&mipi0_csi2 {

    status = "okay";

    ports {

        #address-cells = <1>;

        #size-cells = <0>;

        port@0 {

            reg = <0>;

            #address-cells = <1>;

            #size-cells = <0>;

            mipi0_csi2_input: endpoint@1 {

                reg = <1>;

                remote-endpoint = <&csidcphy0_out>;

            };

        };

        port@1 {

            reg = <1>;

            #address-cells = <1>;

            #size-cells = <0>;

            mipi0_csi2_output: endpoint@0 {

                reg = <0>;

                remote-endpoint = <&cif_mipi_in0>;

            };

        };

    };

};

&mipi2_csi2 {

    status = "okay";

    ports {

        #address-cells = <1>;

        #size-cells = <0>;

        port@0 {

            reg = <0>;

            #address-cells = <1>;

            #size-cells = <0>;

            mipi2_csi2_input: endpoint@1 {

                reg = <1>;

                remote-endpoint = <&csidphy0_out>;

            };

        };

        port@1 {

            reg = <1>;

            #address-cells = <1>;

            #size-cells = <0>;

            mipi2_csi2_output: endpoint@0 {

                reg = <0>;

                remote-endpoint = <&cif_mipi2_in0>;

            };

        };

    };

};



 

&pinctrl {

    cam {

        mipidphy0_pwr: mipidphy0-pwr {

            rockchip,pins =

                /* camera power en */

                <1 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>;

        };

        mipidcphy0_pwr: mipidcphy0-pwr {

            rockchip,pins =

                /* camera power en */

                <1 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none>;

        };

    };

};

&rkcif {

    status = "okay";

};

&rkcif_mipi_lvds {

    status = "okay";

    port {

        cif_mipi_in0: endpoint {

            remote-endpoint = <&mipi0_csi2_output>;

        };

    };

};

&rkcif_mipi_lvds2 {

    status = "okay";

    port {

        cif_mipi2_in0: endpoint {

            remote-endpoint = <&mipi2_csi2_output>;

        };

    };

};

&rkcif_mipi_lvds_sditf {

    status = "okay";

    port {

        mipi_lvds_sditf: endpoint {

            remote-endpoint = <&isp0_vir0>;

        };

    };

};



 

&rkcif_mipi_lvds2_sditf {

    status = "okay";

    port {

        mipi_lvds2_sditf: endpoint {

            remote-endpoint = <&isp0_vir2>;

        };

    };

};


 

&rkcif_mmu {

    status = "okay";

};

&rkisp0 {

    status = "okay";

};

&isp0_mmu {

    status = "okay";

};

&rkisp1 {

    status = "okay";

};

&isp1_mmu {

    status = "okay";

};

&rkisp0_vir0 {

    status = "okay";

    port {

        #address-cells = <1>;

        #size-cells = <0>;

        isp0_vir0: endpoint@0 {

            reg = <0>;

            remote-endpoint = <&mipi_lvds_sditf>;

        };

    };

};

&rkisp0_vir2 {

    status = "okay";

    port {

        #address-cells = <1>;

        #size-cells = <0>;

        isp0_vir2: endpoint@0 {

            reg = <0>;

            remote-endpoint = <&mipi_lvds2_sditf>;

        };

    };

};

上述dts除了链路和引脚外还需要注意的是rockchip,camera-module-name和rockchip,camera-module-lens-name这两个参数,这两个参数对应的是sensor的效果文件,需要以实际的名称对应起来

设备树修改好后,就是内核和驱动相关的配置

内核和驱动配置

1,/kernel/drivers/media/i2c下编辑Makefile,添加编译输出

2,/kernel/drivers/media/i2c下编辑Kconfig,添加编译配置

3,/kernel/arch/arm64/configs下编辑rockchip_linux_defconfig,添加os05a20编译选项

以上配置完成后,连接SoC与sensor,i2c应该是通的,但是用i2c工具测试,设备并没有挂载上,重复多次检查硬件确保硬件正常

调试方法

①移植成功后,使用dmesg指令查看对应驱动的log看下是否正确的获取到了mipi摄像头的ID

②RKISP 驱动如果加载成功,会有 video 及 media 设备存在于/dev/目录下。系统中可能存在 多个/dev/video 设备,通过该命令可以查询到 RKISP 注册的 video 节点

grep "" /sys/class/video4linux/v*/name | grep mainpath

③查看拓扑结构

通过media-ctl -p /dev/mediaX( X指0,1,2)查看已打开的media设备的拓扑可以看到摄像头的信息这里只截取一部分,这里的内容RK官方的文档中也有做说明。

④抓图

都正常的话接下来就可以进行抓图操作了

抓图前需要确保一下几点:

1)查看rkaiq_3A_server有没有启动(看有没有进程,没有说明还未启动服务):
pidof rkaiq_3A_server

2)务必启动先启动 rkaiq_3A_server(否则摄像头预览不正常):

/etc/init.d/rkaiq_3A.sh start

这里的3A会去解析mipi的效果文件也就是IQ文件

关于IQ文件的修改,RK的文档也有说明

执行以下指令抓取图,成功上传/tmp/cif2.out
echo 0 > /sys/devices/platform/rkcif_mipi_lvds/compact_test         //每个平台名字有稍许差别、可能需要自行改下
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 --stream-mmap=3 --stream-skip=60 --stream-to=/tmp/cif2.out --stream-count=1 --stream-poll 

也可以用如下命令

v4l2-ctl -d /dev/videoX --set-fmt-video=width=1920,height=1080,pixelformat=NV12 --stream-mmap=3 --stream-skip=60 --stream-to=/tmp/out.yuv --stream-count=1
区别是raw图是sensor输出的图,yuv图是isp做了3A处理raw图后生成的图

以上都做好后就可以抓图了


遇到的问题及解决办法

①i2c不通导致驱动注册不上,反馈到log上,常见的报错是这样

Unexpected sensor id(000000), ret(-6)

camera 模组的i2c不通,与主控的CIF和ISP控制器模块没有关系,一般都是上电时序的没满足要求,这部分网上相关的内容方法挺多的 这里不做过多赘述,我这边遇到的主要是电源配置的问题

dts 文件通过定义 pwdn-gpios 来告知内核 pwdn_gpio 控制的具体 GPIO 引脚的引脚状态

但是RK原始的驱动文件当中也有对这个引脚做控制描述,这里导致供电异常

关于这部分内容,dts和驱动同时都有做描述的情况下

最终控制的行为是由 代码中的 gpiod_direction_output 调用决定的,因为它通过驱动程序控制 pwdn_gpio 引脚的电平。

dts文件提供了硬件层面的配置,告诉内核硬件资源的布局和配置,通过 pwdn-gpios 来告知内核应该使用 gpio1 控制器的第 7 引脚 (RK_PA7) 来控制 pwdn_gpio,并且这个引脚是低电平激活的。

驱动程序在运行时将会根据设备树中的定义找到对应的 GPIO 引脚,并对该引脚进行操作,代码中的 gpiod_direction_output(os05a20->pwdn_gpio, 0); 会根据设备树中的定义,将该引脚设置为输出低电平,执行 "power down" 操作。

也就是说设备树和驱动代码是相辅相成的:设备树提供硬件描述,驱动代码实现控制行为。如果设备树中的配置不正确(比如错误的引脚),那么驱动代码可能会尝试操作错误的引脚,导致功能无法正常工作。

②抓图的时候报错  select timeout

这个报错网上也有相关的解决办法,我这边主要的原因是驱动源码配置的问题

关于驱动需要关注的点

这部分代码是一个结构体数组 supported_modes,它包含了多个摄像头模式的配置,每个模式对应一个特定的分辨率、帧率、曝光、时序等设置,这部分内容需要与模组提供的参数对应起来比如说分辨率。

在非HDR模式时,默认匹配为supported_modes[0]

因此需要设置os05a20_linear12bit_2592x1944_regs这个寄存器,寄存器值可以通过寄存器手册获取,参照寄存器表,以设置不同的工作模式,也可以问模组FAE要

还有就是要注意这里 需要与实际的硬件对应

③抓图画面全黑
能抓到数据,但是画面预览是全黑的,强光照射下能有反应,像这样

这个一般是IQ文件未解析的问题,可以看下文件系统中有没有对应的IQ文件,然后看下3A程序跑起来了没有。

二、低延迟demo运行与原理分析

具体的运行方法RK提供的手册说明文档里面已经写的很清楚了,这边不做说明具体,

这边主要做下原理的分析

采集数据的帧率与刷新屏幕的帧率不可能完全一致,对于要求低延迟显示来说,可能就会有延迟时间的波动,未了解决这个问题,就需要动态调整屏幕的刷新率,以达到屏幕刷新率和数据采集帧率尽可能的趋于一致

这个demo中有网络传输和本地预览两种,这里只对本地预览做说明


简化的流程图如下

这个demo接口的核心作用是实现采集帧率与显示帧率的动态匹配,以减小两者之间的差值(delta),从而降低延迟波动,确保显示画面流畅、稳定且无撕裂。

原理

其原理基于对VSYNC(屏幕刷新同步信号)帧率的统计和解析,与采集帧率进行比较,计算差值。如果差值超出预设的“安全区间”,则通过动态调整显示设备的时钟(如 HDMI的 PHY 时钟、eDP 的 VPLL 时钟等),改变显示刷新率,使其趋于采集帧率。此外,该接口依赖 RK 平台 VO 模块中的 VCNT(垂直计数器)机制,通过设置 VCNT 和 VO 时间窗,确保显示刷新和图像更新在同步点上协调,并在下一个VSYNC 周期内生效,从而保证画面呈现的实时性和一致性。

VO模块的作用

在RK平台中,VO模块(Video Output,视频输出)是负责视频信号输出的关键模块。它用于将图像或视频数据从处理器传输到显示设备(如 HDMI、eDP、DSI 屏幕等)。

VCNT 机制的作用

VO 模块中VCNT(Vertical counter,垂直计数器)机制 是其实现显示同步的重要功能,以下是详细解释:

VCNT 是一种垂直计数器,用于统计屏幕刷新周期内的时间点(通常以 VSYNC 为基准)。它的核心作用是提供精准的时间参考,协调显示输出的时间安排,确保显示画面与帧数据同步。其主要功能包括:

1,同步显示刷新与数据更新:

①在显示器刷新(VSYNC 信号触发)时,VCNT会记录当前的垂直刷新位置。

②系统可以根据 VCNT 提供的计数值,准确安排图像数据的更新时间,使得更新后的数据能够在下一个 VSYNC 周期内生效,避免画面撕裂。

2,动态调节显示时序:

①VCNT 的计数值可用于检测帧率不匹配的情况(如采集帧率与显示帧率不同步)

②通过调整 VCNT 的计数时刻或刷新时钟频率,可以动态匹配显示设备的刷新率与数据帧率

3,支持多种显示接口:

RK 平台的 VO 模块支持 HDMI、eDP、DSI等多种显示接口。VCNT 机制通过统一的计数器逻辑,协调不同显示接口的输出时序,确保各接口输出画面的稳定性。

VCNT 与时钟调整

VCNT 机制依赖于显示模块的时钟,如 HDMI的 PHY 时钟、eDP 的 VPLL 时钟或 DSI 的 AUPLL 时钟。这些时钟的频率决定了显示刷新率。具体过程如下:

1,捕捉帧率差异(Delta)

VCNT 统计 VSYNC 周期内的时间点,与数据帧率对比,计算差值 △ = capture - display

2,判断同步状态

如果差值 △ 在允许范围内,则无需调整;

如果超出范围,则需要动态调整时钟频率。

3,调整时钟频率

通过改变 VO 模块输出时钟(如 HDM、eDP 的时钟源),
调整显示刷新率,使其与数据采集帧率匹配。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐