S03_CH03_AXI_DMA_OV7725摄像头采集系统
S03_CH03_AXI_DMA_OV7725摄像头采集系统
3.1概述
本课程讲解如何搭建基于DMA的图形系统,方案原理如下。
摄像头采样图像数据后通过DMA送入到DDR,在PS部分产生DMA接收中断,在接收中断里面再把DDR里面保持的图形数据DMA发送出去。在FPGA的接收端口部分产生VID OUT时序驱动VGA显示器显示图形。MIZ701N没有VGA接口,可以跳过直接看《S03_CH05_AXI_DMA_HDMI图像输出》或者大家不想看本章的也可以直接跳到《S03_CH05_AXI_DMA_HDMI图像输出》这两节课的核心教学内容一样。《S03_CH03_AXI_DMA_OV7725摄像头采集系统》、《S03_CH04_AXI_DMA_OV5640摄像头采集系统》、《S03_CH05_AXI_DMA_HDMI图像输出》。读者可以根据自己需求情况而阅读,请知悉。
3.2系统构架
3.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信号输出。
3.2.2构BLOCK模块化设计方案图
3.3 vid in IP介绍
3.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
3.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:时钟的模式,可以选择独立时钟,或者共享时钟
3.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信号。
好了罗嗦了半天,终于解释完了,如果有不清楚的,找我们技术支持吧。
3.4 VTC IP的分析
3.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代码综合那个会进一步设置的。
3.4.2 VTC IP接口信号的定义
红色方框内的绝大部分信号需要我们手动联系,所以下面重点是讲解红色方框内的信号作用,至于AXI4-LITE接口主要是用来设置参数的。
Common Port Descriptions:
本例子中没有使用到输入时序的扑捉,因此笔者下面只对用到的信号做一些介绍。
hsync_out:
产生行同步输出
hsync_out:
产生行消影
vsync_out:
产生场同步输出
vblank_out:
产生场消影
active_video_out:
有效数据输出
3.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)
3.4.5设置VTC IP
讲了这么多实际上我们用的时候很简单,所以只要这么简单。
由于不使用动态配置,并且只使用了视频时序产生,所以只要勾选如下复选框。
由于OV7725分辨率是640X480因此直接选择640PX480就可以了。
3.6 PLL时钟设置
由于这里的分辨率是640X480因此提供给VTC IP 和VID OUTIP的时钟只要25M就可以了
3.7 VID_OUT IP的分析
3.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: 滞后输出
3.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
设置垂直输出的相关寄存器
垂直输出时序图
3.8 FPGA 实现的用户逻辑代码
3.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信号有效。
3.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代表了每一副图像开始的第一个像素。
3.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):图像一行数据的最后一个像素。
3.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 |
3.9 PS部分
3.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; } } |
3.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; } |
3.10实验效果
S03_CH03_AXI_DMA_OV7725摄像头采集系统的更多相关文章
- S03_CH04_AXI_DMA_OV5640摄像头采集系统
S03_CH04_AXI_DMA_OV5640摄像头采集系统 4.1概述 本课程讲解如何搭建基于DMA的图形系统,方案原理和搭建7725的一样,只是OV5640显示的分辨率是1280X720如下,只是 ...
- S03_CH07_AXI_VDMA_OV5640摄像头采集系统
S03_CH07_AXI_VDMA_OV5640摄像头采集系统 7.1概述 本章内容和<S03_CH06_AXI_VDMA_OV7725摄像头采集系统>只是摄像头采用的分辨率不同,其他原理 ...
- S03_CH06_AXI_VDMA_OV7725摄像头采集系统
S03_CH06_AXI_VDMA_OV7725摄像头采集系统 本课程将对Xilinx提供的一款IP核--AXI VDMA(Video Direct Memory Access) 进行详细讲解,为后续 ...
- S03_CH05_AXI_DMA_HDMI图像输出
S03_CH05_AXI_DMA_HDMI图像输出 5.1概述 本课程是在前面课程基础上添加HDMI IP 实现HDMI视频图像的输出.本课程出了多了HDMI输出接口,其他内容和<S03_CH0 ...
- 摄像头驱动的使能配置、V4L2编程接口的设计应用
摄像头采集子系统 一.摄像头驱动的使能配置 摄像头软件驱动构架 摄像头采集系统由上图所示,硬件(摄像头) -> 驱动(Linux内核配置中,选择支持V4L2的驱动选项) -> V4L2接口 ...
- 移动物体监控系统-sprint2摄像头子系统开发
一.摄像头使能驱动 1.1 摄像头软件系统构架 摄像头采集系统按照上图,硬件(摄像头)->摄像头驱动 ->V4L2接口规范 ->图像采集(应用).V4L2将不同类型的摄像头设备按照统 ...
- 玩转摄像头之 基于SDRAM缓冲 USB2.0视频采集系统 MT9T001、MT9P031 演示 展示
玩转摄像头之 基于SDRAM缓冲 USB视频采集系统 MT9T001.MT9P031 最新设计的系统: 核心板(FPGA+SDRAM)+底板(68013+DVP)+sensor 先看图 核心板 正 ...
- USB视频采集系统 视频测试软件将正式发布(方便调试测试各自摄像头,RAW,RGB,YUV)
先上图,看看这个软件,学习fpga将近一年,了解视频图像开发方向也半年有余,不断学习不断总结,开发软件工具是为了更方便的学习新通信 主要相关知识: FPGA+SDRAM+VGA(双端口fifo技术) ...
- 基于Xilinx FPGA的视频图像采集系统
本篇要分享的是基于Xilinx FPGA的视频图像采集系统,使用摄像头采集图像数据,并没有用到SDRAM/DDR.这个工程使用的是OV7670 30w像素摄像头,用双口RAM做存储,显示窗口为320x ...
随机推荐
- Atcoder ABC 139A
Atcoder ABC 139A 题意: 给你两个字符串,记录对应位置字符相同的个数 $ (n=3) $ 解法: 暴力枚举. CODE: #include<iostream> #inclu ...
- linux安装puppeteer
1.安装 下载淘宝镜像的,可以同时下载puppeteer和chromium下面两条语句即可 npm install -g cnpm --registry=https://registry.npm.ta ...
- Python generator 类型
场景: 使用gurobi求解优化问题时,遇到quicksum()函数用法如下: quicksum(mu[i] for i in range(n)) 读着很流畅而且好像并没什么问题欸,但 mu[i] f ...
- <JavaScript> 稳妥构造函数模式与工厂模式的区别
稳妥构造函数模式的代码应该是这样的: function Person(name, age, job) { var o = new Object(); // private members var na ...
- c++ Container print
template<typename Container>void PrintContents(const Container& con) { Container::const_it ...
- 用Keras搭建神经网络 简单模版(四)—— RNN Classifier 循环神经网络(手写数字图片识别)
# -*- coding: utf-8 -*- import numpy as np np.random.seed(1337) from keras.datasets import mnist fro ...
- es6 map()和filter()详解【转】
原文地址:http://www.zhangxinxu.com/wordpress/2013/04/es5%e6%96%b0%e5%a2%9e%e6%95%b0%e7%bb%84%e6%96%b9%e6 ...
- mac Access denied for user 'root'@'localhost' (using password: YES)
1:苹果->系统偏好设置->最下边点mysql 在弹出页面中 关闭mysql服务 2: Start it in safe mode 进入终端 输入: cd /usr/local/mysql ...
- 【算法】矩阵填数,深度优先搜索(DFS),Pascal改C语言
面向对象的上机实验 题目 以下列方式向 5*5 矩阵中填入数字.设数字i(1=<i<=25),则数字i+1 的坐标位置应为(E, W).(E, W)可根据下列关系由(x,y)算出: 1)( ...
- 《剑指offer》字符串专题 (牛客11.01)
字符串的题目难度不一,涉及到的考点有字符串处理.字符串匹配(自动机.正则).模拟,以及递归.动态规划等算法. 难度 题目 知识点 ☆ 02. 替换空格 从后往前 ☆☆ 27. 字符串的排列 回溯,St ...