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接口信號的定義
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; } |