DVP

[English]

1. 概述

本节主要说明DVP的使用和适配方法,以及相关的宏定义。

2. 支持的功能

1、支持最大分辨率1280X720输入(输出);

2、只支持Sensor输出标准YUV422数据格式(大端模式):YUYV/UYVY/YYUV/UVYY;

3、默认VSYNC/HSYNC输入高有效,且支持翻转

4、支持直接输出YUV422数据到BUFF;

5、支持直接输出JPEG编码数据,或YUV422&JPEG数据分别到BUFF;

6、支持直接输出H264编码数据,或YUV422&H264数据分别到BUFF;

7、支持动态调节编码码率(JPEG或H264);

8、支持H264编码任意时刻重新生成IDR帧

9、支持Sensor数据接收错误检测

用户需要根据自己的应用方案,通过配置参数的方式,打开对应的功能

3. 相关宏定义

Marco name

info

CONFIG_DVP_CAMERA

enable dvp sensor function or not

CONFIG_DVP_CAMERA_I2C_ID

config dvp sensor communicate i2c id

CONFIG_DVP_CTRL_POWER_GPIO_ID

config the gpio id of control dvp vdd

CONFIG_JPEG_FRAME_SIZE

config a jpeg frame size

CONFIG_H264_FRAME_SIZE

config a h264 frame size

CONFIG_DVP_GC0308

enable gc0308 sensor function or not

CONFIG_DVP_HM1055

enable hm1055 sensor function or not

CONFIG_DVP_OV2640

enable ov2640 sensor function or not

CONFIG_DVP_SC101

enable sc101 sensor function or not

CONFIG_DVP_GC0328C

enable gc0328c sensor function or not

CONFIG_DVP_GC2145

enable gc2145 sensor function or not

备注

上面的sensor宏是已经适配好了,用户可以直接使用。都默认开启的,其他宏需要用户在编译时自己开启。但是会依赖CONFIG_DVP_CAMERA宏,所以需要用户在编译时自己开启。

4. 适配新摄像头

1.适配新的sensor用户需要在自己的工程下添加sensor的配置文件,具体可以参考 ./ap/components/bk_peripheral/src/dvp/dvp_gc2145.c 的实现方式。实现如下结构体的内容。需要根据sensor的实际情况,填充结构体中的内容。

2.sensor输出指定分辨率和帧率的方式是通过设置sensor的寄存器实现的,方案中是配置三个表实现,分别是: - 初始化表:用于初始化sensor的寄存器。 - 帧率表:用于设置sensor的帧率。 - 分辨率表:用于设置sensor的分辨率。

相关表内容的配置需要sensor厂商给出

3.按照上面的说明配置好相关参数后,需要将sensor的检测函数注册到系统中,具体可以参考 ./projects/dvp_example/ap/dvp_test/src/dvp_gc0001_test.c 的实现方式。注册如下结构体的内容。

const dvp_sensor_config_t *gc0001_detect_sensor(void)
{
    LOGI("%s\n", __func__);
    if (gc0001_detect())
    {
        return &dvp_sensor_gc0001;
    }

    return NULL;
}

BK_CAMERA_SENSOR_DETECT_SECTION(gc0001_detect_sensor);

4.在完成上述步骤后,需要将添加的sensor配置文件添加到编译系统中,参考 ./projects/dvp_example/ap/CMakeLists.txt 的实现方式。

set(GLOBAL_FUNCTION_SYMBOLS "")

list(APPEND srcs
    # other srcs
    dvp_test/src/dvp_gc0001_test.c
)

list(APPEND GLOBAL_FUNCTION_SYMBOLS "gc0001_detect_sensor")

foreach(symbol ${GLOBAL_FUNCTION_SYMBOLS})
    target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ${symbol}")
endforeach()

5.在完成上述步骤后,编译完成后会将添加的sensor检测方式注册到段中。用户在启动时会自动检测sensor,如果检测到sensor,会调用sensor的检测函数。

6.sensor的检测方法段默认会在ld文件中注册,但是有些工程可能有自己的ld文件,不采用SDK默认的ld文件,那么就需要在ld文件中添加如下内容:

.sensor_detect :
{
__camera_sensor_detect_array_start = .;
KEEP(*(.camera_sensor_detect_function_list.*))
__camera_sensor_detect_array_end = .;
} > FLASH

7.Sensor的配置结构体如下:``

