DVP 驱动

基于 lisa_device 框架的 DVP(Digital Video Port)设备驱动,为 ARCS 平台提供统一的数字视频接口,主要用于连接摄像头等图像采集设备。

功能特性

  • 设备支持: 支持 DVP0 设备

  • 传输模式: 支持普通模式和 Ping-Pong 双缓冲模式

  • 数据格式: 支持 YUV422 和 8 位亮度(Lumina)输入格式

  • DMA 传输: 集成 GPDMA 实现高效的数据传输

  • 时钟输出: 支持可配置的主时钟输出(MCLK),用于驱动摄像头

  • 缓冲区重载: 支持运行时动态重载缓冲区,实现连续捕获

  • 线程安全: 内部使用互斥锁保护,可在多线程环境中使用

配置选项

prj.conf 中启用驱动:

CONFIG_LISA_DVP_DEVICE=y

API 接口

配置接口

int lisa_dvp_setup(lisa_device_t *dev, const lisa_dvp_config_t *config,
                   lisa_dvp_callback_t callback, void *user_data);

重要: 必须先调用 lisa_dvp_setup() 配置设备后,才能使用下面的功能 API。

控制接口

int lisa_dvp_start(lisa_device_t *dev, void *buf, uint32_t len);
int lisa_dvp_start_pingpong(lisa_device_t *dev, void *ping_buf, void *pong_buf, uint32_t len);
int lisa_dvp_stop(lisa_device_t *dev);

缓冲区重载接口

int lisa_dvp_reload(lisa_device_t *dev, void *buf, uint32_t len);
int lisa_dvp_reload_pingpong(lisa_device_t *dev, void *buf);

在回调函数中调用,用于重载下一帧的缓冲区地址。

时钟控制接口

int lisa_dvp_enable_clockout(lisa_device_t *dev, uint32_t clock);

启用 DVP 时钟输出,为摄像头提供主时钟。

使用示例

普通模式捕获单帧

#include "lisa_device.h"
#include "lisa_dvp.h"

// 定义接收缓冲区(需要足够大以容纳一帧数据)
static uint8_t frame_buffer[640 * 480 * 2] __attribute__((aligned(4)));

// DVP 事件回调函数
void dvp_callback(lisa_dvp_event_t event, void *user_data)
{
    if (event == LISA_DVP_EVENT_DONE) {
        // 一帧数据接收完成
        // 在此处理图像数据 frame_buffer
        // ...

        // 如需继续捕获下一帧,重载缓冲区
        lisa_device_t *dvp_dev = (lisa_device_t *)user_data;
        lisa_dvp_reload(dvp_dev, frame_buffer, sizeof(frame_buffer));
    }
}

int main(void)
{
    // 1. 获取 DVP 设备
    lisa_device_t *dvp_dev = lisa_device_get(LISA_DVP0_NAME);
    if (!dvp_dev) {
        return -1;
    }

    // 2. 配置 DVP 参数
    lisa_dvp_config_t config = {
        .dvp_hal_config = {
            .frame_width    = 640,
            .frame_height   = 480,
            .pixel_offset   = 0,
            .line_offset    = 0,
            .input_format   = LISA_DVP_INPUT_FORM_YUV422_Y0CBY1CR,
            .data_align     = LISA_DVP_DATA_ALIGN_LEFT,
            .vsync_polarity = LISA_DVP_POL_RISING,
            .hsync_polarity = LISA_DVP_POL_RISING,
            .pclk_polarity  = LISA_DVP_POL_RISING,
        },
        .gpdma_ch = 2,  // 使用 GPDMA 通道 2
    };

    // 3. 初始化 DVP 设备
    int ret = lisa_dvp_setup(dvp_dev, &config, dvp_callback, dvp_dev);
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    // 4. 启用时钟输出(为摄像头提供 25MHz 时钟)
    ret = lisa_dvp_enable_clockout(dvp_dev, 25000000);
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    // 5. 启动 DVP 捕获
    ret = lisa_dvp_start(dvp_dev, frame_buffer, sizeof(frame_buffer));
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    // 等待捕获完成(在回调函数中处理)
    // ...

    return 0;
}

Ping-Pong 模式连续捕获

#include "lisa_device.h"
#include "lisa_dvp.h"

// 定义两个缓冲区用于 Ping-Pong 模式
static uint8_t ping_buffer[640 * 480 * 2] __attribute__((aligned(4)));
static uint8_t pong_buffer[640 * 480 * 2] __attribute__((aligned(4)));

