大家好,又见面了,我是你们的朋友全栈君。
之前文章介绍了基于zynq的图像处理架构问题。其中,作为开发者,需要重点关注图像传感器接口、处理算法、显示接口,这些模块。现在我们一同学习用于视频数据接口的DVP模块,并将其封装成AXI-stream接口便于直接和VDMA IP通信。
DVP_AXI stream IP v1.0使用说明
1.设计概述
•用于cmos传感器视频数据采集,将cmos输出的8位视频数据拼接成RGB 565模式
•AXI_stream主机接口,用于和PS端内存的数据交互
•基于vivado 18.3软件设计
2.模块分析
此设计包括DVP模块及AXI_stream 协议部分。DVP模块负责将采集的8位视频数据及行、场同步信号按照相应时序转换成16位RGB模式输出,DVP模块独立封装,在顶层模块中调用。AXI_stream 部分用于产生相应的AXI_stream接口信号,设计采用AXI_stream主机模式。模块框架图如下图所示。
图1.DVP_AXI stream IP 核模块框架图
DVP模块:
DVP模块是实现视频数据采集的主要部分。
等到初始化摄像完成且行场同步信号出现,释放清零信号,开始写入数据;利用采样计数器对采样数据计数,计数值在行同步信号有效时加1,否则清零;见以下代码。
//在HREF为高电平时,计数输出数据个数
//565模式下的计数器
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
Hcount_1 <= 0;
else if(r_Href)
Hcount_1 <= Hcount_1 + 1’d1;
else
Hcount_1 <= 0;
cmos输出采样的数据是8位的,需要将其转换成16位的RGB565数据模式输出。根据采样计数器的计数值奇偶情况输出数据,在计数值为偶数时,将采样的8位数据存到待输出像素数据的高字节,在计数值为奇数时,将数据存到输出像素数据的低字节。见以下代码。
begin
if(!Hcount_1[0])
r_DataPixel[15:8] <= r_Data;
else
r_DataPixel[7:0] <= r_Data;
end
在此数据拼接过程中,相当于每两个时钟像素完成了一次数据输出,需要指定输出数据有效的标志,以避免错误的数据输出。在将两个单字节采样数据拼接成一个两个字节数据的过程中,第一个时刻的采样数据给到输出数据的高字节,第二个时刻的采样数据给到输出数据的低字节,并且采样计数器从0开始计数,因此指定当采样计数器为奇数时数据有效。见以下代码。
begin
if(Hcount_1[0] && r_Href)
r_DataValid <= 1;
else
r_DataValid <= 0;
end
为保证传图的稳定性,传感器开始工作时舍弃前10帧,见以下代码。
/*帧计数器,对每次系统开始运行后的前10帧图像进行计数*/
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
FrameCnt <= 0;
else if({
r_Vsync,Vsync}== 2’b01)begin
if(FrameCnt >= 10)
FrameCnt <= 4’d10;
else
FrameCnt <= FrameCnt + 1’d1;
end
else
FrameCnt <= FrameCnt;
/*舍弃每次系统开始运行后的前10帧图像的数据,以确保输出图像稳定*/
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
dump_frame <= 0;
else if(FrameCnt >= 10)
dump_frame <= 1’d1;
else
dump_frame <= 0;
AXI_stream 接口部分:
此部分主要作用是产生AXI_stream 接口相关的信号。
根据AXI_stream时序,主机和从机之间需要建立握手信号以传输数据。利用主机的数据有效标志信号(m_axis_video_tvaild)以及从机的响应信号(m_axis_video_tready)实现握手协议。
m_axis_video_tlast在AXI_stream中为传输的一个数据包的的边缘,在这里可给定为一行的结束,可由对行同步信号的边沿检测确定;m_axis_video_tuser为用户自定义的数据包边界信号,这里给定为一帧的开始,可由对帧同步信号的边沿检测得到。
由于DVP模块由cmos产生的像素时钟产驱动,而AXI_stream接口的数据传输由系统时钟驱动,在模块中添加异步fifo保证信号的同步性。使用这个fifo有两个目的:
- 处理跨时钟域问题。
2.为视频数据输入和AXI_stream 流数据输出的缓冲。
结合数据的有效信号、从机的响应信号来确定异步fifo的读写使能。
xpm_fifo_async_inst (
.rst (~cmos_aresetn),
.wr_clk (cmos_pclk),
.wr_en (s_axis_tvalid & fifo_ready),
.din ({
s_axis_tdata,s_axis_tlast,s_axis_tuser}),
.full (full),
.overflow (),
.prog_full (),
.wr_data_count (),
.almost_full (),
.wr_ack (),
.wr_rst_busy (),
.rd_clk (m_axis_video_aclk),
.rd_en (m_axis_video_tready & ~empty & fifo_ready_maxis),
.dout ({
m_axis_video_tdata,m_axis_video_tlast,m_axis_video_tuser}),
.empty (empty),
.underflow (),
.rd_rst_busy (),
.prog_empty (),
.rd_data_count (),
.almost_empty (),
.data_valid (),
.sleep (1’b0),
.injectsbiterr (1’b0),
.injectdbiterr (1’b0),
.sbiterr (),
.dbiterr ()
)
3.端口说明
图2.DVP_AXI stream IP 核端口示意图
注:在实际使用时,cmos传感器根据设置输出相应的的采样时钟,如1280*720p时为84Mhz。
4.功能仿真
编写TestBench文件,模拟cmos采样信号的输入,观察输出波形,分析功能是否达到要求。
首先生成时钟,总共两路时钟,一路是的输入的采样时钟,这里用50MHz代替,一路是AXI_stream接口的驱动时钟,这里用100Mhz代替。此外,产生行、场同步信号的同时,利用循环产生像素数据。这里简化了“一帧”的像素数量,简化为每帧12行,每行16个数据,循环出15帧。TestBench 主要内容如下:
//产生时钟
initial cmos_pclk = 1;
always # 10 cmos_pclk = ~cmos_pclk;//cmos像素时钟设置为50M,实际使用时84M等
initial m_axis_video_aclk = 1;
always # 5 m_axis_video_aclk = ~m_axis_video_aclk;//AXIS接口驱动时钟设置为100M,实际使用时150M等
//产生行、场同步信号和视频数据,这里简化了“一帧”的像素数量,简化为每帧12行,每行16个数据,循环出15帧
parameter WIDTH = 16;
parameter HIGHT = 12;
integer i,j;
initial begin
m_axis_video_aresetn = 0;
m_axis_video_tready=0;
cmos_vsync=0;
cmos_href=0;
cmos_d=8’hff;
#200;
m_axis_video_aresetn = 1;
m_axis_video_tready=1;
#200;
repeat(15)begin
cmos_vsync = 1;
#100;
cmos_vsync = 0;
#200;
for(i=0;i<HIGHT;i=i+1)
begin
for(j=0;j<WIDTH;j=j+1)
begin
cmos_href=1;
cmos_d=cmos_d–1;
#20;
end
cmos_href=0;
#100;
end
end
$stop;
end
endmodule
图3.DVP_AXI stream 模块功能仿真波形图
5.使用说明
此设计输出的是RGB 565模式,AXI_stream主机接口用于与PS端的数据交互,通过vivado自带的VDMA IP进行视频流数据的内存读写。此外,实际应用时,用于HDMI接口的显示模块输入的是RGB888模式的24位数据,可在此IP后接入vivado自带的视频流位宽转换IP——AXI4_Stream_Subset_Converter,将RGB565转换为RGB888模式输出。端口连接如下图所示。
图4.DVP_AXI stream IP与AXI4_Stream_Subset_Converter IP的连接
有需要工程的朋友可以联系我,感谢各位同学,欢迎指正,一块学习进步!
源代码如下:
1.top
`timescale 1ns / 1ps
module userIP_ov5640
#(
parameter BUFFER_DEPTH = 4096
)
(
input cmos_vsync, //cmos vsync
input cmos_href, //cmos hsync refrence
input cmos_pclk, //cmos pxiel clock
input [9:0] cmos_d, //cmos data
// AXI4-Stream signals
input m_axis_video_aclk, // AXI4-Stream clock
input m_axis_video_aresetn, // AXI4-Stream reset, active low
output [15:0] m_axis_video_tdata, // AXI4-Stream data
output m_axis_video_tvalid, // AXI4-Stream valid
input m_axis_video_tready, // AXI4-Stream ready
output m_axis_video_tuser, // AXI4-Stream tuser (SOF)
output m_axis_video_tlast, // AXI4-Stream tlast (EOL)
output[1:0] m_axis_video_tkeep // AXI4-Stream tkeep
);
assign m_axis_video_tkeep = 2'b11;
wire[15:0] cmos_d_16bit;
wire cmos_href_16bit;
reg[7:0] cmos_d_d0;
reg cmos_href_d0;
reg cmos_vsync_d0;
reg cmos_vsync_d1;
wire cmos_hblank;
reg s_axis_tlast;
reg s_axis_tuser;
wire s_axis_tready;
reg cmos_hblank_d0;
reg cmos_hblank_d1;
reg cmos_href_16bit_d0;
reg cmos_href_16bit_d1;
reg[15:0] cmos_d_16bit_d0;
reg[15:0] cmos_d_16bit_d1;
wire s_axis_tvalid = cmos_href_16bit_d1 & cmos_hblank_d1 & s_axis_tready; //dvp输出有效数据到axis接口的标志信号
wire[15:0] s_axis_tdata = cmos_d_16bit_d1; //dvp输出的数据连接到axis接口
reg[31:0] reset_cnt;
reg[31:0] fifo_ready_cnt;
reg fifo_ready;
reg cmos_aresetn;
//reg axis_reset;
reg fifo_ready_maxis;
//信号同步
always@(posedge m_axis_video_aclk)
begin
// axis_reset <= cmos_aresetn;
fifo_ready_maxis <= fifo_ready;
end
//产生DVP复位信号
always@(posedge cmos_pclk)
begin
if(reset_cnt < 32'd200_000_000) //延时复位,可改变大小
begin
reset_cnt <= reset_cnt + 32'd1;
cmos_aresetn <= 1'b0;
end
else
begin
cmos_aresetn <= 1'b1;
end
end
always@(posedge cmos_pclk)
begin
if(cmos_aresetn == 1'b0)
begin
fifo_ready_cnt <= 32'd0;
fifo_ready <= 1'b0;
end
else if(fifo_ready_cnt < 32'd100_000_000)
begin
fifo_ready_cnt <= fifo_ready_cnt + 32'd1;
fifo_ready <= 1'b0;
end
else
begin
fifo_ready <= 1'b1;
end
end
always@(posedge cmos_pclk)
begin
if(cmos_aresetn == 1'b0)
begin
cmos_d_d0 <= 8'd0;
cmos_href_d0 <= 1'b0;
cmos_vsync_d0 <= 1'b0;
cmos_vsync_d1 <= 1'b0;
end
else
begin
cmos_d_d0 <= cmos_d[9:2];
cmos_href_d0 <= cmos_href;
cmos_vsync_d0 <= cmos_vsync;
cmos_vsync_d1 <= cmos_vsync_d0;
end
end
//例化DVP模块
DVP DVP0
(
. Rst_p(~cmos_aresetn),
. PCLK(cmos_pclk), //像素时钟
. Vsync(cmos_vsync_d0), //帧同步
. Href(cmos_href_d0), //行刷新
. Data(cmos_d_d0), //采样数据
.ImageState(), //采样状态
.DataValid(cmos_href_16bit), //数据有效标志
.DataPixel(cmos_d_16bit), //并行数据输出
.DataHs(cmos_hblank), //行同步信号输出
.DataVs(), //帧同步信号输出
.Xaddr(), //行方向数据采样地址
.Yaddr() //列方向数据采样地址
);
always@(posedge cmos_pclk)
begin
if(cmos_aresetn == 1'b0)
begin
cmos_hblank_d0 <= 1'b0;
cmos_hblank_d1 <= 1'b0;
cmos_d_16bit_d0 <= 1'b0;
cmos_d_16bit_d1 <= 1'b0;
cmos_href_16bit_d0 <= 1'b0;
cmos_href_16bit_d1 <= 1'b0;
s_axis_tlast <= 1'b0;
end
else
begin
cmos_hblank_d0 <= cmos_hblank;
cmos_hblank_d1 <= cmos_hblank_d0;
cmos_d_16bit_d0 <= cmos_d_16bit;
cmos_d_16bit_d1 <= cmos_d_16bit_d0;
cmos_href_16bit_d0 <= cmos_href_16bit;
cmos_href_16bit_d1 <= cmos_href_16bit_d0;
s_axis_tlast <= cmos_hblank_d0 & ~cmos_hblank; //s_axis_tlast一行的开始
end
end
always@(posedge cmos_pclk)
begin
if(cmos_aresetn == 1'b0)
s_axis_tuser <= 1'b0;
else if(cmos_vsync_d1 == 1'b1 && cmos_vsync_d0 == 1'b0) //标志着一帧的最开始的数据
s_axis_tuser <= 1'b1;
else if(s_axis_tuser == 1'b1 && s_axis_tvalid == 1'b1) //数据有效后s_axis_tuser 置零,s_axis_tuser 只保持几个周期的高电平
s_axis_tuser <= 1'b0;
end
wire empty;
wire full;
assign m_axis_video_tvalid = ~empty & m_axis_video_tready;
assign s_axis_tready = ~full;
//使用原语进行异步fifo例化
//使用这个fifo有两个目的:处理跨时钟域问题作为视频数据输入和AXI_stream 流数据输出的缓冲
xpm_fifo_async # (
.FIFO_MEMORY_TYPE ("auto"), //string; "auto", "block", or "distributed";
.ECC_MODE ("no_ecc"), //string; "no_ecc" or "en_ecc";
.RELATED_CLOCKS (0), //positive integer; 0 or 1
.FIFO_WRITE_DEPTH (BUFFER_DEPTH), //positive integer
.WRITE_DATA_WIDTH (18), //positive integer
.WR_DATA_COUNT_WIDTH (12), //positive integer
.PROG_FULL_THRESH (10), //positive integer
.FULL_RESET_VALUE (0), //positive integer; 0 or 1
.USE_ADV_FEATURES ("0707"), //string; "0000" to "1F1F";
.READ_MODE ("fwft"), //string; "std" or "fwft";
.FIFO_READ_LATENCY (0), //positive integer;
.READ_DATA_WIDTH (18), //positive integer
.RD_DATA_COUNT_WIDTH (12), //positive integer
.PROG_EMPTY_THRESH (10), //positive integer
.DOUT_RESET_VALUE ("0"), //string
.CDC_SYNC_STAGES (2), //positive integer
.WAKEUP_TIME (0) //positive integer; 0 or 2;
) xpm_fifo_async_inst (
.rst (~cmos_aresetn),
.wr_clk (cmos_pclk),
.wr_en (s_axis_tvalid & fifo_ready),
.din ({s_axis_tdata,s_axis_tlast,s_axis_tuser}),
.full (full),
.overflow (),
.prog_full (),
.wr_data_count (),
.almost_full (),
.wr_ack (),
.wr_rst_busy (),
.rd_clk (m_axis_video_aclk),
.rd_en (m_axis_video_tready & ~empty & fifo_ready_maxis),
.dout ({m_axis_video_tdata,m_axis_video_tlast,m_axis_video_tuser}),
.empty (empty),
.underflow (),
.rd_rst_busy (),
.prog_empty (),
.rd_data_count (),
.almost_empty (),
.data_valid (),
.sleep (1'b0),
.injectsbiterr (1'b0),
.injectdbiterr (1'b0),
.sbiterr (),
.dbiterr ()
);
endmodule
`timescale 1ns / 1ps
///coms数据采集模块
///
module DVP#(
parameter coms_mode=16
)
(
input Rst_p,
input PCLK, //像素时钟
input Vsync, //帧同步
input Href, //行刷新
input [7:0] Data, //采样数据
output reg ImageState, //采样状态
output DataValid, //数据有效标志
output [coms_mode-1:0] DataPixel, //并行数据输出
output DataHs, //行同步信号输出
output DataVs, //帧同步信号输出
output [11:0] Xaddr, //行方向数据采样地址
output [11:0] Yaddr //列方向数据采样地址
);
//内部寄存器
reg r_Vsync;
reg r_Href;
reg [7:0] r_Data;
reg [coms_mode-1:0]r_DataPixel;
reg r_DataValid;
reg r_DataHs;
reg r_DataVs;
reg [12:0]Hcount_1; //565模式下的采样数据计数器
reg [11:0]Vcount; //使用Vcount计数器对HREF信号的高电平进行计数,统计一帧图像中的每一行图像的行号
reg [3:0] FrameCnt; //帧计数器,前10帧舍弃
reg [12:0]Hcount_2; //888模式下的采样数据计数器
reg [12:0]Xaddr_cnt; //行方向数据采样地址计数器
reg dump_frame; //帧计数器计满10帧的标志
//等到初始化摄像完成且头场同步信号出现,释放清零信号,开始写入数据
always@(posedge PCLK or posedge Rst_p)
if (Rst_p)
ImageState <= 1'b1;
else if(r_Vsync)
ImageState <= 1'b0;
//对DVP接口的数据使用寄存器打一拍,以用信号边沿检测功能
always@(posedge PCLK)
begin
r_Vsync <= Vsync;
r_Href <= Href;
r_Data <= Data;
end
//在HREF为高电平时,计数输出数据个数
//565模式下的计数器
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
Hcount_1 <= 0;
else if(r_Href)
Hcount_1 <= Hcount_1 + 1'd1;
else
Hcount_1 <= 0;
//888模式下的计数器
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
Hcount_2 <= 0;
else if(r_Href)begin
if (Hcount_2==2)
Hcount_2<=0;
else
Hcount_2 <= Hcount_2 + 1'd1;
end
else
Hcount_2 <= 0;
/*565模式下:根据计数器的计数值奇数和偶数的区别,在计数器为偶数时,
将DVP接口数据端口上的数据存到输出像素数据的高字节,在计
数器为奇数时,将DVP接口数据端口上的数据存到输出像素数据
的低字节*/
//888模式下,根据计数器的值控制r/g/b的数据,计数器位0时数据给datapixel的高位.....
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
r_DataPixel <= 0;
else if ((coms_mode==24)&&r_Href)begin
if (Hcount_2==0)
r_DataPixel[23:16] <= r_Data;
else if (Hcount_2==1)
r_DataPixel[15:8] <= r_Data;
else if (Hcount_2==2)
r_DataPixel[7:0] <= r_Data;
end
else begin
if(!Hcount_1[0])
r_DataPixel[15:8] <= r_Data;
else
r_DataPixel[7:0] <= r_Data;
end
/*rgb565在行计数器计数值为奇数,且HREF高电平期间,产生输出;
rgb888模式,计数到2有效数据有效信号*/
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
r_DataValid <= 0;
else if ((coms_mode==24))begin
if ((Hcount_2==2)&&r_Href)
r_DataValid <= 1;
else
r_DataValid <= 0;
end
else
begin if(Hcount_1[0] && r_Href)
r_DataValid <= 1;
else
r_DataValid <= 0;
end
always@(posedge PCLK)
begin
r_DataHs <= r_Href;
r_DataVs <= ~r_Vsync;
end
/*使用Vcount计数器对HREF信号的高电平进行计数,统计
一帧图像中的每一行图像的行号*/
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
Vcount <= 0;
else if(r_Vsync)
Vcount <= 0;
else if({r_Href,Href} == 2'b01)
Vcount <= Vcount + 1'd1;
else
Vcount <= Vcount;
/*输出X地址*/
assign Yaddr = Vcount;
/*对于RGB565模式,由于一行N个像素的图像输出2N个数据,所以Hcount_1计数值为N的2倍,将该计数值除以2后即可作为Xaddr输出;
对于RGB88模式,由于一行N个像素的图像输出3N个数据,所以Xaddr可以通过对Hcount_2计数满2确定*/
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
Xaddr_cnt <= 0;
else if(r_Href)begin
if(Hcount_2==2)
Xaddr_cnt <=Xaddr_cnt+1;
else
Xaddr_cnt <=Xaddr_cnt;
end
else
Xaddr_cnt <=0;
assign Xaddr = (coms_mode==24)?Xaddr_cnt:Hcount_1[12:1];
/*帧计数器,对每次系统开始运行后的前10帧图像进行计数*/
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
FrameCnt <= 0;
else if({r_Vsync,Vsync}== 2'b01)begin
if(FrameCnt >= 10)
FrameCnt <= 4'd10;
else
FrameCnt <= FrameCnt + 1'd1;
end
else
FrameCnt <= FrameCnt;
/*舍弃每次系统开始运行后的前10帧图像的数据,以确保输出图像稳定*/
always@(posedge PCLK or posedge Rst_p)
if(Rst_p)
dump_frame <= 0;
else if(FrameCnt >= 10)
dump_frame <= 1'd1;
else
dump_frame <= 0;
assign DataPixel = r_DataPixel;
assign DataValid = r_DataValid & dump_frame;
assign DataHs = r_DataHs & dump_frame;
assign DataVs = r_DataVs & dump_frame;
endmodule
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/138592.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...