S03_CH05_AXI_DMA_HDMI图像输出
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 0‐15 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图像输出的更多相关文章
- python二维图像输出操作大全(非常全)!
//2019.07.141.matplotlib模块输出函数图像应用时主要用的是它的ptplot模块,因此在导入使用该模块时可以直接用以下语句:import matplotlib.pyplot as ...
- S03_CH04_AXI_DMA_OV5640摄像头采集系统
S03_CH04_AXI_DMA_OV5640摄像头采集系统 4.1概述 本课程讲解如何搭建基于DMA的图形系统,方案原理和搭建7725的一样,只是OV5640显示的分辨率是1280X720如下,只是 ...
- S03_CH03_AXI_DMA_OV7725摄像头采集系统
S03_CH03_AXI_DMA_OV7725摄像头采集系统 3.1概述 本课程讲解如何搭建基于DMA的图形系统,方案原理如下. 摄像头采样图像数据后通过DMA送入到DDR,在PS部分产生DMA接收中 ...
- 【OpenCV入门教程之三】 图像的载入,显示和输出 一站式完全解析(转)
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/20537737 作者:毛星云(浅墨) ...
- 基于window7+caffe实现图像艺术风格转换style-transfer
这个是在去年微博里面非常流行的,在git_hub上的代码是https://github.com/fzliu/style-transfer 比如这是梵高的画 这是你自己的照片 然后你想生成这样 怎么实现 ...
- PHP图形操作之生成图像验证码
简单的验证码其实就是在图片中输出了几个字符,通过imagestring函数就能实现. 但是在处理上,为了使验证码更加的安全,防止其他程序自动识别,因此常常需要对验证码进行一些干扰处理,通常会采用绘制一 ...
- 【Windows编程】系列第三篇:文本字符输出
上一篇我们展示了如何使用Windows SDK创建基本控件,本篇来讨论如何输出文本字符. 在使用Win32编程时,我们常常要输出文本到窗口上,Windows所有的文本字符或者图形输出都是通过图形设备接 ...
- MVC控制下输出图片、javascript与json格式
/// <summary> /// 输出图片 /// </summary> /// <returns></returns> public ActionR ...
- PHP获取远程图片并调整图像大小(转)
<?php /** * *函数:调整图片尺寸或生成缩略图 *修改:2013-2-15 *返回:True/False *参数: * $Image 需要调整的图片(含路径) * $Dw=450 调整 ...
随机推荐
- h5播放rtsp流
最近由于项目上需要一个摄像头在线预览的功能,于是便琢磨了一个小玩意出来分享分享.项目是在win上,合作的人懂js,基于这样的情况,我只选择nodejs作为开发.并未使用php相关. 一开始做这个,我并 ...
- docker安装hbase
.下载安装Hbase: ().docker search hbase : 查找Hbase ().docker pull harisekhon/hbase:1.3 注意:不要安装最新版本的,不稳定 (我 ...
- Java 面向对象(十一)
常用类之集合 集合:就是用来存放数据的一个容器. 数组和集合的区别 (1)数组能存基本数据类型和引用类型:集合当中只能存放引用数据类型,直接放基本数据类型,也会自动帮你装箱(把基本数据类型转成对象), ...
- 学JavaScript的感想小结1
学了几天的Javascript,刚开始就在想Java和JavaScript有什么不同,算了其实两个咱都不会也没多想了,带着这个好奇心学菜鸟教程,没想到还真得到了解答,瞬间兴趣提升,愿意追根溯源的教程还 ...
- uboot移植spi驱动
记录一下在uboot内移植spi驱动的过程 芯片:freescale Mpc8308 uboot版本:u-boot-2009.11-rc1.2 需求:我们需要在uboot下通过spi配置一个时钟芯片( ...
- Mysql字段修饰符(约束)
(1).null和not null not null不可以插入null,但可以插入空值. 数值型.字符型.日期型都可以插入null,但只有字符型可以插入空值. 使用方法如下: mysql> cr ...
- Day7作业:选课系统
这周的作业有点糙,迁就看吧,给大家点思路: readme: 需要安装模块: prettytable 测试帐号: 1.后台管理:admin/admin 只设定了这个后台管理帐号,没有写到数据库中 2.学 ...
- 从成员函数指针生成可调用对象:function<>、mem_fn()和bind()
我们知道,普通函数指针是一个可调用对象,但是成员函数指针不是可调用对象.因此,如果我们想在一个保存string的vector中找到第一个空string,不能这样写: vector<string& ...
- vim 中与编码有关的选项
在 Vim 中,有四个与编码有关的选项,它们是:fileencodings.fileencoding.encoding 和 termencoding.在实际使用中,任何一个选项出现错误,都会导致出现乱 ...
- 区块链学习(四)truffle部署编译智能合约以太坊私有链
前面我们介绍了以太坊私有链的搭建以及多节点私有链网络,这次我们介绍如何使用truffle框架来部署编译智能合约到我们之前搭建的私有链网络中. 搭建环境及需使用的工具:ubuntu18.04 Truf ...