前言

泰山派官方适配的摄像头是ov5695,但是博主想尝试一下移植另一款摄像头,积攒一下mipi摄像头驱动移植的经验,发现有一款ov8858非常适合,下面就来分享一下移植这款摄像头的方法。这篇文章的内容可以和博主的另一篇文章结合,可以看我主页里泰山派网络AI摄像头那个项目,把移植ov8858驱动加进去,这样驱动和应用就都有了。

参数分析

移植一款新的摄像头肯定是有目的的,这样放在项目里也有理由,比如原来的摄像头像素不够,帧率太低什么什么的,所以我们移植了一个新的摄像头。

立创教程里推荐的摄像头是ov5695,我们看一下这个摄像头的参数:

在这里插入图片描述

可以看到,它最大只能支持到500万像素的分辨率,显然是不够用的。那我们再来看一下泰山派rk3566的硬件参数。

可以看到,ISP最大支持8M,即800万像素

所以ov5695最大支持500万像素,其实还不够,因此我找了一款800万像素的摄像头,也就是ov8858,他的参数如下:

在这里插入图片描述

它最大支持3264×2448,约等于800万像素。具体如下:

https://e.tb.cn/h.Senf3i3VTCNvhPD?tk=1cWU4wixhhw

注意如果想实现自动聚焦功能,就必须要买自动聚焦的那一款。博主已经和厂家确认过了,这款ov8858摄像头的接口和泰山派是一摸一样的,直接接上就行,不需要画扩展板。

OV8858驱动适配

首先要在内核里添加ov8858的驱动

SDK源码目录/kernel/drivers/media/i2c目录下的Makefile文件中添加内容:

obj-y += ov8858.o

然后重新编译内核,目录下出现ov8858.o即为添加成功。

下面来配置设备树,在目录SDK源码目录/kernel/arch/arm64/boot/dts/rockchip下,添加一个dtsi文件,命名为tspi-rk3566-csi-ov8858.dtsi,然后在文件tspi-rk3566-user-v10-linux.dts中,我们把ov5695的头文件注释掉,添加ov8858的头文件:

在这里插入图片描述

下面就是来配置tspi-rk3566-csi-ov8858.dtsi内容,代码如下:


/*********************************************************************
 *           立创开发板不靠卖板赚钱,以培养中国工程师为己任  
 *         泰山派软硬件资料与相关扩展板软硬件资料官网全部开源
 *                      开发板官网:www.lckfb.com            
 *                     立创论坛:oshwhub.com/forum           
 *            关注B站:【立创开发板】,掌握我们的最新动态!  
 *********************************************************************
 * 文件名:tspi-rk3566-csi-v10
 * 描述:mipi 摄像头
 * 更新:
 * 时间          作者           联系           说明
 * 2023-09-13   吴才成    1378913492@qq.com   v1.0.0
 *********************************************************************/

//phy u序列
&combphy1_usq {
	status = "okay";
};

//phy P序列
&combphy2_psq {
	status = "okay";
};

//dphy硬件
&csi2_dphy_hw {
	status = "okay";
};
//摄像头D-PHY接口
&csi2_dphy0 {
	status = "okay";
	/*
	 * dphy0 only used for full mode,
	 * full mode and split mode are mutually exclusive
	 */
	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		port@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;

			dphy0_in: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&ov8858_out>;
				data-lanes = <1 2 3 4>;
			};
		};

		port@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;

			dphy0_out: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&isp0_in>;
			};
		};
	};
};

//摄像头D-PHY接口
&csi2_dphy1 {
	status = "disabled";

	/*
	 * dphy1 only used for split mode,
	 * can be used  concurrently  with dphy2
	 * full mode and split mode are mutually exclusive
	 */
	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		port@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;

			dphy1_in: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&ov8858_out>;
				data-lanes = <1 2 3 4>;
			};
		};

		port@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;

			dphy1_out: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&isp0_in>;
			};
		};
	};
};
//摄像头D-PHY接口
&csi2_dphy2 {
	status = "disabled";

	/*
	 * dphy2 only used for split mode,
	 * can be used  concurrently  with dphy1
	 * full mode and split mode are mutually exclusive
	 */
	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		port@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;

			dphy2_in: endpoint@1 {
				reg = <1>;
				//remote-endpoint = <&gc5025_out>;
				data-lanes = <1 2>;
			};
		};

		port@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;

			dphy2_out: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&mipi_csi2_input>;
			};
		};
	};
};



&mipi_csi2 {
	status = "disabled";

	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		port@0 {
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;

			mipi_csi2_input: endpoint@1 {
				reg = <1>;
				remote-endpoint = <&dphy2_out>;
				data-lanes = <1 2>;
			};
		};

		port@1 {
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;

			mipi_csi2_output: endpoint@0 {
				reg = <0>;
				remote-endpoint = <&cif_mipi_in>;
				data-lanes = <1 2>;
			};
		};
	};
};

