Zephyr Video驱动之驱动添加

Creative Commons
本作品采用知识共享署名

本文以摄像头OV7725为例说明如何添加一个新的Video驱动到Zephyr。

通过前面几篇文章我们对Zephyr的视频驱动的模型使用和实现有一个大致的了解,本文通过OV7725在Zephyr上驱动的实现来说明如何添加新的Video驱动。
本文只说明实现Video驱动中和Zephyr驱动Video模型相关联的部分,不说明如何设置OV7725寄存器等硬件上的操作。

硬件接口分析

硬件上使用开发板mm_swiftio搭配正点原子的OV7725。
OV7725是一颗VGA CMOS的摄像头,硬件接口可分为下面四类:

  • CSI接口:必须,包括D0~D7, PCLK, VYSNC,HSYNC。用于读取OV7725捕获的图像数据。
  • SCCB接口:必须,包括SDA和SCL。用于读写OV7725寄存器,对OV7725进行控制,例如初始化硬件,设置画幅,格式,饱和度,亮度等等。
  • Reset:必须,只有一个硬件引脚,用于硬件复位OV7725
  • 外部clock: 非必须,包含一个CTRL和一个MCLK, 正点原子的OV7725模块带有时钟产生,可以不需要外部clock。 CTRL默认上拉选择内部Clock。这里我们不使用。

mm_swiftio使用rt1052为SOC,和OV7725对接的硬件接口:

  • CSI接口
  • 1个I2C接口,在Zephyr驱动SCCB设备一文中有说明Zephyr下可以用I2C驱动SCCB
  • 1个GPIO用于硬件复位

RT1052的CSI Video驱动在zephyr已经实现,驱动OV7725只需要将OV7725作为senor添加即可。

驱动添加流程

1. 增加设备树绑定

Zephyr内目前没有支持OV7725,要根据OV7725的硬件添加新的设备树绑定,因此需要进行添加才能在设备树中进行使用。对于Sensor来说CSI out是以port的形式和CSI in交互, 这里相关的硬件就只有1个I2C和1个GPIO.
在dts/bindings/video下添加文件omnivision,ov7725.yaml,绑定文件的文件名由厂商名和器件名组成,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
description: OV7725 CMOS video sensor

compatible: "ovti,ov7725"

properties:
reset-gpios:
type: phandle-array
required: false
description: |
The RESETn pin is asserted to disable the sensor causing a hard
reset. The sensor receives this as an active-low signal.
include: i2c-device.yaml

compatible提供绑定名,dts中会引用。properties中的reset-gpios提供硬件复位引脚的绑定。I2C的绑定通过Include i2c-device.yaml来完成

2. 驱动代码添加

源文件

driver/video/下增加文件ov7725.c,里面包含的驱动ov7725的驱动代码,为了使驱动能够访问设备树,需要在文件开头放置如下内容

1
2
3
#define DT_DRV_COMPAT ovti_ov7725
#include <zephyr.h>
#include <device.h>

其中DT_DRV_COMPAT定义非常重要,C对设备树中的节点和属性的访问是依靠宏生成的DT_DRV_COMPAT的内容就是设备树中设备区别于其它设备的关键内容,其内容是compatible的值将 , 转化为 _ . 例如这里ovti,ov7725被变为ovti_ov7725。
驱动的实现模式和mt9m114一样,目前主要实现了下面API并注册,由于Zephyr演进很快在我提交PR的时候,review已经要求从DEVICE_AND_API_INIT修改为DEVICE_DT_INST_DEFINE, 这点和前面的文章列举的示例代码有所差别。

1
2
3
4
5
6
7
8
9
10
11
12
static const struct video_driver_api ov7725_driver_api = {
.set_format = ov7725_set_fmt,
.get_format = ov7725_get_fmt,
.get_caps = ov7725_get_caps,
.stream_start = ov7725_stream_start,
.stream_stop = ov7725_stream_stop,
};

DEVICE_DT_INST_DEFINE(0, &ov7725_init_0, device_pm_control_nop,
&ov7725_data_0, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&ov7725_driver_api);

重点分析Zephyr下驱动实现的特点,将配置硬件的部分省略掉。在注册ov7725 device驱动后,Zephyr系统初始化的时候会自动调用ov7725_init_0对ov7725驱动进行初始化,具体流程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct ov7725_data {
const struct device *i2c;
const struct device *reset_gpio;
uint8_t reset_pin;
gpio_dt_flags_t reset_flags;
struct video_format fmt;
uint8_t i2c_addr;
};

static int ov7725_init_0(const struct device *dev)
{
struct ov7725_data *drv_data = dev->data;
char *gpio_name = DT_INST_GPIO_LABEL(0, reset_gpios); //获取reset gpio device name

drv_data->reset_pin = DT_INST_GPIO_PIN(0, reset_gpios), //获取reset gpio device pin
drv_data->reset_flags = DT_INST_GPIO_FLAGS(0, reset_gpios), //获取reset gpio flags

//获取用于reset的GPIO handle
if (gpio_name) {
drv_data->reset_gpio = device_get_binding(gpio_name);
if (drv_data->reset_gpio == NULL) {
LOG_ERR("Failed to get pointer to %s device!",
gpio_name);
}
}

//获取用于SCCB的i2c handle
drv_data->i2c = device_get_binding(DT_INST_BUS_LABEL(0));
if (drv_data->i2c == NULL) {
LOG_ERR("Failed to get pointer to %s device!",
DT_INST_LABEL(0));
return -EINVAL;
}
//获取ov7725的I2C Salver地址
drv_data->i2c_addr = DT_INST_REG_ADDR(0);

//对ov7725进行初始化
return ov7725_init(dev);
}