// DVP 事件回调函数
void dvp_pingpong_callback(lisa_dvp_event_t event, void *user_data)
{
    lisa_device_t *dvp_dev = (lisa_device_t *)user_data;

    if (event == LISA_DVP_EVENT_PING_DONE) {
        // Ping 缓冲区接收完成,处理 ping_buffer 数据
        // ...

        // 重载 Ping 缓冲区用于下一次捕获
        lisa_dvp_reload_pingpong(dvp_dev, ping_buffer);

    } else if (event == LISA_DVP_EVENT_PONG_DONE) {
        // Pong 缓冲区接收完成,处理 pong_buffer 数据
        // ...

        // 重载 Pong 缓冲区用于下一次捕获
        lisa_dvp_reload_pingpong(dvp_dev, pong_buffer);
    }
}

int main(void)
{
    // 1. 获取 DVP 设备
    lisa_device_t *dvp_dev = lisa_device_get(LISA_DVP0_NAME);
    if (!dvp_dev) {
        return -1;
    }

    // 2. 配置 DVP 参数(同上)
    lisa_dvp_config_t config = {
        .dvp_hal_config = {
            .frame_width    = 640,
            .frame_height   = 480,
            .pixel_offset   = 0,
            .line_offset    = 0,
            .input_format   = LISA_DVP_INPUT_FORM_YUV422_Y0CBY1CR,
            .data_align     = LISA_DVP_DATA_ALIGN_LEFT,
            .vsync_polarity = LISA_DVP_POL_RISING,
            .hsync_polarity = LISA_DVP_POL_RISING,
            .pclk_polarity  = LISA_DVP_POL_RISING,
        },
        .gpdma_ch = 2,
    };

    // 3. 初始化 DVP 设备
    int ret = lisa_dvp_setup(dvp_dev, &config, dvp_pingpong_callback, dvp_dev);
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    // 4. 启用时钟输出
    ret = lisa_dvp_enable_clockout(dvp_dev, 25000000);
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    // 5. 启动 Ping-Pong 模式捕获
    ret = lisa_dvp_start_pingpong(dvp_dev, ping_buffer, pong_buffer, sizeof(ping_buffer));
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    // Ping-Pong 模式会自动交替使用两个缓冲区
    // 在回调函数中处理并重载缓冲区即可实现连续捕获
    // ...

    return 0;
}

8 位灰度图像捕获

#include "lisa_device.h"
#include "lisa_dvp.h"

// 灰度图像缓冲区(每像素 1 字节)
static uint8_t gray_buffer[640 * 480] __attribute__((aligned(4)));

void dvp_gray_callback(lisa_dvp_event_t event, void *user_data)
{
    if (event == LISA_DVP_EVENT_DONE) {
        // 灰度图像接收完成,处理 gray_buffer
        // ...
    }
}

int main(void)
{
    lisa_device_t *dvp_dev = lisa_device_get(LISA_DVP0_NAME);
    if (!dvp_dev) {
        return -1;
    }

    // 配置为 8 位亮度格式
    lisa_dvp_config_t config = {
        .dvp_hal_config = {
            .frame_width    = 640,
            .frame_height   = 480,
            .pixel_offset   = 0,
            .line_offset    = 0,
            .input_format   = LISA_DVP_INPUT_FORM_LUMINA_8BIT,  // 8 位灰度
            .data_align     = LISA_DVP_DATA_ALIGN_RIGHT,
            .vsync_polarity = LISA_DVP_POL_RISING,
            .hsync_polarity = LISA_DVP_POL_RISING,
            .pclk_polarity  = LISA_DVP_POL_FALLING,
        },
        .gpdma_ch = 2,
    };

    int ret = lisa_dvp_setup(dvp_dev, &config, dvp_gray_callback, dvp_dev);
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    ret = lisa_dvp_enable_clockout(dvp_dev, 25000000);
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    ret = lisa_dvp_start(dvp_dev, gray_buffer, sizeof(gray_buffer));
    if (ret != LISA_DEVICE_OK) {
        return -1;
    }

    return 0;
}

硬件配置

引脚复用配置

DVP 驱动在初始化时会自动调用板型目录中定义的 lisa_dvp_pinmux() 函数,用于配置引脚复用。

配置位置:

  • 定义: boards/<板型名>/pinmux.c 中实现函数

  • 声明: boards/<板型名>/pinmux.h 中声明函数

  • 调用时机: 设备初始化时自动调用

