S03_CH05_AXI_DMA_HDMI图像输出

5.1概述

本课程是在前面课程基础上添加HDMI IP 实现HDMI视频图像的输出。本课程出了多了HDMI输出接口,其他内容和《S03_CH03_AXI_DMA_OV7725摄像头采集系统》。本章课程内容使用的也是OV7725摄像头,但是课后代码会给出OV5640的配套代码。下面的内容除了涉及到HDMI部分的,其他和《S03_CH03_AXI_DMA_OV7725摄像头采集系统》。

《S03_CH03_AXI_DMA_OV7725摄像头采集系统》、《S03_CH04_AXI_DMA_OV5640摄像头采集系统》、《S03_CH05_AXI_DMA_HDMI图像输出》。读者可以根据自己需求情况而阅读,请知悉。

5.2系统构架

5.2.1构架方案图

摄像头接口采集的摄像头数据,进过vid in视频输入 IP后,还需要通过用户FPGA逻辑编程,和DMA IP之间实现握手协议,实现把数据通过DMA写入到DDR。每次写入一副图像的数据后,产生一次接收中断,接收中断函数,会把数据三缓存后,在通过DMA发出去,DMA发送完成后产生中断,在中断中,把缓存好的图像发送出去。DMA发送的数据需要发送到vid out 视频输出IP。同理,DMA和vid out  IP之间也许需要增加FPGA用户代码实现接口的握手协议。数据进入vid out 后,会随同vtc IP 输出符合VGA时序的图像信号。vid out 的输出就可以直接定义成VGA信号输出。

5.2.2构BLOCK模块化设计方案图

MIZ702/MIZ702N的HDMI(ADV7511 HDMI芯片方案)显示构架图

MIZ701N的HDMI(FPGA IO模拟HDMI时序方案)显示构架图

5.3 vid in IP介绍

5.3.1 OV_Sensor_ML 自定义 IP模块

外部信号接口说明:

CLK_i :为输入时钟,通常接24MHZ 或者25MHZ

Cmos_xclk_o:摄像头工作,通常直接把CLK_i连接到cmos_xclk_o

Cmos_vsyns_i:摄像头场同步输入 上升沿代表场同步开始

Cmos_href_i:摄像头行同步输入 高电平代表行数据有效

Cmos_data[7:0]:摄像头数据输入

Hs_o:采集 OV_Sensor_ML IP 输出的行数据有效

Vs_o:采集 OV_Sensor_ML IP 输出的场同步信号

Vid_clk_ce:此信号用于和vid_in IP的时钟同步(由于OV_Sensor_ML IP 是每两个时钟输出一次rgb[23:0]的图像数据,因此需要通过Vid_clk_ce对时钟频率进行同步,有了这个信号,可以解决输入采集IP和vid_in IP数据接口之间的同步问题).

OV_Sensor_ML IP 包含3个源程序文件,分别为OV_Sensor_ML.v、cmos_decode_v1.v、count_reset_v1.v文件。

OV_Sensor_ML.v程序中,对cmos_data_i、cmos_href_i、cmos_vsync_i做了一次寄存器,笔者发现图像效果有所改观。笔者分析,是因为寄存后有利于去除一些毛刺信号,提高了数据的稳定性。

表3-3-1-1 OV_Sensor_ML 源码OV_Sensor_ML.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////

// Company: milinker

// Engineer:tangjinyuan

//

// Create Date:    15:54:59 11/21/2015

// Design Name:

// Module Name:    OV7725_IP_ML

// Project Name: OV7725_IP_ML

// Target Devices: ZYNQ

// Tool versions:

// Description:

//

// Dependencies:

//

// Revision:

// Revision 0.01 - File Created

// Additional Comments:

//

//////////////////////////////////////////////////////////////////////////////////

module OV_Sensor_ML(

input CLK_i,

//---------------------------- CMOS sensor hardware interface --------------------------/

input cmos_vsync_i, //cmos vsync

input cmos_href_i, //cmos hsync refrence

input cmos_pclk_i, //cmos pxiel clock

output cmos_xclk_o, //cmos externl clock

input[7:0] cmos_data_i, //cmos data

output hs_o,//hs signal.

output vs_o,//vs signal.

// output de_o,//data enable.

output [23:0] rgb_o,//data output,

output vid_clk_ce

);

//----------------------视频输出解码模块---------------------------//

wire  [15:0]rgb_o_r;

