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構架方案圖

wps3BD4.tmp

攝像頭接口采集的攝像頭數據,進過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芯片方案)顯示構架圖

wps3BD5.tmp

MIZ701N的HDMI(FPGA IO模擬HDMI時序方案)顯示構架圖

5.3 vid in IP介紹

5.3.1 OV_Sensor_ML 自定義 IP模塊

wps3BE6.tmp

外部信號接口說明:

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模塊

wps3BF6.tmp

wps3BF7.tmp

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

wps3BF8.tmp

Common Interface wps3C09.tmpwps3C0A.tmp

Video Timing Interface

wps3C0B.tmp

Video Input Interface

wps3C1B.tmp

使用到的信號有:

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的連接關系。

wps3C1C.tmp

上圖中,被紅色圈起來的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的參數介紹

wps3C2D.tmp

wps3C2E.tmp

這個IP就是一個時序發生器,產生顯示器輸出所需要的時序信號。

這個頁面中,incluse AXI-lite interface可以不勾選,不勾選就只能采用默認設置,無法在C語言中靈活配置了,所以筆者這里建議大家勾選吧。max clocks per line 和 max_lines per frame 需要設置下,當設置到4096的時候可以支持分辨率到最大,當然消耗的資源也更多。筆者這里太奢侈了設置了4096。實際上設置到2048就夠用了。本頁面的其他信號可以采取默認設置。

Enable Generation:

支持產生時序,這個肯定是必須勾選的。

Enable Detection:

支持時序撲捉,這個不是必須的,根據需要而定,如果設置了這個選項,就可以先撲捉輸入的時序,然后再設置輸出的時序,實現輸入和輸出一致的效果。

wps3C2F.tmp

在這個頁面中,只要選擇需要支持的分辨率就可以了,當然不設置也沒關系的,因為我們在C代碼綜合那個會進一步設置的。

5.4.2 VTC IP接口信號的定義

wps3C30.tmp
紅色方框內的絕大部分信號需要我們手動聯系,所以下面重點是講解紅色方框內的信號作用,至於AXI4-LITE接口主要是用來設置參數的。

Common Port Descriptions

wps3C41.tmpwps3C42.tmp

wps3C46.tmpwps3C53.tmpwps3C54.tmp

本例子中沒有使用到輸入時序的撲捉,因此筆者下面只對用到的信號做一些介紹。

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.

wps3C55.tmp

Generator Active Size Register (Address Offset 0x0060)

wps3C66.tmp

這是重要的寄存器用來設置有效的行數量和場數量

Generator Timing Status Register (Address Offset 0x0064)

wps3C67.tmp

GEN_ACTIVE_VIDEO:當第一幀圖像輸出時候置1

GEN_VBLANK:第一幀有效圖像的blank信號輸出的時候置1

Generator Encoding Register (Address Offset 0x0068)

wps3C6B.tmp

CHROMA_PARITY:奇偶色度(讀者沒明白)

FIELD_ID_PARITY:奇偶場標志

INTERLACED:視頻格式是漸進式還是各行掃描

VIDEO_FORMAT:視頻格設置,有YUV422  YUV444  YUV420  RGB

Generator Polarity Register (Address Offset 0x006C)

wps3C78.tmp

wps3C79.tmp這個寄存器設置相應的場輸出極性和色度輸出極性。

Generator Horizontal Frame Size Register (Address Offset 0x0070)

wps3C7A.tmp

一副圖像的一行的大小,包括了消隱和有效數據階段。

Generator Vertical Frame Size Register (Address Offset 0x0074)

wps3C7F.tmp

一副圖像的一場的大小,包括了消隱和有效數據階段。


Generator Horizontal Sync Register (Address Offset 0x0078)

wps3C8C.tmp

設置行的水平同步結束和同步開始

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

wps3C8D.tmp

設置Fram/Field0的水平消隱結束和開始

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

wps3C91.tmp

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

wps3C9F.tmp

設置Fram/Field0的垂直同步水平結束和開始

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

wps3CA0.tmp

設置Field1的水平消隱結束和開始

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

wps3CA4.tmp

設置Field1的垂直同步垂直結束和開始

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

wps3CB1.tmp

設置Field1的垂直同步水平結束和開始

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

wps3CB2.tmp

Generator Global Delay Register (Address Offset 0x140)

wps3CB3.tmpwps3CB8.tmp

5.4.5設置VTC IP

講了這么多實際上我們用的時候很簡單,所以只要這么簡單。

wps3CC5.tmp

由於不使用動態配置,並且只使用了視頻時序產生,所以只要勾選如下復選框。

wps3CC6.tmp

由於OV7725分辨率是640X480因此直接選擇640PX480就可以了。
wps3CCA.tmp

5.6 PLL時鍾設置

由於這里的分辨率是640X480因此提供給VTC IP 和VID OUTIP的時鍾只要25M就可以了

MIZ702/MIZ702N時鍾設置

wps3CD7.tmp

MIZ701N時鍾設置

wps3CD8.tmp

5.7 VID_OUT IP的分析

5.7.1 VID_OUT 的參數介紹

wps3CD9.tmp

wps3CEA.tmp

這些參數和前面的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接口信號的定義

wps3CEB.tmp

wps3CF0.tmp

Video Timing Interface

wps3CFD.tmp

AXI4‐Stream Interface

wps3CFE.tmp

對於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如下圖:

wps3CFF.tmp

這種截取比較簡單,把每個色度的低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有效的時候開始傳輸數據。

wps3D03.tmp

EOL代表每一行的最后一個數據,SOF代表前一幀的最后一行的結束,下一幀第一行的開始。SOF為1個PLUS有效(pg044_v_axis_out.pdf 沒有描述清楚,而且有錯誤)。

wps3D10.tmpExample Horizontal Generation Register Inputs

wps3D11.tmp

設置水平輸出的相關寄存器

wps3D12.tmp

水平輸出時序圖

Example Vertical Generation Register Inputs

wps3D23.tmp

設置垂直輸出的相關寄存器

wps3D24.tmp

垂直輸出時序圖

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實驗效果

wps3D34.tmpwps3D45.tmp


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM