基于FPGA的DVP接口实现
一、DVP简介
DVP接口(Digital Video Port)是一种用于数字视频传输的并行接口,常见于嵌入式系统和图像传感器中。DVP直接传输数字视频信号,减少模数转换需求,适合中低速视频传输。数据线:通常为8、10、12或16根数据线,用于传输像素数据。控制信号包括像素时钟(PCLK)、行同步(HSYNC 或 HREF)信号、场同步(VSYNC)等,用于同步数据传输。
DVP和VGA时序有相似之处,都使用行同步(HSYNC)和场同步(VSYNC)信号来同步图像数据。区别在于DVP是数字接口,直接传输数字像素数据,包含像素时钟(PCLK)和数据线;VGA是模拟接口,传输模拟RGB信号,需数模转换 。
本例介绍的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数据有效信号与数据同步产生。