//Rockchip Camera Interface
&rkcif {
	status = "disabled";
};

//dvp接口摄像头
&rkcif_dvp {
	status = "disabled";

	port {
		/* Parallel bus endpoint */
		dvp_in_bcam: endpoint {
			// remote-endpoint = <&gc2145_out>;
			bus-width = <8>;
			vsync-active = <0>;
			hsync-active = <1>;
		};
	};
};

//LVDS接口摄像头
&rkcif_mipi_lvds {
	status = "disabled";

	port {
		cif_mipi_in: endpoint {
			remote-endpoint = <&mipi_csi2_output>;
			data-lanes = <1 2>;
		};
	};
};

//摄像头内存管理
&rkcif_mmu {
	status = "disabled";
};

//硬件图像处理器模块
&rkisp {
	status = "okay";
};

//硬件图像处理器模块内存管理器
&rkisp_mmu {
	status = "okay";
};

//图像处理接口
&rkisp_vir0 {
	status = "okay";

	port {
		#address-cells = <1>;
		#size-cells = <0>;

		isp0_in: endpoint@0 {
			reg = <0>;
			remote-endpoint = <&dphy0_out>;
		};
	};
};

&i2c4 {
	/* i2c4 sda conflict with camera pwdn */
	status = "okay";

	/* 自动聚焦马达 */
    dw9714: dw9714@0c {
        compatible = "dongwoon,dw9714";
        status = "okay";
        reg = <0x0c>;
        rockchip,camera-module-index = <0>;
        rockchip,vcm-start-current = <10>;
        rockchip,vcm-rated-current = <85>;
        rockchip,vcm-step-mode = <5>;
        rockchip,camera-module-facing = "back";
 	};

	ov8858: ov8858@36 {
		status = "okay";
		compatible = "ovti,ov8858";
		reg = <0x36>;
		clocks = <&cru CLK_CIF_OUT>;
		clock-names = "xvclk";
		power-domains = <&power RK3568_PD_VI>;
		pinctrl-names = "default";
		pinctrl-0 = <&cif_clk>;
        reset-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_HIGH>;  /* 高电平有效 */
		pwdn-gpios = <&gpio4 RK_PB4 GPIO_ACTIVE_HIGH>;
		power-gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_HIGH>;
		rockchip,camera-module-index = <0>;
		rockchip,camera-module-facing = "back";
        rockchip,camera-module-name = "HS5885-BNSM1018-V01"; /* 这两个跟镜头有关 在external下直接搜 配置错误会导致图像颜色不对 */
		rockchip,camera-module-lens-name = "default";
		lens-focus = <&dw9714>; /* 关联马达 */
		port {
			ov8858_out: endpoint {
				remote-endpoint = <&dphy0_in>;  /* 4-lanes要用dphy0,1和2只支持split mode */
				data-lanes = <1 2 3 4>;
			};
		};
	};
};

这个代码是在ov5695的基础上修改的,有几个要点,接下来逐一说明。

首先就是添加ov8858的驱动,i2c地址是0x36compatible属性用来加载驱动,为ovti,ov8858
然后reset-gpios要默认初始化为高电平有效(GPIO_ACTIVE_HIGH),否则摄像头无法成功复位,也就检测不到了。

其实在ov5695的设备树配置里,reset-gpios的配置是为低电平有效的,也就是reset-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_LOW>;,我们来看一下ov8858上电复位这一块的驱动源码:

static int ov8858_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
    ...
	ov8858->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
	if (IS_ERR(ov8858->reset_gpio))
		dev_warn(dev, "Failed to get reset-gpios, maybe no use\n");
    ...
}

static int __ov8858_power_on(struct ov8858 *ov8858)
{
	...
	if (!IS_ERR(ov8858->reset_gpio))
		gpiod_set_value_cansleep(ov8858->reset_gpio, 0);

	ret = regulator_bulk_enable(OV8858_NUM_SUPPLIES, ov8858->supplies);
	if (ret < 0) {
		dev_err(dev, "Failed to enable regulators\n");
		goto disable_clk;
	}

	if (!IS_ERR(ov8858->reset_gpio))
		gpiod_set_value_cansleep(ov8858->reset_gpio, 1);
	...
}

当在设备树里设置为高电平有效时,probe函数里获取gpio时默认初始化reset_gpio为低电平,然后在__ov8858_power_on中先拉低复位,然后再转为高电平释放,完成复位。这样是满足ov8858的复位要求的。

但其实在ov8858数据手册里的描述是,复位引脚是低电平有效的,也就是拉低引脚表示复位

所以个人认为最规范的写法,还是在设备树里配置为低电平有效,然后修改ov8858的源码,probe函数里的不变,这样就是初始化reset_gpio为高电平,然后在__ov8858_power_on里先将reset_gpio设置为逻辑1,这样表示拉低复位,然后再将其设置为逻辑0,表示拉高释放,如下图。