/**
* @brief DVP传感器配置结构体
* @details DVP传感器的配置参数和函数指针
*/
typedef struct
{
    char *name;                     /**< 传感器名称 */
    media_ppi_t def_ppi;            /**< 传感器默认分辨率 */
    frame_fps_t def_fps;            /**< 传感器默认帧率 */
    mclk_freq_t  clk;               /**< 配置帧率和分辨率时的传感器工作时钟 */
    pixel_format_t fmt;             /**< 传感器输入数据格式 */
    sync_level_t vsync;             /**< 传感器垂直同步信号有效电平 */
    sync_level_t hsync;             /**< 传感器水平同步信号有效电平 */
    uint16_t id;                    /**< 传感器类型 (sensor_id_t) */
    uint16_t address;               /**< 传感器通过I2C写入寄存器的地址 */
    uint16_t fps_cap;               /**< 传感器支持的帧率 */
    uint16_t ppi_cap;               /**< 传感器支持的分辨率 */

    /**
    * @brief 自动检测使用的DVP传感器
    * @return 检测到传感器返回true,否则返回false
    */
    bool (*detect)(void);

    /**
    * @brief 初始化DVP传感器
    * @return 成功返回0,失败返回错误码
    */
    int (*init)(void);

    /**
    * @brief 设置传感器的分辨率
    * @param ppi 要设置的分辨率
    * @return 成功返回0,失败返回错误码
    */
    int (*set_ppi)(media_ppi_t ppi);

    /**
    * @brief 设置传感器的帧率
    * @param fps 要设置的帧率
    * @return 成功返回0,失败返回错误码
    */
    int (*set_fps)(frame_fps_t fps);

    /**
    * @brief 传感器断电或重置
    * @return 成功返回0,失败返回错误码
    */
    int (*power_down)(void);

    /**
    * @brief 读取传感器寄存器
    * @param reg 寄存器地址
    * @param val 存储寄存器值的指针
    * @return 成功返回0,失败返回错误码
    */
    int (*read_register)(uint32_t reg, uint32_t *val);

    /**
    * @brief 写入传感器寄存器
    * @param reg 寄存器地址
    * @param val 要写入的寄存器值
    * @return 成功返回0,失败返回错误码
    */
    int (*write_register)(uint32_t reg, uint32_t val);
} dvp_sensor_config_t;

一些参数的说明: * clk: 摄像头输入时钟,默认是24MHz,需要根据摄像头规格配置; * fmt: 摄像头输出数据格式,目前只支持YUV420,需要与摄像头输出顺序同步,默认是YUYV; * vsync: 摄像头输出vsync有效电平,有些摄像头在vsync低电平时输出有效数据,需要与摄像头vsync输出电平同步,默认是高电平有效; * hsync: 摄像头输出hsync有效电平,有些摄像头在hsync低电平时输出有效数据,需要与摄像头hsync输出电平同步,默认是高电平有效; * address: 摄像头寄存器地址,需要根据摄像头规格配置; * fps_cap: 摄像头支持的帧率表,需要配置对应的寄存器实现; * ppi_cap: 摄像头支持的分辨率表,需要配置对应的寄存器实现; * id: 摄像头类型,需要根据摄像头规格配置; * address: 摄像头寄存器地址,需要根据摄像头规格配置; * fps_cap: 摄像头支持的帧率表,需要配置对应的寄存器实现; * ppi_cap: 摄像头支持的分辨率表,需要配置对应的寄存器实现;

5. DVP摄像头的使用流程

5.1 DVP上电

在对dvp sensor使用之前需要对DVP进行上电操作,对于BK7258平台,需要配置 CONFIG_DVP_CTRL_POWER_GPIO_ID 为DVP的VDD引脚, 默认控制引脚为GPIO_28,将该引脚配置为输出模式,并且将其电平设置为高电平。

/**
* @brief DVP摄像头上电
*/
{
    #define GPIO_INVALID_ID           (0xFF)
    #ifdef CONFIG_DVP_CTRL_POWER_GPIO_ID
    #define DVP_POWER_GPIO_ID CONFIG_DVP_CTRL_POWER_GPIO_ID
    #else
    #define DVP_POWER_GPIO_ID GPIO_INVALID_ID
    #endif

    if (DVP_POWER_GPIO_ID != GPIO_INVALID_ID)
    {
        GPIO_UP(DVP_POWER_GPIO_ID);
    }
}

5.2 创建DVP摄像头控制器

调用如下接口创建DVP摄像头控制器:

avdk_err_t bk_camera_dvp_ctlr_new(bk_camera_ctlr_handle_t *handle, bk_dvp_ctlr_config_t *config);
描述:

创建DVP摄像头控制器,返回控制器句柄。

传入的配置包含两个部分:

第一部分:bk_dvp_config_t,包含DVP摄像头IO映射,I2C的配置,需要输出的分辨率帧率配置,需要输出的图像格式配置等。

