基于RK3588S摄像头低延迟数据采集传输
这篇文章主要介绍了RK3588S sensor移植过程然后在NVR平台下运行RK低延迟demo 实现本地预览延迟50ms以内 及该demo的原理分析与实现
目录
前言
这篇文章主要介绍了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 的时钟源),
调整显示刷新率,使其与数据采集帧率匹配。
更多推荐



所有评论(0)