FPGA:HDMI接口
HDMI 是一種數(shù)字視頻接口,因此很容易從現(xiàn)代 FPGA 驅(qū)動。讓我們看看它是如何工作的。
本文引用地址:http://cafeforensic.com/article/202401/454692.htm連接器
標準 HDMI 連接器有 19 個引腳。 在 19 個引腳中,有 8 個特別值得關(guān)注,因為它們形成 4 個 TMDS 差分對來傳輸實際的高速視頻信息。
TMDS 時鐘+ 和時鐘-
TMDS data0+ 和 data0-
TMDS data1+ 和 data1-
TMDS data2+ 和 data2-
我們從FPGA到HDMI連接器的連接再簡單不過了......我們使用 8 個 FPGA 引腳,配置為 4 個差分 TMDS 輸出。
視頻信號
讓我們創(chuàng)建一個 640x480 RGB 24bpp @ 60Hz 的視頻信號。 這是每幀 307200 像素,由于每個像素有 24 位(紅色、綠色和藍色為 8 位),在 60Hz 時,HDMI 鏈路傳輸 0.44Gbps 的“有用”數(shù)據(jù)。
但視頻信號通常也有一個“屏幕外”區(qū)域,HDMI接收器(電視或顯示器)使用它進行一些內(nèi)務(wù)處理。 我們的 640x480 幀實際上是作為 800x525 幀發(fā)送的。
考慮到這一點,我們需要一個 24.5MHz 的像素時鐘來實現(xiàn)每秒 60 幀,但 HDMI 指定了 25MHz 的最小像素時鐘,所以我們就這樣使用(這讓我們獲得了 61Hz 的幀速率)。
TMDS信號
FPGA 有 4 個 TMDS 差分對可供驅(qū)動。
首先,TMDS時鐘只是像素時鐘,因此它以25MHz運行。 其他 3 對傳輸紅色、綠色和藍色 8 位信號,因此我們得到類似的東西。
事實上,事情只是稍微復(fù)雜一些。 HDMI 要求我們對數(shù)據(jù)進行加擾,并在每個顏色通道上添加 2 位,因此我們有 10 位而不是 8 位,鏈路最終每像素傳輸 30 位。 HDMI 接收器需要加擾和額外位才能正確同步和采集每個通道(請務(wù)必查看 DVI 和 HDMI 規(guī)格以獲取更多詳細信息)。
源代碼
首先是視頻生成器。 我們使用幾個穿過 800x525 像素區(qū)域的計數(shù)器......
reg [9:0] CounterX; // counts from 0 to 799
always @(posedge pixclk) CounterX <= (CounterX==799) ? 0 : CounterX+1;
reg [9:0] CounterY; // counts from 0 to 524
always @(posedge pixclk) if(CounterX==799) CounterY <= (CounterY==524) ? 0 : CounterY+1;
并創(chuàng)建 H-Sync 和 V-Sync 信號...
wire hSync = (CounterX>=656) && (CounterX<752);
wire vSync = (CounterY>=490) && (CounterY<492);
wire DrawArea = (CounterX<640) && (CounterY<480);
并生成一些紅色、綠色和藍色信號(每個 8 位)......
wire [7:0] red = {CounterX[5:0] & {6{CounterY[4:3]==~CounterX[4:3]}}, 2'b00};
wire [7:0] green = CounterX[7:0] & {8{CounterY[6]}};
wire [7:0] blue = CounterY[7:0];
通過三個“TMDS_encoder”實例分別擴展到 10 位......
wire [9:0] TMDS_red, TMDS_green, TMDS_blue;
TMDS_encoder encode_R(.clk(pixclk), .VD(red ), .TMDS(TMDS_red) , .CD(2'b00) , .VDE(DrawArea));
TMDS_encoder encode_G(.clk(pixclk), .VD(green), .TMDS(TMDS_green), .CD(2'b00) , .VDE(DrawArea));
TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .TMDS(TMDS_blue) , .CD({vSync,hSync}), .VDE(DrawArea));
現(xiàn)在,我們?yōu)槊總€像素時鐘周期發(fā)送三個 10 位值。 我們將 25MHz 時鐘乘以 10 以生成 250MHz 時鐘......
wire clk_TMDS, DCM_TMDS_CLKFX;
DCM_SP #(.CLKFX_MULTIPLY(10)) DCM_TMDS_inst(.CLKIN(pixclk), .CLKFX(DCM_TMDS_CLKFX), .RST(1'b0));
BUFG BUFG_TMDSp(.I(DCM_TMDS_CLKFX), .O(clk_TMDS)); // 250 MHz
并使用三個時鐘頻率為250MHz的移位寄存器...
reg [3:0] TMDS_mod10; // modulus 10 counter
always @(posedge clk_TMDS) TMDS_mod10 <= (TMDS_mod10==9) ? 0 : TMDS_mod10+1;
reg TMDS_shift_load;
always @(posedge clk_TMDS) TMDS_shift_load <= (TMDS_mod10==9);
reg [9:0] TMDS_shift_red, TMDS_shift_green, TMDS_shift_blue;
always @(posedge clk_TMDS)begin
TMDS_shift_red <= TMDS_shift_load ? TMDS_red : TMDS_shift_red [9:1];
TMDS_shift_green <= TMDS_shift_load ? TMDS_green : TMDS_shift_green[9:1];
TMDS_shift_blue <= TMDS_shift_load ? TMDS_blue : TMDS_shift_blue [9:1];
end
將TMDS數(shù)據(jù)發(fā)送到FPGA外部。
OBUFDS OBUFDS_red (.I(TMDS_shift_red [0]), .O(TMDSp[2]), .OB(TMDSn[2]));
OBUFDS OBUFDS_green(.I(TMDS_shift_green[0]), .O(TMDSp[1]), .OB(TMDSn[1]));
OBUFDS OBUFDS_blue (.I(TMDS_shift_blue [0]), .O(TMDSp[0]), .OB(TMDSn[0]));
OBUFDS OBUFDS_clock(.I(pixclk), .O(TMDSp_clock), .OB(TMDSn_clock));
更高的分辨率
對于 640x480,我們使用了 250MHz 時鐘串行器,但為了獲得更高的分辨率,我們需要更高的頻率,這很快就會超過 FPGA 的能力。 解決方法是使用一些特殊的FPGA IO功能,如DDR輸出和IO串行器。
在較高頻率下,另一個問題是如何可靠地將數(shù)據(jù)從像素時鐘域傳輸?shù)酱衅饔颉?一種可能的技術(shù)是使用淺層FIFO。 查看 Xilinx XAPP460(用于 Spartan-3A)和 XAPP495(用于 Spartan-6)應(yīng)用筆記,了解一些想法。
截圖
以下是使用數(shù)碼相機拍攝的幾張照片,該相機拍攝了由Pluto-IIx HDMI驅(qū)動的LCD顯示器。
我們有乒乓球比賽......
為了好玩,經(jīng)典的 PacMan 街機游戲......曾經(jīng)可以從 fpgaarcade.com 獲得,但該網(wǎng)站最近進行了重新設(shè)計。 您仍然可以使用回溯機獲取原始源代碼。
這是我們測試板的圖片(Pluto-IIx HDMI加載了一個可選的HDMI適配器 - 所以我們實際上有兩個HDMI輸出可以玩......
評論