在这里插入图片描述

所以这里其实有两种写法,第一种最简单,不动驱动源码,直接设备树里设置reset-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_HIGH>;;第二种个人认为较为规范,但是要修改一下驱动源码,参照上文。

rockchip,camera-module-namerockchip,camera-module-lens-name是ISP相关的文件,有了这个文件ISP才能根据对应sensor的参数进行工作。否则就会出现图像偏绿的情况。rk3566的ISP版本是ISP21,所以在目录SDK源码目录external/camera_engine_rkaiq/iqfiles/isp21下,有一个ov8858_HS5885-BNSM1018-V01_default.xml文件,就是根据这个文件名来配置的参数。

然后我们将ov8858配置成4-lanes,即full mode,链路改为csi2_dphy0->rkisp_vir0,ov8858是支持4-lanes的,但是ov5695不支持。

可以看一下驱动里的配置(ov8858.c)

可以看到,像素都是800万,但是4-lanes下的配置,帧率有30fps,而2-lanes只有15fps,所以我们要配置成4-lanes,来达到最高的帧率和像素。

再来聊聊rk3566配置mipi摄像头的链路,先来看下面这一张图,截自SDK源码目录/docs/Common/CAMERA/ISP2X下的Rockchip_Driver_Guide_VI_CN_v1.0.8.pdf文件。

在这里插入图片描述

物理dphy其实就是mipi数据和时钟接口,也就是开发板上定义的MIPI_CSI_DP/DN 0-3,MIPI_CSI_MCP/MCN,即四对数据线和一对时钟线,它可以拆分成三个逻辑dphy,分别为csi2_dphy0、csi2_dphy1、csi2_dphy2,可以应用于full mode或者split mode。

split mode就是将物理dphy拆分成两对csi2_dphy1和csi2_dphy2,能接两个2-lanes的mipi摄像头,csi2_dphy1对应的就是MIPI_CSI_DP/DN 0-1,csi2_dphy2对应MIPI_CSI_DP/DN 2-3,然后分别都有一对差分时钟线,不过泰山派只引出了一对差分时钟线,所以这个板子也就只能配置一个2-lanes的摄像头。

full mode就要用csi2_dphy0了,也就是一个摄像头同时接四对数据线和一对时钟线,显然,用了csi2_dphy0,就不能用csi2_dphy1和csi2_dphy2了,因为物理数据线都被占用。

物理dphy拆分成三个逻辑dphy,其实也就是用来区分是使用full mode还是使用split mode。

说了这么多,其实就是将ov8858的链路配置成csi2_dphy0->rkisp_vir0data-lanes改为<1 2 3 4>,配置成full mode。原来ov5695的链路是csi2_dphy1->rkisp_vir0,是split mode。

接下来就是自动聚焦马达驱动,添加dw9714节点,然后在ov8858的节点里要关联马达节点:lens-focus = <&dw9714>

单独编译内核,烧录内核进开发板,查看一下拓扑结构:

media-ctl -p -d /dev/media0

如果能看到以下信息,就说明马达配置成功了:

在这里插入图片描述

快速获取视频流

接下来我们再来研究一下如何抓取摄像头的图像。

一般来说,抓图就是操作/dev/videoX设备节点,但是我们不知道哪一个video才是我们的摄像头设备,我们先输下面的命令:

grep '' /sys/class/video4linux/video*/name

打印结果如下:

在这里插入图片描述

我们注意到video0的name是rkisp_mainpath,video1的name是rkisp_selfpath,这两个是什么意思呢?好,我们再来看下面这张图:

在这里插入图片描述

这是 rk356x 视频输入硬件处理流程框图,我们看到Sensor(摄像头传感器)是经过了ISP,然后输出了两个分支,一个是mainpath,一个是selfpath,这两个就是经过ISP处理的视频传输路径,并且这两个路径都可以输出视频帧。

因此,我们可以直接通过video0来抓取视频,可以用下面的命令:

v4l2-ctl -d /dev/video0 --stream-mmap=4 --stream-to=/data/out.yuv

/data/out.yuv是保存的视频文件,路径可以自行修改,保存后可以将这个文件复制到虚拟机下,用ffmpeg将其转为mp4格式就可以播放了。

ffmpeg -f rawvideo -pixel_format nv12 -video_size 3264x2448 -framerate 30 \
  -i ./out.yuv -c:v libx264 -pix_fmt yuv420p ./out.mp4

结语

以上就是泰山派移植ov8858的全部内容,刚好ov5695的那个厂家就有卖相同接口的ov8858,完全不用动硬件,用来积攒驱动移植经验还是非常合适的。不过遗憾的是泰山派没有将另一对差分时钟线引出来,不然就能接两个摄像头了,双路摄像头应用,想想都觉得非常酷!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