泰山派移植OV8858驱动并添加自动聚焦功能
摘要 本文介绍了在泰山派开发板上移植OV8858摄像头驱动并添加自动聚焦功能的过程。作者分析了原厂适配的OV5695摄像头(500万像素)性能不足的问题,选择了支持800万像素的OV8858摄像头进行移植。文章详细描述了驱动添加步骤,包括内核配置、设备树修改等硬件适配工作,并提供了摄像头购买链接。该移植方案可与作者另一篇"泰山派网络AI摄像头"项目结合使用,实现更高性能的摄像头
前言
泰山派官方适配的摄像头是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地址是0x36,compatible属性用来加载驱动,为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-name和rockchip,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_vir0,data-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,完全不用动硬件,用来积攒驱动移植经验还是非常合适的。不过遗憾的是泰山派没有将另一对差分时钟线引出来,不然就能接两个摄像头了,双路摄像头应用,想想都觉得非常酷!
更多推荐


所有评论(0)