第二部分:bk_dvp_callback_t,通过此回调函数申请存放目标数据的空间,以及返回图像数据给用户,用户自行管理目标数据。

备注

1.在DVP的回调函数中不能有任何的阻塞操作,也不能执行长时间的操作,否则会导致硬件中断被延时,进而导致图像异常。在下面的创建DVP摄像头控制器中会给出具体的示例,实例中回调函数只进行了简单的帧缓冲区申请释放操作,用户可以参考实现。 2.如果工作在编码模式下,会默认根据分辨率大下申请内存给硬件模块使用,在open的时候配置下去,在delete的时候释放掉。如果需要不重复申请释放这块内存,建议不做delete控制器的操作。

参数:

handle:DVP摄像头控制器句柄。

config:DVP摄像头控制器配置参数。

相关数据结构:bk_dvp_ctlr_config_t, bk_dvp_config_t, bk_dvp_callback_t

参考路径:ap/include/components/dvp_camera_types.h

bk_dvp_config_t支持如下默认配置:

/**
 * @brief DVP摄像头640x480分辨率30 FPS MJPEG格式配置
 * @details 常用DVP摄像头设置的预定义配置宏
 */

/* 宏定义中各参数说明:
 * i2c_config.id - I2C控制器ID
 * i2c_config.scl_pin - I2C时钟引脚
 * i2c_config.sda_pin - I2C数据引脚
 * i2c_config.baud_rate - I2C波特率(100kHz)
 * i2c_config.flags - I2C标志位(无特殊配置)
 * reset_pin - 复位引脚(0xFF表示未使用)
 * pwdn_pin - 电源关闭引脚(0xFF表示未使用)
 * io_config.data_width - 数据位宽度(8位)
 * io_config.data_pin - 数据引脚数组
 * io_config.vsync_pin - 垂直同步引脚
 * io_config.hsync_pin - 水平同步引脚
 * io_config.xclk_pin - 外部时钟引脚
 * io_config.pclk_pin - 像素时钟引脚
 * clk_source - 时钟源(24MHz)
 * width - 图像宽度(像素)
 * height - 图像高度(像素)
 * fps - 帧率(帧/秒)
 * img_format - 图像格式(MJPEG)
 * user_data - 用户数据指针(无)
 */
#define BK_DVP_864X480_30FPS_MJPEG_CONFIG()\
{\
    .i2c_config = {\
        .id = 1,\
        .scl_pin = 0,\
        .sda_pin = 1,\
        .baud_rate = 100000,\
        .flags = 0,\
    },\
    .reset_pin = 0xFF,\
    .pwdn_pin = 0xFF,\
    .io_config = {\
        .data_width = SENSOR_BITS_WIDTH_8BIT,\
        .data_pin = {32, 33, 34, 35, 36, 37, 38, 39},\
        .vsync_pin = 31,\
        .hsync_pin = 30,\
        .xclk_pin = 27,\
        .pclk_pin = 29,\
    },\
    .clk_source = MCLK_24M,\
    .width = 640,\
    .height = 480,\
    .fps = 30,\
    .img_format = IMAGE_MJPEG,\
    .user_data = NULL,\
}

// 建议用户针对申请的帧,根据图像格式选择自己维护的链表中空闲的节点,与下面complete的操作维护两个链表,一个是free链表,一个是ready链表。
// 每次申请时从free链表中取出一个节点,申请完成后将节点加入到ready链表中,需要使用时从ready链表中取出,使用后直接释放到free链表中。
// 具体可以参考: ``./projects/dvp_example/dvp_test/src/dvp_frame_list.c``
/**
 * @brief 根据图像格式分配不同类型的帧缓冲区并初始化帧结构成员
 *
 * @param format 图像格式(YUV或其他格式如MJPEG/H264等)
 * @param size 要分配的缓冲区大小(字节)
 * @return 成功返回帧缓冲区指针,失败返回NULL
 */
static frame_buffer_t *dvp_frame_malloc(image_format_t format, uint32_t size)
{
    // 从DVP帧队列中申请帧缓冲区
    frame_buffer_t *frame = dvp_frame_queue_malloc(format, size);

    if (frame)
    {
        frame->sequence = 0;
        frame->length = 0;
        frame->timestamp = 0;
        frame->size = size;
    }

    return frame;
}


/**
 * @brief 通知DVP摄像头数据处理完成,释放帧缓冲区
 *
 * @param format 图像格式(YUV或其他格式如MJPEG/H264等)
 * @param frame 指向帧缓冲区的指针,包含要释放的帧数据
 * @param result 处理结果,0表示成功,非0表示失败,成功时可以存入用户自己维护的链表,失败时建议直接释放
 */