ov7725_init对ov7725进行初始化就是使用GPIO对ov7725的reset引脚进行操作和使用I2C对ov7725的寄存器进行配置。与硬件相关的配置不做说明,可以参考文末PR连接。
目前实现非常简单,sersor只提供一种格式ov7725_get_caps给出的caps如下,ov7725支持的格式还很多,可以根据需要再进行添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static const struct video_format_cap fmts[] = {
{
.pixelformat = VIDEO_PIX_FMT_RGB565,
.width_min = 640,
.width_max = 640,
.height_min = 480,
.height_max = 480,
.width_step = 0,
.height_step = 0,
},
{ 0 }
};

static int ov7725_get_caps(const struct device *dev,
enum video_endpoint_id ep,
struct video_caps *caps)
{
caps->format_caps = fmts;
return 0;
}

ov7725_set_fmt会将符合caps的格式进行设置,并将格式保存在驱动内,使用ov7725_get_fmt时将保存的格式送出,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static int ov7725_set_fmt(const struct device *dev,
enum video_endpoint_id ep,
struct video_format *fmt)
{
struct ov7725_data *drv_data = dev->data;


/* we only support one format for now (VGA RGB565) */
if (fmt->pixelformat != VIDEO_PIX_FMT_RGB565 || fmt->height != 480 ||
fmt->width != 640) {
return -ENOTSUP;
}

width = fmt->width;
height = fmt->height;

if (!memcmp(&drv_data->fmt, fmt, sizeof(drv_data->fmt))) {
/* nothing to do */
return 0;
}

drv_data->fmt = *fmt;

//在这之后是使用I2C读写ov7725的寄存器流程,不再列出
}

static int ov7725_get_fmt(const struct device *dev,
enum video_endpoint_id ep,
struct video_format *fmt)
{
struct ov7725_data *drv_data = dev->data;

*fmt = drv_data->fmt;

return 0;
}

这是一个简单的驱动实现示例,在ov7725初始化完成后,ov7725就开始工作了,没有提供start和stop的功能,因此ov7725_stream_startov7725_stream_stop留空不做任何事情

1
2
3
4
5
6
7
8
9
static int ov7725_stream_start(const struct device *dev)
{
return 0;
}

static int ov7725_stream_stop(const struct device *dev)
{
return 0;
}

到这里驱动的代码部分就已经全部完成了。

构建配置修改

在driver/video/下增加Kconfig.ov7725文件,用于配置启用OV7725,内容如下

1
2
3
4
5
config VIDEO_OV7725
bool "OV7725 CMOS digital image sensor"
depends on I2C
help
Enable driver for OV7725 CMOS digital image sensor device.

在drivers/video/Kconfig中增加

1
source "drivers/video/Kconfig.ov7725"

在drivers/video/CMakeLists.txt中增加如下内容,将ov7725.c加入构建

1
zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7725	ov7725.c)

使用

以mm_swiftio为例说明如何使用OV7725

修改DTS

在boards/arm/mm_swiftio/mm_swiftio.dts中添加如下内容,和前面文章mt9m114区别不大,只多一个reset-gpios = <&gpio2 20 GPIO_ACTIVE_HIGH>;用于指定gpio2.20作为reset引脚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
&lpi2c3 {
status = "okay";

ov7725@21 {
compatible = "ovti,ov7725";
reg = <0x21>;
label = "OV7725";
status = "okay";

reset-gpios = <&gpio2 20 GPIO_ACTIVE_HIGH>;

port {
ov7725_ep_out: endpoint {
remote-endpoint = <&csi_ep_in>;
};
};
};
};

&csi {
status = "okay";
sensor-label = "OV7725";

port {
csi_ep_in: endpoint {
remote-endpoint = <&ov7725_ep_out>;
};
};
};

添加PIN Mux

不同的开发板方式不一样,mm_swiftio需要在boards/arm/mm_swiftio/pinmux.c中将CSI使用的IO配置为CSI功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#if DT_NODE_HAS_STATUS(DT_NODELABEL(csi), okay) && CONFIG_VIDEO
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_04_CSI_PIXCLK,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_05_CSI_MCLK,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B0_14_CSI_VSYNC,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B0_15_CSI_HSYNC,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_08_CSI_DATA09,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_09_CSI_DATA08,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_10_CSI_DATA07,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_11_CSI_DATA06,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_12_CSI_DATA05,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_13_CSI_DATA04,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_14_CSI_DATA03,
0U);
IOMUXC_SetPinMux(
IOMUXC_GPIO_AD_B1_15_CSI_DATA02,
0U);
#endif

测试程序

这里测试程序就直接借用Zephyr的Sample了,新增文件samples/video/capture/boards/mm_swiftio.conf ,内容为

1
2
CONFIG_VIDEO_OV7725=y
CONFIG_I2C=y

然后使用west build -b mm_swiftio samples/video/capture 编译出结果即可运行测试。

参考

OV7725的支持已提交PR,正在接受review修改,这个版本的只是很基础的功能,以后会根据实际需求逐步添加
https://github.com/zephyrproject-rtos/zephyr/pull/30744/files