示例 (参考 boards/arcs_evb/pinmux.c:32-46):

__attribute__((weak)) void lisa_dvp_pinmux()
{
    // HSYNC: PAD_A 10
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 10, 16);
    // VSYNC: PAD_A 11
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 11, 16);
    // PCLK: PAD_A 12
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 12, 16);
    // D0-D7: PAD_A 13-20
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 13, 16);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 14, 16);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 15, 16);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 16, 16);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 17, 16);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 18, 16);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 19, 16);
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 20, 16);
    // MCLK: PAD_A 26
    IOMuxManager_PinConfigure(CSK_IOMUX_PAD_A, 26, 16);
}

引脚说明:

信号

PAD

PIN

功能编号

说明

HSYNC

PAD_A

10

16

水平同步信号

VSYNC

PAD_A

11

16

垂直同步信号

PCLK

PAD_A

12

16

像素时钟

D0-D7

PAD_A

13-20

16

8 位数据线

MCLK

PAD_A

26

16

主时钟输出

注意:

  • 该函数由板型相关代码实现,不同板型的引脚配置可能不同

  • 使用 __attribute__((weak)) 修饰,允许在应用代码中覆盖默认配置

  • DVP 接口使用 8 位并行数据线,支持 YUV422 和灰度格式

配置参数说明

输入数据格式

格式

说明

每像素字节数

LISA_DVP_INPUT_FORM_YUV422_Y0CBY1CR

YUV422 格式,字节序为 Y0CbY1Cr

2

LISA_DVP_INPUT_FORM_LUMINA_8BIT

8 位亮度(灰度)格式

1

信号极性

参数

说明

LISA_DVP_POL_FALLING

0

下降沿有效

LISA_DVP_POL_RISING

1

上升沿有效

用于配置 VSYNC、HSYNC 和 PCLK 的有效边沿。

数据对齐方式

对齐方式

说明

LISA_DVP_DATA_ALIGN_RIGHT

右对齐(低位对齐)

LISA_DVP_DATA_ALIGN_LEFT

左对齐(高位对齐)

当数据位宽小于总线宽度时,指定数据的对齐方式。

事件类型

事件

触发时机

使用场景

LISA_DVP_EVENT_DONE

普通模式下一帧接收完成

单缓冲区模式

LISA_DVP_EVENT_PING_DONE

Ping 缓冲区接收完成

Ping-Pong 模式

LISA_DVP_EVENT_PONG_DONE

Pong 缓冲区接收完成

Ping-Pong 模式

注意事项

  1. 必须先配置: 设备获取后必须先调用 lisa_dvp_setup() 配置设备,才能使用其他功能 API

  2. 缓冲区对齐: 接收缓冲区必须 4 字节对齐,建议使用 __attribute__((aligned(4))) 修饰

  3. 缓冲区大小: 缓冲区大小必须足够容纳一帧数据,计算公式:

    • YUV422 格式:width × height × 2 字节

    • 8 位灰度:width × height 字节

  4. GPDMA 通道: 需指定一个可用的 GPDMA 通道,不同设备使用的通道不能冲突

  5. 时钟频率: lisa_dvp_enable_clockout() 的时钟频率参数需根据摄像头规格配置,常见值为 12MHz、24MHz、25MHz

  6. Ping-Pong 模式: Ping-Pong 模式下,两个缓冲区会交替接收数据,需在回调中及时处理并重载缓冲区

  7. 重载时机: lisa_dvp_reload()lisa_dvp_reload_pingpong() 应在回调函数中调用,确保连续捕获不丢帧

  8. 停止捕获: 调用 lisa_dvp_stop() 后需重新调用 lisa_dvp_start() 才能继续捕获

  9. 线程安全: 驱动内部使用互斥锁保护,但建议在同一线程中操作同一设备

  10. 信号极性: VSYNC、HSYNC、PCLK 的极性需根据摄像头数据手册配置,配置错误会导致图像错乱或无法捕获

  11. 像素/行偏移: pixel_offsetline_offset 用于裁剪图像,通常设置为 0

  12. 回调函数: 回调函数在中断上下文中执行,应尽量简短,避免阻塞操作

  13. 缓冲区长度: 传递给 lisa_dvp_start() 和相关函数的 len 参数单位为字节

文件说明

相关示例

完整的使用示例请参考:samples/drivers/devices/lisa_camera/src/main.c