本篇文档中所使用的芯片是瑞芯微的RV1106G2, 使用的SDK 是由LUCKFOX所提供
SDK环境搭建
由于官方提供的摄像头是SC3336 3MP Camera (A),这个摄像头并不是一个全景摄像头,刚好手上有一个OV2685的摄像头,所以开始尝试驱动OV2685,第一次在linux玩摄像头,所以记录一下过程和问题
linux驱动
一般来说linux系统源码中集成了很多的摄像头的驱动,拿luckfox的sdk来说,其所在的位置是:
luckfox-pico/sysdrv/source/kernel/drivers/media/i2c/ov2685.c
由于使用I2C作为控制接口,所以在I2C目录下,如果你的摄像头没有对应的驱动文件,那么就要根据摄像头的数据手册按照驱动文件的格式添加驱动
menuconfig
在知道有驱动文件后,那么只需要在内核配置里面打开,具体的配置方法luckFox教程里面有,这里只说一下要配置的一些东西,在ov2685.c的同级目录下有一个Kconfig的文件,告诉了每种摄像头需要的一些依赖,下面是ov2685的
config VIDEO_OV2685
tristate "OmniVision OV2685 sensor support"
depends on VIDEO_V4L2 && I2C
select MEDIA_CONTROLLER
select V4L2_FWNODE
help
This is a Video4Linux2 sensor driver for the OmniVision
OV2685 camera.
To compile this driver as a module, choose M here: the
module will be called ov2685.
可以知道要打开的有OmniVision OV2685 VIDEO_V4L2 I2C MEDIA_CONTROLLER V4L2_FWNODE,依次找到使能即可,打开之后再重新编译内核,就可以得到ov2685.ko文件
有了.ko后,就可以使用insmod 加载,但在这之前要在设备数据添加节点
配置设备树
&csi2_dphy_hw {
status = "okay";
};
&csi2_dphy0 {
status = "okay";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
csi_dphy_input0: endpoint@0 {
reg = <0>;
remote-endpoint = <&sc3336_out>;
data-lanes = <1 2>;
};
csi_dphy_input1: endpoint@1 {
reg = <1>;
remote-endpoint = <&sc4336_out>;
data-lanes = <1 2>;
};
csi_dphy_input2: endpoint@2 {
reg = <2>;
remote-endpoint = <&ov2685_out>;
data-lanes = <1>;
};
};
port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
csi_dphy_output: endpoint@0 {
reg = <0>;
remote-endpoint = <&mipi_csi2_input>;
};
};
};
};
&i2c4 {
status = "okay";
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&i2c4m2_xfer>;
sc3336: sc3336@30 {
compatible = "smartsens,sc3336";
status = "okay";
reg = <0x30>;
clocks = <&cru MCLK_REF_MIPI0>;
clock-names = "xvclk";
pwdn-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&mipi_refclk_out0>;
rockchip,camera-module-index = <0>;
rockchip,camera-module-facing = "back";
rockchip,camera-module-name = "CMK-OT2119-PC1";
rockchip,camera-module-lens-name = "30IRC-F16";
port {
sc3336_out: endpoint {
remote-endpoint = <&csi_dphy_input0>;
data-lanes = <1 2>;
};
};
};
sc4336: sc4336@30 {
compatible = "smartsens,sc4336";
status = "okay";
reg = <0x30>;
clocks = <&cru MCLK_REF_MIPI0>;
clock-names = "xvclk";
pwdn-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&mipi_refclk_out0>;
rockchip,camera-module-index = <0>;
rockchip,camera-module-facing = "back";
rockchip,camera-module-name = "OT01";
rockchip,camera-module-lens-name = "40IRC_F16";
port {
sc4336_out: endpoint {
remote-endpoint = <&csi_dphy_input1>;
data-lanes = <1 2>;
};
};
};
ov2685: camera-sensor@3c {
compatible = "ovti,ov2685";
reg = <0x3c>;
pinctrl-names = "default";
pinctrl-0 = <&mipi_refclk_out0>;
clocks = <&cru MCLK_REF_MIPI0>;
clock-names = "xvclk";
reset-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_LOW>;
rockchip,camera-module-index = <1>;
rockchip,camera-module-facing = "back";
rockchip,camera-module-name = "YT-RV1106-2-V1";
port {
ov2685_out: endpoint {
remote-endpoint = <&csi_dphy_input2>;
data-lanes = <1>;
};
};
};
};
&i2s0_8ch {
#sound-dai-cells = <0>;
status = "okay";
};
&mipi0_csi2 {
status = "okay";
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 = <&csi_dphy_output>;
};
};
port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
mipi_csi2_output: endpoint@0 {
reg = <0>;
remote-endpoint = <&cif_mipi_in>;
};
};
};
};
&rkcif {
status = "okay";
};
&rkcif_mipi_lvds {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&mipi_pins>;
port {
/* MIPI CSI-2 endpoint */
cif_mipi_in: endpoint {
remote-endpoint = <&mipi_csi2_output>;
};
};
};
&rkcif_mipi_lvds_sditf {
status = "okay";
port {
/* MIPI CSI-2 endpoint */
mipi_lvds_sditf: endpoint {
remote-endpoint = <&isp_in>;
};
};
};
&rkisp {
status = "okay";
};
&rkisp_vir0 {
status = "okay";
port@0 {
isp_in: endpoint {
remote-endpoint = <&mipi_lvds_sditf>;
};
};
};
以上是实际设备树的一部分
数据从摄像头到实际图像需要经过的步骤
[摄像头] ——->(raw图像数据) ——->[mipi总线] ——->[图像处理芯片(如ISP)] ——-> (NV16/NV12等格式图像数据)
所以在设备树里面就要实现这个结构,
ov2685_out—>csi_dphy0_input ----> csi_dphy0_output ----> mipi_csi2_input -> mipi_csi2_output—>isp
这一个结构基本上不用自己实现,我照着之前的框架把其中一个支持的摄像头节点改为ov2685即可,还有对应的I2C地址和reset-gpios用自己的即可
添加好了之后,便可以使用,编译内核然后烧录到rv1106开发板
使用insmod加载模块
v4l2
使用v4l2框架去获取图像,V4L2(Video for Linux 2):Linux内核中视频设备中的驱动框架,对于应用层它提供了一系列的API接口,同时对于硬件层,它适配大部分的视频设备,因此通过调用V4L2的接口函数可以适配大部分的视频设备。
如果ov2685.ko加载成功那么,通过V4L2就能检测到video设备,通过命令行
v4l2-ctl --list-devices
能够看到正常出现vedio0到vedio10 表示注册加载正常
获取影像
使用v4l2-ctl工具去获取
v4l2-ctl -d /dev/video12 --set-fmt-video=width=800,height=800,pixelformat=NV12 --stream-mmap=3 --stream-skip=10 --stream-to=/gc5035.raw --stream-count=1 --stream-poll
碰到的问题
1.ISP无法正常同步
并且v4l2 不能够看到正常出现vedio0到vedio10,于是用dmesg打开日志,摄像头注册过程确实出现了一些问题
我查了一下原因,是因为获取frame_interval失败才导致的,看了下ov2685.c里面也确实没有相关的代码,于是我对比了一下ov2680的驱动文件,发现ov2680.c里面是注册了这个驱动函数的,于是我在ov2685.c也添加并注册了帧概率获取函数:
static int ov2685_s_g_frame_interval(struct v4l2_subdev *sd,
struct v4l2_subdev_frame_interval *fi)
{
struct v4l2_fract frame_interval;
frame_interval.numerator = 1;
frame_interval.denominator = 30;
fi->interval= frame_interval;
return 0;
}
static const struct v4l2_subdev_video_ops ov2685_video_ops = {
.s_stream = ov2685_s_stream,
.g_frame_interval = ov2685_s_g_frame_interval,
.s_frame_interval = ov2685_s_g_frame_interval,
};
具体ov2685.c为什么不注册这个函数我没有深究,因为摄像头确实跑起来了。
2.使用MIPI 2通道无法正常获取图像
出现select timeout
错误
解决方法,在设备树里面改动,当然也有可能是其它原因
port {
ov2685_out: endpoint {
remote-endpoint = <&csi_dphy_input2>;
data-lanes = <1>;
};
};
本来是data-lanes = <1,2>,因为我硬件上使用的是2lane ,手册上也说了可以使用2lane,但是文档里面给的示例却是1通道
不过确实改为1通道才能正常使用,不过我觉得2通道也能正常使用,不过需要对摄像头传感器进行配置。
参考:
https://www.ebaina.com/articles/140000017027
如果有问题可以发送到我的邮箱 qq:1074485750