static void dvp_frame_complete(image_format_t format, frame_buffer_t *frame, int result)
{
    if (frame->sequence % 30 == 0)
    {
        LOGD("%s: seq:%d, length:%d, format:%s, h264_type:%x, result:%d\n", __func__, frame->sequence,
            frame->length, (format == IMAGE_MJPEG) ? "mjpeg" : (format == IMAGE_H264) ? "h264" : "YUV", frame->h264_type, result);
    }

    if (result != AVDK_ERR_OK)
    {
        // 图像有问题,直接释放帧缓冲区,加入到free链表中
        dvp_frame_queue_free(format, frame);
    }
    else
    {
        // 图像正常,加入到ready链表中,等待用户使用
        dvp_frame_queue_complete(format, frame);
    }
}

static const bk_dvp_callback_t dvp_cbs = {
    .malloc = dvp_frame_malloc,
    .complete = dvp_frame_complete,
};

// create dvp controller
{
    bk_dvp_ctlr_config_t dvp_ctrl_config = {
        .config = BK_DVP_864X480_30FPS_MJPEG_CONFIG(),
        .cbs = &dvp_cbs,
    };

    // you can change the config here
    //dvp_ctrl_config.config.img_format = output_format;
    //dvp_ctrl_config.config.width = ppi >> 16;
    //dvp_ctrl_config.config.height = ppi & 0xFFFF;
    // new
    ret = bk_camera_dvp_ctlr_new(&s_dvp_handle, &dvp_ctrl_config);
}

5.3 打开DVP摄像头

调用如下接口打开DVP摄像头:

avdk_err_t bk_camera_open(bk_camera_ctlr_handle_t handle);
描述:

打开DVP摄像头控制器,返回错误码。

打开DVP摄像头后,上面注册的回调函数会不停的申请数据空间用以存储。

打开DVP摄像头后,会开始采集数据,且会调用上面注册的回调函数,将采集到的数据返回给用户,用户需要关注frame是否有效,选择使用还是释放。

参数:

handle:DVP摄像头控制器句柄。

5.4 DVP Ioctl

调用如下接口进行DVP的IOCTL操作:

avdk_err_t bk_camera_ioctl(bk_camera_ctlr_handle_t handle, uint32_t cmd, void *arg)
描述:

DVP的IOCTL操作,支持的命令如下: - DVP_IOCTL_CMD_H264_IDR_RESET:重新生产IDR帧,必须确保DVP的工作模式是H264_MODE或H264_YUV_MODE模式,才能生效。 - DVP_IOCTL_CMD_SENSOR_WRITE_REGISTER:写入传感器寄存器。 - DVP_IOCTL_CMD_SENSOR_READ_REGISTER:读取传感器寄存器。

参数:

handle:DVP摄像头控制器句柄。

cmd:命令码,支持的命令如上所示。

arg:参数,用户传入参数,根据命令不同,参数不同。

5.5 关闭DVP摄像头

调用如下接口关闭DVP摄像头:

avdk_err_t bk_camera_close(bk_camera_ctlr_handle_t handle);
描述:

关闭DVP摄像头控制器。

关闭DVP摄像头后,上面注册的回调函数会停止申请数据空间,也会停止采集数据。

参数:

handle:DVP摄像头控制器句柄。

5.6 注销DVP摄像头控制器

调用如下接口注销DVP摄像头控制器:

avdk_err_t bk_camera_delete(bk_camera_ctlr_handle_t handle);
描述:

注销DVP摄像头控制器。

释放控制器申请的内存空间,只有当dvp成功关闭时才允许被调用。

参数:

handle:DVP摄像头控制器句柄。

5.7 DVP Power Off

将控制DVP LDO的GPIO拉低即可关闭DVP摄像头电源。

{
    // power off dvp camera
    if (DVP_POWER_GPIO_ID != GPIO_INVALID_ID)
    {
        GPIO_DOWN(DVP_POWER_GPIO_ID);
    }
}

6. DVP支持方案

因为DVP支持输出各种格式的数据,所以需要用户根据自己的需求,选择合适的输出格式。

1.DVP直接输出YUV图像,联合显示模块进行显示。 2.DVP直接输出MJPEG图像,再联合WiFi模块进行图传。 3.DVP直接输出H264图像,再联合WiFi模块进行图传。 4.DVP直接输出MJPEG&YUV图像,再联合WiFi模块进行图传,联合显示模块进行显示。 5.DVP直接输出H264&YUV图像,再联合WiFi模块进行图传,联合显示模块进行显示。