歡迎您光臨本站 登入註冊首頁

用FPGA設計乒乓球遊戲

admin @ 2014-03-26 , reply:0

概述

   FPGA可以很方便的產生視頻信號。   乒乓球遊戲由一個在屏幕上反覆彈跳的小球和用來擋住小球使之反彈的擋板。在這個設計中,擋板的位置由……

    FPGA可以很方便的產生視頻信號。
    乒乓球遊戲由一個在屏幕上反覆彈跳的小球和用來擋住小球使之反彈的擋板。在這個設計中,擋板的位置由用戶通過滑鼠來控制。
 
    儘管任何FPGA開發板都可以用來實現此設計,但這裡我們仍然使用Pluto板。
 

驅動VGA監視器(顯示器)
    VGA監視器需要5個信號來顯示一幅圖像:

  • R, G and B (紅 綠 藍信號)。
  • HS and VS (水平和垂直同步)。

 
    這裡的R、G、B均為較低電壓的模擬信號(範圍從0V到0.7V),而HS和VS則是數字信號。

通過FPGA的引腳產生VGA視頻信號
    FPGA的13和14引腳連接到HS和VS兩個數字信號,所以可以直接由FPGA驅動.
    1,2,3引腳(R,G,B) 則是75歐姆的模擬信號。對於3.3V的IO,我們使用三個270歐姆的串聯電阻來分壓。這樣就可以在輸出端得到3.3*75/(270+75)=0.72V的電壓,已經很接近0.7V了。通過分別設置這三個引腳的電平狀態,最多可以輸出八種顏色。5, 6, 7, 8, 10均接地。
    下面是VGA接頭(母頭)與Pluto開發板在麵包板上的連接圖。
 
    下面是VGA接頭與12腳插針的連接圖(后視圖),12腳的插針使得與麵包板的連接變得十分容易。圖中,3個270歐姆的電阻清晰可見。
  
    我們也可以使用一塊適配板來連接。

同步信號的產生
    監視器通常是從上至下,從左至右一行一行顯示圖像的。這個是規定好的,我們不能改變它。因此設計中我們需要根據規定來產生同步信號(HS和VS),使之以合適的間隔產生一個短脈衝。其中:HS用來指示某一行的開始,而VS則意味著當前顯示已經到達屏幕的底端,需要重新回到頂端開始顯示。.

對於標準的640x480 VGA 視頻信號,兩個同步信號的頻率為:

垂直同步平率 (VS) 水平同步頻率 (HS)
60 Hz (=每秒60個脈衝) 31.5 kHz (=每秒31500個脈衝)
    為了產生標準的視頻信號,還有很多細節需要處理,比如脈衝的寬度,HS和VS之間的關係等等。詳細信息請參看VGA 時序。

我們的第一個視頻產生器
    大部分現在的VGA監視器都是[多同步的],所以可以支持非標準的同步頻率,這樣便不一定必須產生精確的60Hz和31.5Hz的頻率。但是對於一些老的監視器,仍然需要產生精確的同步信號。

reg [9:0] CounterX;
reg [8:0] CounterY;
wire CounterXmaxed = (CounterX==767);

always @(posedge clk)
if(CounterXmaxed)
CounterX <= 0;
else
CounterX <= CounterX + 1;

always @(posedge clk)
if(CounterXmaxed)
CounterY <= CounterY + 1; 

    CounterX 從 0 計到 767,CounterY 從 0 計到 511。我們用CounterX 產生 HS, CounterY 產生 VS。當時鐘頻率為25MHz時, 我們可以得到32.5KHz的水平同步信號HS和63.5Hz的垂直同步信號VS。
    我們通過使用D觸發器來產生HS和VS信號,從而避免輸出毛刺信號。脈衝必須足夠寬,這樣監視器才能檢測到它們。這裡我們使 HS 的脈衝寬度為 16 個時鐘周期(0.64uS),而使 VS 的脈衝寬度為一整個水平行的時間,共768個時鐘周期(30uS)。雖然比要求的寬度稍窄,但仍然能很好的工作。

reg vga_HS, vga_VS;
always @(posedge clk)
begin
vga_HS <= (CounterX[9:4]==0); // 16個時鐘周期
vga_VS <= (CounterY==0); // 768個時鐘周期
end 

    為了得到一個負的脈衝,我們將信號翻轉得到輸出:

assign vga_h_sync = ~vga_HS;
assign vga_v_sync = ~vga_VS; 

    最後我們再來驅動 R, G , B 信號。首先,為了簡單起見,我們使用 X 和 Y 計數器的某幾位來得到一個很好的直方彩條圖。

assign R = CounterY[3] | (CounterX==256);
assign G = (CounterX[5] ^ CounterX[6]) | (CounterX==256);
assign B = CounterX[4] | (CounterX==256); 

畫出有用的圖形
    同步信號的產生最好用一個單獨Verilog HDL的模塊重寫,而把 R, G , B 信號的產生放到模塊外部。同樣,如果X 和 Y計數器從畫圖區域開始計數的畫,對於R,G,B信號的產生也是十分有用的。
這裡是新的設計文件

現在,我們可以用它來畫一個圍繞監視器屏幕的邊框

module pong(clk, vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B);
input clk;
output vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B;