assign rgb_o = {rgb_o_r[4:0]   ,3'd0 ,rgb_o_r[10:5]     ,2'd0,rgb_o_r[15:11],3'd0};

reg [7:0]cmos_data_r;

reg cmos_href_r;

reg cmos_vsync_r;

always@(posedge cmos_pclk_i)

begin

cmos_data_r <= cmos_data_i;

cmos_href_r <= cmos_href_i;

cmos_vsync_r<= cmos_vsync_i;

end

//assign rgb_o = 24'b11111111_00000000_11111111;

cmos_decode cmos_decode_u0(

//system signal.

.cmos_clk_i(CLK_i),//cmos senseor clock.

.rst_n_i(RESETn_i2c),//system reset.active low.

//cmos sensor hardware interface.

.cmos_pclk_i(cmos_pclk_i),//(cmos_pclk),//input pixel clock.

.cmos_href_i(cmos_href_r),//(cmos_href),//input pixel hs signal.

.cmos_vsync_i(cmos_vsync_r),//(cmos_vsync),//input pixel vs signal.

.cmos_data_i(cmos_data_r),//(cmos_data),//data.

.cmos_xclk_o(cmos_xclk_o),//(cmos_xclk),//output clock to cmos sensor.

//user interface.

.hs_o(hs_o),//hs signal.

.vs_o(vs_o),//vs signal.

// .de_o(de_o),//data enable.

.rgb565_o(rgb_o_r),//data output

.vid_clk_ce(vid_clk_ce)

);

count_reset_v1#(

.num(20'hffff0)

)(

.clk_i(CLK_i),

.rst_o(RESETn_i2c)

);    

endmodule

1 cmos_decode_v1.v 是本模块的关键部分,实现了RGB565 的解码输出以及vid_clk_ce实现了此模块和vid_in IP直接时序匹配的关系。

表3-3-1-2 OV_Sensor_ML 源码cmos_decode_v1.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////

// Company: milinker corperation

// WEB:www.milinker.com

// BBS:www.osrc.cn

// Engineer:.

// Create Date:    07:28:50 09/04/2015

// Design Name:  cmos_decode_v1

// Module Name:    cmos_decode_v1

// Project Name:  cmos_decode_v1

// Target Devices: XC6SLX25-FTG256 Mis603

// Tool versions:  ISE14.7

// Description:  cmos_decode_v1.

// Revision:  V1.0

// Additional Comments:

//1) _i PIN input  

//2) _o PIN output

//3) _n PIN active low

//4) _dg debug signal

//5) _r  reg delay

//6) _s state machine

//////////////////////////////////////////////////////////////////////////////

module cmos_decode(

//system signal.

input cmos_clk_i,//cmos senseor clock.

input rst_n_i,//system reset.active low.

//cmos sensor hardware interface.

input cmos_pclk_i,//input pixel clock.

input cmos_href_i,//input pixel hs signal.

input cmos_vsync_i,//input pixel vs signal.

input[7:0]cmos_data_i,//data.

output cmos_xclk_o,//output clock to cmos sensor.

//user interface.

output hs_o,//hs signal.

output vs_o,//vs signal.

output reg [15:0] rgb565_o,//data output

output vid_clk_ce

);

parameter[5:0]CMOS_FRAME_WAITCNT = 4'd15;

reg[4:0] rst_n_reg = 5'd0;

//reset signal deal with.

always@(posedge cmos_clk_i)

begin

rst_n_reg <= {rst_n_reg[3:0],rst_n_i};

end

reg[1:0]vsync_d;

reg[1:0]href_d;

wire vsync_start;

wire vsync_end;

//vs signal deal with.

always@(posedge cmos_pclk_i)

begin

vsync_d <= {vsync_d[0],cmos_vsync_i};

href_d  <= {href_d[0],cmos_href_i};

end

assign vsync_start =  vsync_d[1]&(!vsync_d[0]);

assign vsync_end   = (!vsync_d[1])&vsync_d[0];

reg[6:0]cmos_fps;

//frame count.

always@(posedge cmos_pclk_i)

begin

if(!rst_n_reg[4])

begin

cmos_fps <= 7'd0;

end

else if(vsync_start)

begin

cmos_fps <= cmos_fps + 7'd1;

end

else if(cmos_fps >= CMOS_FRAME_WAITCNT)

begin

cmos_fps <= CMOS_FRAME_WAITCNT;

end

end

//wait frames and output enable.

reg out_en;

always@(posedge cmos_pclk_i)

begin

if(!rst_n_reg[4])

begin

out_en <= 1'b0;

end

else if(cmos_fps >= CMOS_FRAME_WAITCNT)

begin

out_en <= 1'b1;

end

else

begin

out_en <= out_en;

end

end

//output data 8bit changed into 16bit in rgb565.

reg [7:0] cmos_data_d0;

reg [15:0]cmos_rgb565_d0;

reg byte_flag;

always@(posedge cmos_pclk_i)

begin

if(!rst_n_reg[4])

byte_flag <= 0;

else if(cmos_href_i)

byte_flag <= ~byte_flag;

else

byte_flag <= 0;

end

reg byte_flag_r0;

always@(posedge cmos_pclk_i)

begin

if(!rst_n_reg[4])

byte_flag_r0 <= 0;

else

byte_flag_r0 <= byte_flag;

end

always@(posedge cmos_pclk_i)

begin

if(!rst_n_reg[4])

cmos_data_d0 <= 8'd0;

else if(cmos_href_i)

cmos_data_d0 <= cmos_data_i; //MSB -> LSB

else if(~cmos_href_i)

cmos_data_d0 <= 8'd0;

end

always@(posedge cmos_pclk_i)

begin

if(!rst_n_reg[4])

rgb565_o <= 16'd0;

else if(cmos_href_i&byte_flag)

rgb565_o <= {cmos_data_d0,cmos_data_i}; //MSB -> LSB

else if(~cmos_href_i)

rgb565_o <= 8'd0;

end

assign vid_clk_ce = out_en ? (byte_flag_r0&hs_o)||(!hs_o) : 1'b0;

assign vs_o = out_en ? vsync_d[1] : 1'b0;

assign hs_o = out_en ? href_d[1] : 1'b0;

assign cmos_xclk_o = cmos_clk_i;

endmodule

count_reset_v1.v 源文件实现了信号的延迟复位。

表3-3-1-1 count_reset_v1.v

`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////

// Company: milinker corperation

// WEB:www.milinker.com

// BBS:www.osrc.cn

// Engineer:sanliuyaoling.

// Create Date:    07:28:50 12/04/2015

// Design Name:    count_reset_v1

// Module Name:    count_reset_v1

// Project Name:  count_reset_v1

// Target Devices: XC7Z020-CLG484-1I

// Tool versions:  vivado2015.4

// Description:  count_reset_v1

// Revision:  V1.0

// Additional Comments:

//1) _i PIN input  

//2) _o PIN output

//3) _n PIN active low

//4) _dg debug signal

//5) _r  reg delay

//6) _s state machine

//////////////////////////////////////////////////////////////////////////////

module count_reset_v1#

(

parameter[19:0]num = 20'hffff0

)(

input clk_i,

output rst_o

);

reg[19:0] cnt = 20'd0;

reg rst_d0;

/*count for clock*/

always@(posedge clk_i)

begin

cnt <= ( cnt <= num)?( cnt + 20'd1 ):num;

end

/*generate output signal*/

always@(posedge clk_i)

begin

rst_d0 <= ( cnt >= num)?1'b1:1'b0;

end

assign rst_o = rst_d0;

endmodule

表3-3-1-2

5.3.2 vid in IP模块

• Pixels Per Clock: 设置每个时钟输出的像素个数,可以是1、2、4

• Video Format: 视频格式

• Input Component Width: 输入像素的宽度,这个参数影响TDATA的位宽

• Output Component Width:输出像素的宽度

• FIFO Depth: FIFO深度

• Clock Mode:时钟的模式,可以选择独立时钟,或者共享时钟

5.3.2 VID_IN IP接口信号的定义

Common Interface

Video Timing Interface

Video Input Interface

使用到的信号有:

Vid in IP输入端信号:

Vid_data:视频数据输入

Vid_active_video:视频数据有效

Vid_hsync:视频行同步信号(非常关键信号,下面重点分析对象)

Vid_vsync:视频场同步信号

Vid_io_in_ce:数据输入有效(非常关键信号,下面重点分析对象)

vid_io_in_clk:这是时钟信号和摄像头时钟同步

Vid_io_in_reset:这个信号,高电平的时候复位

有很多读者会问笔者,这些官方的IP如何使用,这么没有详细的技术手册。还别说,官方就是没有非常详细的技术手册,有时候笔者也是使出浑身分析vid_in IP 内部信号时序,掌握OV_Sensor_ML 自定义IP 时序接口设计。

打开 v_vid_in_axi4s_v4_0_1_formatter.v这个文件

下面对其关键的部分进行说明。

表3-3-2-1 v_vid_in_axi4s_v4_0_1_formatter.v

`timescale 1ps/1ps

`default_nettype none

(* DowngradeIPIdentifiedWarnings="yes" *)

module v_vid_in_axi4s_v4_0_1_formatter #(

parameter  C_NATIVE_DATA_WIDTH = 24

) (

// System signals

input  wire VID_IN_CLK,           // Native video clock

input  wire VID_RESET,            // Native video reset

input  wire VID_CE,               // Native video clock enable

// Video input signals

input  wire VID_ACTIVE_VIDEO,     // Native video input data enable

input  wire VID_VBLANK,           // Native video input vertical blank

input  wire VID_HBLANK,           // Native video input horizontal blank

input  wire VID_VSYNC,            // Native video input vertical sync

input  wire VID_HSYNC,            // Native video input horizontal sync

input  wire VID_FIELD_ID,         // Native video input field-id

input  wire [C_NATIVE_DATA_WIDTH-1:0] VID_DATA, // Native video input data

// Video timing detector signals

output wire VTD_ACTIVE_VIDEO,     // Native video output data enable

output wire VTD_VBLANK,           // Native video output vertical blank

output wire VTD_HBLANK,           // Native video output horizontal blank

output wire VTD_VSYNC,            // Native video output vertical sync

output wire VTD_HSYNC,            // Native video output horizontal sync

output wire VTD_FIELD_ID,         // Native video output field-id

input  wire VTD_LOCKED,           // Native video locked signal from VTD

// FIFO write signals

output wire [C_NATIVE_DATA_WIDTH+2:0] FIFO_WR_DATA, // FIFO write data

output wire FIFO_WR_EN            // FIFO write enable

);

// Wire and register declarations

reg  de_1 = 0;         

reg  vblank_1 = 0;

reg  hblank_1 = 0;

reg  vsync_1 = 0;  

reg  hsync_1 = 0;

reg  [C_NATIVE_DATA_WIDTH -1:0] data_1 = 0;  

reg  de_2 = 0;  

reg  v_blank_sync_2 = 0;  

reg  [C_NATIVE_DATA_WIDTH -1:0] data_2 = 0;  

reg  de_3 = 0;  // DE output register

reg  [C_NATIVE_DATA_WIDTH -1:0] data_3 = 0;  // data output register

reg  vert_blanking_intvl = 0; // SR, reset by DE rising

reg  field_id_1 = 0;

reg  field_id_2 = 0;

reg  field_id_3 = 0;

wire v_blank_sync_1;  // vblank or vsync

wire de_rising;                   

wire de_falling;      

wire vsync_rising;

reg  sof;

reg  sof_1;

reg  eol;   

reg  vtd_locked;

wire sof_rising;

// Assignments

assign FIFO_WR_DATA     = {field_id_3,sof_1,eol,data_3};

assign FIFO_WR_EN       = de_3 & ~VID_RESET & vtd_locked;

assign VTD_ACTIVE_VIDEO = de_1;

assign VTD_VBLANK       = vblank_1;

assign VTD_HBLANK       = hblank_1;

assign VTD_VSYNC        = vsync_1;

assign VTD_HSYNC        = hsync_1;

assign VTD_FIELD_ID     = field_id_1;

assign v_blank_sync_1 = vblank_1 || vsync_1;

assign de_rising  = de_1  && !de_2;  

assign de_falling = !de_1 && de_2;  

assign vsync_rising = v_blank_sync_1 && !v_blank_sync_2;    

assign sof_rising = sof & ~sof_1;

// VTD locked process

always @(posedge VID_IN_CLK) begin

if(VID_RESET | ~VTD_LOCKED) begin

vtd_locked <= 1'b0;

end else if(VID_CE) begin

vtd_locked <= (sof_rising & VTD_LOCKED) ? 1'b1 : vtd_locked;

end

end

// input, output, and delay registers

always @ (posedge VID_IN_CLK) begin

if(VID_RESET) begin

de_1           <= 1'b0;  

de_2           <= 1'b0;

de_3           <= 1'b0;

vblank_1       <= 1'b0;

hblank_1       <= 1'b0;

vsync_1        <= 1'b0;

hsync_1        <= 1'b0;

field_id_1     <= 1'b0;

field_id_2     <= 1'b0;

field_id_3     <= 1'b0;

data_1         <= {C_NATIVE_DATA_WIDTH{1'b0}};

data_2         <= {C_NATIVE_DATA_WIDTH{1'b0}};

data_3         <= {C_NATIVE_DATA_WIDTH{1'b0}};

v_blank_sync_2 <= 1'b0;

eol            <= 1'b0;

sof            <= 1'b0;

sof_1          <= 1'b0;

end else if(VID_CE) begin

de_1           <= VID_ACTIVE_VIDEO;

de_2           <= de_1;    

de_3           <= de_2;    

vblank_1       <= VID_VBLANK;

hblank_1       <= VID_HBLANK;

vsync_1        <= VID_VSYNC;

hsync_1        <= VID_HSYNC;

field_id_1     <= VID_FIELD_ID;

field_id_2     <= field_id_1;

field_id_3     <= field_id_2;

data_1         <= VID_DATA;

data_2         <= data_1;

data_3         <= data_2;

v_blank_sync_2 <= v_blank_sync_1;

eol            <= de_falling;

sof            <= de_rising && vert_blanking_intvl;

sof_1          <= sof;

end      

end

// Vertical back porch SR register

always @ (posedge VID_IN_CLK) begin

if (VID_CE) begin

if (vsync_rising)   // falling edge of vsync

vert_blanking_intvl <= 1;

else if (de_rising)        // rising edge of data enable

vert_blanking_intvl <= 0;

end

end

endmodule

在上面代码中,

eol            <= de_falling;

sof            <= de_rising && vert_blanking_intvl;

eol 实际就是tlast信号,而sof就是tuser信号。tlast信号代表每行图像数据的最后一个数据,tuser代表每场数据的第一个数据。

所有非常关键的信号都和de_falling 和vert_blanking_intvl有关系。

hs_o和vid_in IP的连接关系。

上图中,被红色圈起来的hs_o信号,同时接到了vid_in ip的vid_active_video和vid_hsync信号接口。因此,de信号就是hs_o信号,而vid_hsync 我们发现没有任何作用,也就是说不hs_o不连接到vid_hsync也不影响这里的程序工作。

VID_CE这个参数就是前面的vid_io_in_ce信号,可以看出这个芯片有效的时候相对应的时序电路才会执行。在本工程中,摄像头每2个pclk输出1个有效的数据,而vid_in IP如果VID_CE为1则数据输入会每个时钟输入1个就错了。因此官方的IP设计的还是很不错考虑周到,通过VID_CE这个条件,控制时钟同步。

...

else if(VID_CE) begin

...

end

...

现在回到OV_Sensor_ML的cmos_decode_v1.v文件中有一段红色的代码如下:

assign vid_clk_ce = out_en ? (byte_flag_r0&hs_o)||(!hs_o) : 1'b0;

这段代码控制了vid_clk_ce的正确输出,关键部分是(byte_flag_r0&hs_o)||(!hs_o)。当hs_o有效的时候,vid_in的VID_CE信号就有效,当hs_o=0的时候VID_CE必须仍然有效,这样才能检测到vsync_rising信号了,检测到了vsync_rising才能有ert_blanking_intvl为1,才有tuser信号。

好了罗嗦了半天,终于解释完了,如果有不清楚的,找我们技术支持吧。

5.4 VTC IP的分析

5.4.1 VTC IP的参数介绍

这个IP就是一个时序发生器,产生显示器输出所需要的时序信号。

这个页面中,incluse AXI-lite interface可以不勾选,不勾选就只能采用默认设置,无法在C语言中灵活配置了,所以笔者这里建议大家勾选吧。max clocks per line 和 max_lines per frame 需要设置下,当设置到4096的时候可以支持分辨率到最大,当然消耗的资源也更多。笔者这里太奢侈了设置了4096。实际上设置到2048就够用了。本页面的其他信号可以采取默认设置。

Enable Generation:

支持产生时序,这个肯定是必须勾选的。

Enable Detection:

支持时序扑捉,这个不是必须的,根据需要而定,如果设置了这个选项,就可以先扑捉输入的时序,然后再设置输出的时序,实现输入和输出一致的效果。

在这个页面中,只要选择需要支持的分辨率就可以了,当然不设置也没关系的,因为我们在C代码综合那个会进一步设置的。

5.4.2 VTC IP接口信号的定义


红色方框内的绝大部分信号需要我们手动联系,所以下面重点是讲解红色方框内的信号作用,至于AXI4-LITE接口主要是用来设置参数的。

Common Port Descriptions

本例子中没有使用到输入时序的扑捉,因此笔者下面只对用到的信号做一些介绍。

hsync_out

产生行同步输出

hsync_out

产生行消影

vsync_out:

产生场同步输出

vblank_out:

产生场消影

active_video_out:

有效数据输出

5.4.3 VTC IP配置寄存器

shows the start of the horizontal front porch (Hblank Start), synchronization

(Hsync Start), back porch (Hsync End) and active video (SAV). It also shows the start of the

vertical front porch (Vblank Start), synchronization (Vsync Start), back porch (Vsync End)

and active video (SAV). The total number of horizontal clock cycles is HSIZE and the total

number of lines is the VSIZE.

Generator Active Size Register (Address Offset 0x0060)

这是重要的寄存器用来设置有效的行数量和场数量

Generator Timing Status Register (Address Offset 0x0064)

GEN_ACTIVE_VIDEO:当第一帧图像输出时候置1

GEN_VBLANK:第一帧有效图像的blank信号输出的时候置1

Generator Encoding Register (Address Offset 0x0068)

CHROMA_PARITY:奇偶色度(读者没明白)

FIELD_ID_PARITY:奇偶场标志

INTERLACED:视频格式是渐进式还是各行扫描

VIDEO_FORMAT:视频格设置,有YUV422  YUV444  YUV420  RGB

Generator Polarity Register (Address Offset 0x006C)

这个寄存器设置相应的场输出极性和色度输出极性。

Generator Horizontal Frame Size Register (Address Offset 0x0070)

一副图像的一行的大小,包括了消隐和有效数据阶段。

Generator Vertical Frame Size Register (Address Offset 0x0074)

一副图像的一场的大小,包括了消隐和有效数据阶段。


Generator Horizontal Sync Register (Address Offset 0x0078)

设置行的水平同步结束和同步开始

Generator Frame/Field 0 Vertical Blank Cycle Register (Address Offset 0x007C)

设置Fram/Field0的水平消隐结束和开始

Generator Frame/Field 0 Vertical Sync Line Register (Address Offset 0x0080)

设置Fram/Field0的垂直同步垂直结束和开始
Generator Frame/Field 0 Vertical Sync Cycle Register (Address Offset 0x0084)

设置Fram/Field0的垂直同步水平结束和开始

Generator Field 1 Vertical Blank Cycle Register (Address Offset 0x0088)

设置Field1的水平消隐结束和开始

Generator Field 1 Vertical Sync Line Register (Address Offset 0x008C)

设置Field1的垂直同步垂直结束和开始

Generator Field 1 Vertical Sync Cycle Register (Address Offset 0x0090)

设置Field1的垂直同步水平结束和开始

Frame Sync 015 Configuration Registers (Address Offsets 0x0100 0x013C)

Generator Global Delay Register (Address Offset 0x140)


5.4.5设置VTC IP

讲了这么多实际上我们用的时候很简单,所以只要这么简单。

由于不使用动态配置,并且只使用了视频时序产生,所以只要勾选如下复选框。

由于OV7725分辨率是640X480因此直接选择640PX480就可以了。

5.6 PLL时钟设置

由于这里的分辨率是640X480因此提供给VTC IP 和VID OUTIP的时钟只要25M就可以了

MIZ702/MIZ702N时钟设置

MIZ701N时钟设置


5.7 VID_OUT IP的分析

5.7.1 VID_OUT 的参数介绍

这些参数和前面的V_TPG参数类似

• Pixels Per Clock: 设置每个时钟输出的像素个数,可以是1、2、4

• Input Component Width: 输入像素的宽度,这个参数影响TDATA的位宽

• Output Component Width:输出像素的宽度

• Clock Mode:时钟的模式,可以选择独立时钟,或者共享时钟

• Video Format: 视频格式

• FIFO Depth: FIFO深度

• Hysteresis Level: 滞后输出


5.7.2 VID_OUT IP接口信号的定义

Video Timing Interface

AXI4‐Stream Interface

对于s_axis_video_tdata(TDATA)需要注意一些事情,一般情况下我们的RGB888 输出,但是,如果s_axis_video_tdata是32bit 那么VID_OUT IP会自动截取到24bit。由于技术手册只给出了12bit 到 8bit 的截取方式,也就是RGB 12:12:12 到RGB 8:8:8如下图:

这种截取比较简单,把每个色度的低4bit截取就可以了。但是如果是RGB10:10:10 ,官方并没有给出截取方式,但是可以通过纯色输出来进行测试。

因此最简单的办法是无需任何截取了,如果s_axis_video_tdata是RGB8:8:8 那就无需任何截取,笔者设计的时候由于AXI 总线是32bit 因此数据的低24bit为RGB 8:8:8只要去掉高24-31bit就可以取得RGB8:8:8,这样最省事。

以下时序图是在SOF是一帧图像的开始,当VALID 和 READY有效的时候开始传输数据。

EOL代表每一行的最后一个数据,SOF代表前一帧的最后一行的结束,下一帧第一行的开始。SOF为1个PLUS有效(pg044_v_axis_out.pdf 没有描述清楚,而且有错误)。

Example Horizontal Generation Register Inputs

设置水平输出的相关寄存器

水平输出时序图

Example Vertical Generation Register Inputs

设置垂直输出的相关寄存器

垂直输出时序图

5.8 FPGA 实现的用户逻辑代码

5.8.1关键信号1

assign s_axis_s2mm_tlast = m_axis_video_tvalid & s_axis_s2mm_tready & m_axis_video_tlast &(vid_in_v_cnt == VID_IN_VS);// dma in last signal

m_axis_video_tvalid:此信号是vid in IP输出的,代表输出数据有效

s_axis_s2mm_tready:此信号是DMA IP 输出的,代表DMA可以接收数据

m_axis_video_tlast:这是每一行图像数据的最后一个像素的信号标志

vid_in_v_cnt == VID_IN_VS:表示一副图像的最后一个像素输出。

s_axis_s2mm_tlast:所有这些信号有效的时候代表DMA的最后一个数据s_axis_s2mm_tlast信号有效。

5.8.2关键信号2

assign s_axis_video_tuser = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == 11'd0) & (vid_out_v_cnt == 11'd0); //vid out user

m_axis_mm2s_tvalid:是M_AXIS_MM2S接口(读DMA接口)的数据有效标志。

s_axis_video_tready:vid out IP 准备好了,可以接收数据

(vid_out_h_cnt == 11'd0) & (vid_out_v_cnt == 11'd0);行计数器为0场计数器也为0说明要么这副图像已经结束,也可以理解为下一副图像开始前。这样结合s_axis_video_tready,m_axis_mm2s_tvalid为1,基于FPGA时序,下一个时钟输出s_axis_video_tuser为1正好是一副图像的第一个像素。

s_axis_video_tuser:因此s_axis_video_tuser代表了每一副图像开始的第一个像素。

5.8.3关键信号3

assign s_axis_video_tlast = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == VID_OUT_HS);//vid out last signal

m_axis_mm2s_tvalid:是M_AXIS_MM2S接口(读DMA接口)的数据有效标志。

s_axis_video_tready:vid out IP 准备好了,可以接收数据

vid_out_h_cnt == VID_OUT_HS):图像一行数据的最后一个像素。

5.8.4 部分关键代码

表3-6-4-1

reg [10:0] vid_out_v_cnt;

reg [10:0] vid_out_h_cnt;

reg [10:0] vid_in_v_cnt;

parameter VID_OUT_HS = 11'd639;//图像输出行分辨率

parameter VID_OUT_VS = 11'd479;//图像输出场分辨率

parameter VID_IN_VS = 11'd479;

always@(posedge FCLK_CLK0)

begin

if(!gpio_rtl_tri_o_0)

vid_out_v_cnt <= 11'd0;

else

if(m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == VID_OUT_HS))

if(vid_out_v_cnt != VID_OUT_VS)

vid_out_v_cnt <= vid_out_v_cnt + 1'b1;

else

vid_out_v_cnt <= 11'd0;

else

vid_out_v_cnt <= vid_out_v_cnt;

end

always@(posedge FCLK_CLK0)

begin

if(!gpio_rtl_tri_o_0)

vid_out_h_cnt <= 11'd0;

else

if(m_axis_mm2s_tvalid & s_axis_video_tready)

if(vid_out_h_cnt != VID_OUT_HS)

vid_out_h_cnt <= vid_out_h_cnt + 1'b1;

else

vid_out_h_cnt <= 11'd0;

else

vid_out_h_cnt <= vid_out_h_cnt;

end

always@(posedge FCLK_CLK0)

begin

if(!gpio_rtl_tri_o_0)

vid_in_v_cnt <= 11'd0;

else

if(m_axis_video_tvalid & s_axis_s2mm_tready & m_axis_video_tlast)

if(vid_in_v_cnt != VID_IN_VS)

vid_in_v_cnt <= vid_in_v_cnt + 1'b1;

else

vid_in_v_cnt <= 11'd0;

else

vid_in_v_cnt <= vid_in_v_cnt;

end

assign s_axis_video_tuser = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == 11'd0) & (vid_out_v_cnt == 11'd0); //vid out user

assign s_axis_video_tlast = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == VID_OUT_HS);//vid out last signal

assign s_axis_s2mm_tlast = m_axis_video_tvalid & s_axis_s2mm_tready & m_axis_video_tlast &(vid_in_v_cnt == VID_IN_VS);// dma in last signal

5.9 PS部分

5.9.1 DMA中断函数部分分析

为了让图像输出高品质效果,PS部分设计了3缓存处理机制。3缓存处理机制在大量图像缓冲处理方法是最有效的办法之一。

在DMA_intr.h文件中,定义3段内存空间用于保存三副最新的图像。

#define BUFFER0_BASE (MEM_BASE_ADDR )

#define BUFFER1_BASE (MEM_BASE_ADDR + IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

#define BUFFER2_BASE (MEM_BASE_ADDR + 2 * IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

在DMA_intr.h文件中,还定义一下2个变量1个指针数组。tx_buffer_index;指示了当前的发送缓存序号,rx_buffer_index;指示了当前的接收缓存序号。*BufferPtr[3]会被制定到对应的内存地址空间。

extern volatile u8 tx_buffer_index;

extern volatile u8 rx_buffer_index;

extern u32 *BufferPtr[3];

在main函数里面有这么一段实现了指针数组指向内存地址空间。

BufferPtr[0] = (u32 *)BUFFER0_BASE;

BufferPtr[1] = (u32 *)BUFFER1_BASE;

BufferPtr[2] = (u32 *)BUFFER2_BASE;

下面给出dma_intr.h的完整代码

表3-7-1-1 dma_intr.h

/*

*

* www.osrc.cn

* www.milinker.com

* copyright by nan jin mi lian dian zi www.osrc.cn

*/

#ifndef DMA_INTR_H

#define DMA_INTR_H

#include "xaxidma.h"

#include "xparameters.h"

#include "xil_exception.h"

#include "xdebug.h"

#include "xscugic.h"

/************************** Constant Definitions *****************************/

/*

* Device hardware build related constants.

*/

#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID

#define MEM_BASE_ADDR 0x10000000

#define RX_INTR_ID XPAR_FABRIC_AXI_DMA_0_S2MM_INTROUT_INTR

#define TX_INTR_ID XPAR_FABRIC_AXI_DMA_0_MM2S_INTROUT_INTR

#define IMAGE_WIDTH     640

#define IMAGE_HEIGHT 480

#define BYTES_PER_PIXEL 4

#define BUFFER_NUM     2

#define MEM_BASE_ADDR 0x10000000

#define BUFFER0_BASE (MEM_BASE_ADDR )

#define BUFFER1_BASE (MEM_BASE_ADDR +     IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

#define BUFFER2_BASE (MEM_BASE_ADDR + 2 * IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

/* Timeout loop counter for reset

*/

#define RESET_TIMEOUT_COUNTER 10000

/* test start value

*/

#define TEST_START_VALUE 0xC

/*

* Buffer and Buffer Descriptor related constant definition

*/

#define MAX_PKT_LEN (IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

/*

* transfer times

*/

#define NUMBER_OF_TRANSFERS 100000

extern volatile int TxDone;

extern volatile int RxDone;

extern volatile int Error;

extern  volatile u8 tx_buffer_index;

extern  volatile u8 rx_buffer_index;

extern  u32 *BufferPtr[3];

int  DMA_CheckData(int Length, u8 StartValue);

int  DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId);

int  DMA_Intr_Enable(XScuGic * IntcInstancePtr,XAxiDma *DMAPtr);

int  DMA_Intr_Init(XAxiDma *DMAPtr,u32 DeviceId);

#endif

每次一整副图像通过DMA进入DDR后,会产生DMA中断请求,在DMA中断请求中,会指定下一次DMA接收的buffer位置。

表3-7-1-2 DMA_RxIntrHandler函数

/*****************************************************************************/

/*

*

* This is the DMA RX interrupt handler function

*

* It gets the interrupt status from the hardware, acknowledges it, and if any

* error happens, it resets the hardware. Otherwise, if a completion interrupt

* is present, then it sets the RxDone flag.

*

* @param Callback is a pointer to RX channel of the DMA engine.

*

* @return None.

*

* @note None.

*

******************************************************************************/

static void DMA_RxIntrHandler(void *Callback)

{

u32 IrqStatus;

u32 Status;

int TimeOut;

XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

/* Read pending interrupts */

IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);

/* Acknowledge pending interrupts */

XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

/*

* If no interrupt is asserted, we do not do anything

*/

if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

return;

}

/*

* If error interrupt is asserted, raise error flag, reset the

* hardware to recover from the error, and return with no further

* processing.

*/

if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

xil_printf("rx error! \r\n");

return;

}

/*

* If completion interrupt is asserted, then set RxDone flag

*/

if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

RxDone++;

}

if(rx_buffer_index == 2)

rx_buffer_index = 0;

else

rx_buffer_index++;

Status = XAxiDma_SimpleTransfer(AxiDmaInst, (u32)BufferPtr[rx_buffer_index],

MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

if (Status != XST_SUCCESS) {

xil_printf("rx axi dma failed! 0 %d\r\n", Status);

return;

}

}

发送函数通过tx_buffer_index标记需要发送的缓存部分,并且确保发送的是最新保存的一副图像。

表3-7-3 DMA_TxIntrHandler

/*****************************************************************************/

/*

*

* This is the DMA TX Interrupt handler function.

*

* It gets the interrupt status from the hardware, acknowledges it, and if any

* error happens, it resets the hardware. Otherwise, if a completion interrupt

* is present, then sets the TxDone.flag

*

* @param Callback is a pointer to TX channel of the DMA engine.

*

* @return None.

*

* @note None.

*

******************************************************************************/

static void DMA_TxIntrHandler(void *Callback)

{

u32 IrqStatus;

u32 Status;

int TimeOut;

XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

/* Read pending interrupts */

IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);

/* Acknowledge pending interrupts */

XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);

/*

* If no interrupt is asserted, we do not do anything

*/

if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

return;

}

/*

* If error interrupt is asserted, raise error flag, reset the

* hardware to recover from the error, and return with no further

* processing.

*/

if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

//Error = 1;

xil_printf("tx error! \r\n");

return;

}

/*

* If Completion interrupt is asserted, then set the TxDone flag

*/

if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

TxDone ++;

}

if(rx_buffer_index == 0)

tx_buffer_index = 2;

else

tx_buffer_index = rx_buffer_index - 1;

Status = XAxiDma_SimpleTransfer(AxiDmaInst, (u32)BufferPtr[tx_buffer_index],

MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

if (Status != XST_SUCCESS) {

xil_printf("tx axi dma failed! 0 %d\r\n", Status);

return;

}

}

5.9.2 main.c文件

这个主程序比较简单,内容比上一个课程的精简很多,这里需要注意的地方是XGpio_DiscreteWrite(&Gpio, 1, 1);函数这个函数是这只摄像头和DMA之间数据同步的,没有这个同步图像容易错位。另外在主函数里面首先启动DMA接收和发送中断各一次,以后就可以在中断里面继续触发了。

表3-7-2-1 main.c

/*

*

* www.osrc.cn

* www.milinker.com

* copyright by nan jin mi lian dian zi www.osrc.cn

* axi dma test

*

*/

#include "dma_intr.h"

#include "sys_intr.h"

#include "xgpio.h"

volatile int TxDone;

volatile int RxDone;

volatile int Error;

volatile u8 tx_buffer_index;

volatile u8 rx_buffer_index;

u32 *BufferPtr[3];

static XScuGic Intc; //GIC

static XAxiDma AxiDma;

static XGpio Gpio;

#define AXI_GPIO_DEV_ID         XPAR_AXI_GPIO_0_DEVICE_ID

int init_intr_sys(void)

{

DMA_Intr_Init(&AxiDma,0);//initial interrupt system

Init_Intr_System(&Intc); // initial DMA interrupt system

Setup_Intr_Exception(&Intc);

DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//setup dma interrpt system

DMA_Intr_Enable(&Intc,&AxiDma);

}

int main(void)

{

u32 Status;

BufferPtr[0] = (u32 *)BUFFER0_BASE;

BufferPtr[1] = (u32 *)BUFFER1_BASE;

BufferPtr[2] = (u32 *)BUFFER2_BASE;

tx_buffer_index = 0;

rx_buffer_index = 0;

TxDone = 0;

RxDone = 0;

Error = 0;

XGpio_Initialize(&Gpio, AXI_GPIO_DEV_ID);

XGpio_SetDataDirection(&Gpio, 1, 0);

init_intr_sys();

Miz702_EMIO_init();

ov7725_init_rgb();

XGpio_DiscreteWrite(&Gpio, 1, 1);

Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)BufferPtr[rx_buffer_index],

MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)BufferPtr[tx_buffer_index],

MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

while (1) ;

return XST_SUCCESS;

}

5.10实验效果

S03_CH05_AXI_DMA_HDMI图像输出的更多相关文章

  1. python二维图像输出操作大全(非常全)!

    //2019.07.141.matplotlib模块输出函数图像应用时主要用的是它的ptplot模块,因此在导入使用该模块时可以直接用以下语句:import matplotlib.pyplot as ...

  2. S03_CH04_AXI_DMA_OV5640摄像头采集系统

    S03_CH04_AXI_DMA_OV5640摄像头采集系统 4.1概述 本课程讲解如何搭建基于DMA的图形系统,方案原理和搭建7725的一样,只是OV5640显示的分辨率是1280X720如下,只是 ...

  3. S03_CH03_AXI_DMA_OV7725摄像头采集系统

    S03_CH03_AXI_DMA_OV7725摄像头采集系统 3.1概述 本课程讲解如何搭建基于DMA的图形系统,方案原理如下. 摄像头采样图像数据后通过DMA送入到DDR,在PS部分产生DMA接收中 ...

  4. 【OpenCV入门教程之三】 图像的载入,显示和输出 一站式完全解析(转)

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/20537737 作者:毛星云(浅墨)  ...

  5. 基于window7+caffe实现图像艺术风格转换style-transfer

    这个是在去年微博里面非常流行的,在git_hub上的代码是https://github.com/fzliu/style-transfer 比如这是梵高的画 这是你自己的照片 然后你想生成这样 怎么实现 ...

  6. PHP图形操作之生成图像验证码

    简单的验证码其实就是在图片中输出了几个字符,通过imagestring函数就能实现. 但是在处理上,为了使验证码更加的安全,防止其他程序自动识别,因此常常需要对验证码进行一些干扰处理,通常会采用绘制一 ...

  7. 【Windows编程】系列第三篇:文本字符输出

    上一篇我们展示了如何使用Windows SDK创建基本控件,本篇来讨论如何输出文本字符. 在使用Win32编程时,我们常常要输出文本到窗口上,Windows所有的文本字符或者图形输出都是通过图形设备接 ...

  8. MVC控制下输出图片、javascript与json格式

    /// <summary> /// 输出图片 /// </summary> /// <returns></returns> public ActionR ...

  9. PHP获取远程图片并调整图像大小(转)

    <?php /** * *函数:调整图片尺寸或生成缩略图 *修改:2013-2-15 *返回:True/False *参数: * $Image 需要调整的图片(含路径) * $Dw=450 调整 ...

随机推荐

  1. [Codeforces1137F]Matches Are Not a Child's Play——LCT+树状数组

    题目链接: [Codeforces1137F]Matches Are Not a Child's Play 题目大意: 我们定义一棵树的删除序列为:每一次将树中编号最小的叶子删掉,将该节点编号加入到当 ...

  2. Spring —— @Async注解的使用

    参考文档 Spring Boot使用@Async实现异步调用:自定义线程池 Spring Boot使用@Async实现异步调用:ThreadPoolTaskScheduler线程池的优雅关闭

  3. [软工]Github的使用

    注册 修改个人信息 fork项目 使用github客户端 commit项目 发送PR 注意事项 不要使用上述项目进行试验 建议Github用户名有规律,好记忆

  4. qt mvc1

    mvc是经典的三层结构,将数据,视图和逻辑分离.Qt中的Model/View框架,实现了这个模式.在Qt中这个模式设计到三个类,model类,view类和delegate类.model类保存数据,vi ...

  5. 【转载】 AutoML相关论文

    原文地址: https://www.cnblogs.com/marsggbo/p/9308518.html ---------------------------------------------- ...

  6. Hibernate 自动更新表出错 More than one table found in namespace

    报错:Caused by: org.hibernate.tool.schema.extract.spi.SchemaExtractionException: More than one table f ...

  7. 在Springmvc普通类@Autowired注入request为null解决方法

    在Springmvc普通类@Autowired注入request为null解决方法   在类中加入以下注入request对象的代码,运行时发现request为null,注入失败.在@Controlle ...

  8. LeetCode_67. Add Binary

    67. Add Binary Easy Given two binary strings, return their sum (also a binary string). The input str ...

  9. (十六)toString()的用法

    每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你却只有一个对象时候,该方法便会被调用. public class te { public String to ...

  10. redis未设置idle超时时间导致连接过多

    今天ELK收集日志的时候,发现收集失败,查找各方面原因,最后在redis日志里面发现报错:[2489] 02 Jun 10:43:42 # Error allocating resoures for ...