一、DVP简介

        DVP接口(Digital Video Port)是一种用于数字视频传输的并行接口,常见于嵌入式系统和图像传感器中。DVP直接传输数字视频信号,减少模数转换需求,适合中低速视频传输。数据线:通常为8、10、12或16根数据线,用于传输像素数据。控制信号包括像素时钟(PCLK)、行同步(HSYNC 或 HREF)信号、场同步(VSYNC)等,用于同步数据传输。

        DVP和VGA时序有相似之处,都使用行同步(HSYNC)和场同步(VSYNC)信号来同步图像数据。区别在于DVP是数字接口,直接传输数字像素数据,包含像素时钟(PCLK)和数据线;VGA是模拟接口,传输模拟RGB信号,需数模转换(VGA的介绍可参考Verilog:VGA控制器) 。

        本例介绍的DVP行同步信号采用 HREF,用于 ov5640 DVP数据接收采集。

        (1)行时序

                1.高电平有效:HREF为高电平时,表示正在传输有效像素数据。

                2.像素传输:在HREF高电平期间,每个PCLK周期传输一个像素数据。

                3.行结束:HREF从高电平变为低电平,标志一行数据传输完成。

                4.像素数据周期:ov5640 DVP每个有效数据为8位,数据采集时需要根据输出格式进行调整,输出RGB565格式需要两个像素时钟才能完成传输,先传输高8位,后第8位。

        (2)场时序

                1.同步脉冲:VSYNC产生高电平脉冲(取决于极性,ov5640为高)表示新一帧开始。

                2.有效数据期:新一帧开始一段时间后,每行数据通过HSYNC或HREF信号同步。

                3.帧结束:一帧数据传输完成后,VSYNC再次产生脉冲,标志下一帧的开始。

    二、Verilog 实现

            (1)设计要求

                    1. 对 ov5640 输出RGB565格式的图像数据进行接收

                    2. 接收数据输出给存储器(如rom、fifo、ddr)进行存储,包含必要输出端口

            (2)设计要点

                    1. 场同步:每当 vsync 产生一次上升沿即代表新一帧的开始(上升沿通过打拍判断)

                    2. 行同步:href 的每段高电平代表一行有效数据

                    3. 数据格式:一个pclk像素时钟传8位,而RGB565一个像素16位,需要两个pclk像素时钟才传输完一个像素数据,因此需要进行先缓存高8位数据,并在接收第8位时拼接数据并输出

                    4. 数据有效信号:高电平时代表当前RGB565数据有效,可供后续存储器作写使能使用

            (3)模块代码

      `timescale 1ns / 1ps
    module DVP_ctrl#(
        parameter PIC_CNT_MAX = 4'd10   //舍弃前10帧不稳定图像数据
    )(
        input  wire rst_n,
        input  wire ov5640_pclk,        //摄像头像素时钟
        input  wire ov5640_href,        //摄像头行同步信号
        input  wire ov5640_vsync,       //摄像头场同步信号
        input  wire [7:0] ov5640_data,  //摄像头场数据输入
        output reg  [15:0] RGB565_data, //图像数据输出(RGB565格式)
        output wire data_valid          //数据有效信号(给存储器的写使能信号)
    );
    reg  pix_flag;               //一像素数据结束标志位
    wire pic_flag;               //一帧图像结束标志位
    reg  pic_valid;              //帧有效标志位
    reg  [3:0] pic_cnt;          //帧计数器
    reg  [7:0] r_ov5640_data;    //输入数据缓存
    reg  ov5640_vsync_delay;     //场同步信号打拍
    reg  pix_flag_delay;         //一像素数据结束标志位打拍
    
    //***************************** 场同步 ****************************//
    //场同步信号打拍(用于检测vsync上升沿)
    always@(posedge ov5640_pclk or negedge rst_n)
        if(rst_n == 1'b0)
            ov5640_vsync_delay <= 1'b0;
        else
            ov5640_vsync_delay <= ov5640_vsync;
    
    //一帧图像结束标志位(vsync上升沿产生一次)
    assign pic_flag = ((ov5640_vsync_delay == 1'b0) &&
                       (ov5640_vsync == 1'b1)) ? 1'b1 : 1'b0;
    
    //前几帧计数,计满产生帧有效信号
    always @(posedge ov5640_pclk or negedge rst_n) begin
        if (!rst_n) begin
            pic_cnt   <= 4'd0;
            pic_valid <= 1'b0;
        end else if (pic_flag) begin
            if (pic_cnt == PIC_CNT_MAX) begin
                pic_cnt   <= 4'd0;
                pic_valid <= 1'b1;
            end else
                pic_cnt <= pic_cnt + 4'd1;
        end
    end
    
    //***************************** 行同步 ****************************//
    //行同步
    always @(posedge ov5640_pclk or negedge rst_n) begin
        if (!rst_n) begin
            pix_flag      <= 1'b0;
            r_ov5640_data <= 8'b0;
            RGB565_data   <= 8'b0;
        end else if (ov5640_href) begin
            if (!pix_flag) begin
                r_ov5640_data <= ov5640_data; //先缓存高8位
                pix_flag <= 1'b1;
            end else begin
                RGB565_data <= {r_ov5640_data , ov5640_data};//后拼接低8位输出
                pix_flag <= 1'b0;
            end
        end
    end
    
    //一像素数据结束标志位打拍(用于产生像素数据有效信号)
    always@(posedge ov5640_pclk or negedge rst_n)
        if(rst_n == 1'b0)
            pix_flag_delay <= 1'b0;
        else
            pix_flag_delay <= pix_flag;
    
    //像素数据有效信号
    assign data_valid = pic_valid & pix_flag_delay;
    
    endmodule
    

            (4)仿真代码

                   仿真就是给DVP模块模拟ov5640产生的图像数据,我用的deepseek写了一版,但经过测试发现不能直接使用,于是根据它的框架自己进行了一些修改,可以通过参数设置模拟图像数据的参数(仿真多少帧、一帧多少行、一行多少像素)。以下模拟输出了10帧、一帧8行数据、一行16个像素点,同时DVP舍去前3帧图像数据。

    `timescale 1ns / 1ps
    module DVP_data_gen_tb();
    reg pclk;                 //像素时钟 (10ns周期)
    reg rst_n;               //复位信号 (低电平有效)
    reg vsync;               //场同步信号
    reg href;                //行同步信号
    reg [7:0] data;          //像素数据 (8bit)
    wire data_valid;         //数据有效信号
    wire [15:0] RGB565_data; //输出RGB565格式数据 (16bit)
    
    //模拟OV5640视频数据生成
    parameter WIDTH   = 16, //宽(一行多少个像素)
              HIGTH   = 8,  //高(一帧多少行数据)
              FRAME   = 10; //帧(模拟发送多少帧数据)
    integer pixel_cnt = 0,  //像素计数器
            row_cnt   = 0,  //行计数器
            frame_cnt = 0;  //帧计数器
    
    //时钟(10ns周期)
    always #5 pclk = ~pclk; 
    initial begin //初始化复位
        pclk  = 0; 
        rst_n = 0; #20;    
        rst_n = 1;   
    end
    
    always @(posedge pclk or negedge rst_n) begin
        if (!rst_n) begin
            vsync     <= 0;
            href      <= 0;
            data      <= 0;
            pixel_cnt <= 0;
            row_cnt   <= 0;
            frame_cnt <= 0;
        end else begin
            //******************************************模拟场同步信号 (VSYNC)
            if (pixel_cnt == 0 && row_cnt == 0) begin
                vsync <= 1; // 一帧开始,VSYNC拉高一个时钟周期
            end else begin
                vsync <= 0; // VSYNC拉低
            end
            //******************************************模拟行同步信号 (HREF)
            if (row_cnt < HIGTH*10 + 10) begin//一帧模拟HIGTH行,多余行模拟行与行之间的输出间隔
                if (row_cnt <10 || (row_cnt % 10)!=0)
                    row_cnt <= row_cnt + 1;
                else begin
                    if (pixel_cnt < WIDTH) begin
                        href      <= 1; //HREF高电平表示行数据传输
                        data      <= data + 1; //像素数据每次自增1
                        pixel_cnt <= pixel_cnt + 1;
                    end else begin
                        //一行结束
                        href      <= 0; //HREF低电平表示行结束
                        pixel_cnt <= 0;
                        row_cnt   <= row_cnt + 1; //行计数器加1
                    end
                end
            end else begin//一帧结束
                href      <= 0;
                data      <= 0;
                row_cnt   <= 0; //重置行计数器
                frame_cnt <= frame_cnt + 1; //帧计数器加1
            end
            //******************************************模拟FRAME帧后结束测试
            if (frame_cnt == FRAME) begin
                $finish; // 结束仿真
            end
        end
    end
    
    DVP_ctrl #(
        .PIC_CNT_MAX (4'd3) //舍去前三帧图像
    ) DVP_ctrl (
        .ov5640_pclk    (pclk),
        .rst_n          (rst_n),
        .ov5640_vsync   (vsync),
        .ov5640_href    (href),
        .ov5640_data    (data),
        .data_valid     (data_valid),
        .RGB565_data    (RGB565_data)
    );
    endmodule

    三、仿真波形

            完整波形:可以看到一共发送了10帧数据,同时data_valid在前三帧保持为0,后面才开始变化,说明前三帧数据被成功舍去。

            一帧波形:1帧包含8行,1行有16个8位数据。

            一行波形:一个RGB565像素数据对应两个8位数据,可以看到每接收2个数据就相应拼接输出1个RGB565数据,同时data_valid数据有效信号与数据同步产生。

    Logo

    有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

    更多推荐