wire inDisplayArea;
wire [9:0] CounterX;
wire [8:0] CounterY;

hvsync_generator syncgen(.clk(clk), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync),
.inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY));

// 畫出邊框
wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59);
wire R = border;
wire G = border;
wire B = border;

reg vga_R, vga_G, vga_B;
always @(posedge clk)
begin
vga_R <= R & inDisplayArea;
vga_G <= G & inDisplayArea;
vga_B <= B & inDisplayArea;
end

endmodule 

畫擋板
我們使用滑鼠來控制擋板左右移動。

正交解碼 為我們揭示了其中的奧秘,下面是其代碼:

reg [8:0] PaddlePosition;
reg [2:0] quadAr, quadBr;
always @(posedge clk) quadAr <= {quadAr[1:0], quadA};
always @(posedge clk) quadBr <= {quadBr[1:0], quadB};

always @(posedge clk)
if(quadAr[2] ^ quadAr[1] ^ quadBr[2] ^ quadBr[1])
begin
if(quadAr[2] ^ quadBr[1])
begin
if(~&PaddlePosition) // make sure the value doesn't overflow
PaddlePosition <= PaddlePosition + 1;
end
else
begin
if(|PaddlePosition) // make sure the value doesn't underflow
PaddlePosition <= PaddlePosition - 1;
end
end 

已知了擋板的位置,我們就可以在屏幕上畫出擋板:

wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59);
wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27);

wire R = border | (CounterX[3] ^ CounterY[3]) | paddle;
wire G = border | paddle;
wire B = border | paddle; 

畫小球
    小球需要在屏幕上移動,當碰到物體(邊框或擋板)的時候又會被彈回。
    首先,我們讓我們來畫出小球。用一個16乘16的小方塊來表示它,當CounterX和CounterY的值在小球的坐標內時,我們開始在屏幕上畫小球。

reg [9:0] ballX;
reg [8:0] ballY;
reg ball_inX, ball_inY;

always @(posedge clk)
if(ball_inX==0) ball_inX <= (CounterX==ballX) & ball_inY; else ball_inX <= !(CounterX==ballX+16);

always @(posedge clk)
if(ball_inY==0) ball_inY <= (CounterY==ballY); else ball_inY <= !(CounterY==ballY+16);

wire ball = ball_inX & ball_inY; 

    現在,我們遇到了設計中最難解決的問題--如何判斷碰撞的發生。
    我們可以通過檢測小球的坐標和屏幕上任何一個物體(擋板和邊框)的位置來判斷是否有碰撞發生,但是當物體的數量增加時,情況將變得十分複雜。因此我們引入一個新的模型,該模型由4個關鍵點構成,分別分佈在小球四個邊框的中點。如果在重畫物體(邊框或擋板)的時候畫到了這4個點中的任何一個點,就認為有碰撞發生,並且碰撞發生在所畫到的點那一側。

wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59);
wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27);
wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself

reg CollisionX1, CollisionX2, CollisionY1, CollisionY2;
always @(posedge clk) if(BouncingObject & (CounterX==ballX ) & (CounterY==ballY+ 8)) CollisionX1<=1;
always @(posedge clk) if(BouncingObject & (CounterX==ballX+16) & (CounterY==ballY+ 8)) CollisionX2<=1;
always @(posedge clk) if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY )) CollisionY1<=1;
always @(posedge clk) if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY+16)) CollisionY2<=1; 

(上面是簡化的代碼,其中沒有複位“碰撞標誌”的操作。 完整的代碼將在後面給出)。
現在我們來處理小球位置的更新,這裡設計更新頻率僅為每一幀圖像更新一次。

reg UpdateBallPosition;
always @(posedge clk) UpdateBallPosition <= (CounterY==500) & (CounterX==0); // 每幀有效一次
reg ball_dirX, ball_dirY;
always @(posedge clk)
if(UpdateBallPosition)
begin
if(~(CollisionX1 & CollisionX2)) //如果X方向上均發生碰撞,不改變X上的運動方向
begin
ballX <= ballX + (ball_dirX ? -1 : 1);
if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0;
end

if(~(CollisionY1 & CollisionY2)) // 如果Y方向上均發生碰撞,不改變Y上的運動方向
begin
ballY <= ballY + (ball_dirY ? -1 : 1);
if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0;
end
end 

這樣,我們就可以將他們全部在屏幕上畫出來。

wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3]);
wire G = BouncingObject | ball;
wire B = BouncingObject | ball;

reg vga_R, vga_G, vga_B;
always @(posedge clk)
begin
vga_R <= R & inDisplayArea;
vga_G <= G & inDisplayArea;
vga_B <= B & inDisplayArea;
end 

    通過一步一步的設計發現,其實這個乒乓球的遊戲並沒有想象中那麼難。
    這裡是完整的代碼 pong.zip, hvsync_generator.zip
    這裡的設計僅僅是功能上的實現,並不是那麼激動人心,當加入更多的小球、物體以及記分的功能后,就變得很有意思了。不過,那將是你所要做的了。


[admin via 研發互助社區 ] 用FPGA設計乒乓球遊戲已經有9210次圍觀

http://cocdig.com/docs/show-post-43109.html