新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 乒乓球比賽

        乒乓球比賽

        作者: 時間:2023-12-22 來源:電子森林 收藏

        FPGA可以輕松成為視頻生成器。

        本文引用地址:http://www.104case.com/article/202312/454181.htm

        乒乓球游戲包括在屏幕上彈跳的球。槳(此處由鼠標控制)使用戶能夠使球彈回。

        盡管可以使用其他任何FPGA開發板,但我們都使用Pluto FPGA板。


        驅動VGA顯示器

        一個VGA監視器需要5個信號才能顯示圖片:

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

        R,G和B是模擬信號,而HS和VS是數字信號。

        通過FPGA引腳創建

        以下是驅動VGA接口的方法:

        • VGA連接器(HS和VS)的引腳13和14是數字信號,因此可以直接從兩個FPGA引腳驅動(或通過低阻值電阻,例如10Ω或20Ω)驅動。
        • 引腳1、2和3(R,G和B)是75 are模擬信號,標稱值為0.7V。對于3.3V FPGA輸出,請使用三個270Ω串聯電阻。電阻與監視器輸入中的75Ω電阻形成分壓器,因此
        • 3.3V變為3.3 * 75 /(270 + 75)= 0.72V,非常接近0.7V。以0和1的不同組合來驅動這3個引腳時,最多可以得到8種顏色。

        接地引腳是引腳5、6、7、8和10。

        這是連接到面包板上Pluto的母VGA連接器的視圖。

        VGA母頭連接器至12針接頭連接器的后視圖。12針插頭可輕松連接到面包板。三個270Ω串聯電阻清晰可見。我們也可以使用轉接板。

        頻率發生器

        監視器始終從上到下逐行顯示圖片。每條線從左到右繪制。

        這是硬編碼的,您無法更改。

        但是,您可以通過以固定間隔在HS和VS上發送短脈沖來指定何時開始繪制圖形。HS畫了一條新線開始繪制;而VS告訴您已經到達底部(使監視器回到頂部)。

        對于標準640×480 ,脈沖頻率應為:

        垂直頻率(VS)水平頻率(HS)
        60 Hz(= 60脈沖每秒)31.5 kHz(= 31500脈沖/秒)

        要創建標準視頻信號,需要處理更多細節,例如脈沖的持續時間以及HS和VS之間的關系。在此頁面上獲得一個想法。

        我們的第一個視頻生成器

        如今,VGA監視器是多同步的,因此可以適應非標準頻率-不再需要精確地生成60Hz和31.5KHz(但是,如果您使用的是舊的(非多同步)VGA監視器,則需要生成精確的頻率)。

        讓我們從X和Y計數器開始。

        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計數768個值(從0到767),CounterY計數512個值(0到511)。

        現在,使用CounterX生成HS,使用CounterY生成VS。使用25MHz時鐘,HS的頻率為32.5KHz,VS的頻率為63.5Hz。脈沖需要激活足夠長的時間,以使監視器能夠檢測到它們。讓我們為HS使用16個時鐘脈沖(0.64μs),為VS使用完整的水平線長脈沖(768個時鐘或30μs)。這比VGA規范所要求的要短,但仍然可以正常工作。

        我們從D觸發器生成HS和VS脈沖(以獲得無毛刺輸出)。

        reg vga_HS, vga_VS;always @(posedge clk)begin
          vga_HS <= (CounterX[9:4]==0);   // active for 16 clocks
          vga_VS <= (CounterY==0);   // active for 768 clocksend

        VGA輸出必須為負,因此我們將信號反相。

        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);

        …然后我們在VGA監視器上得到一張照片!

        畫有用的圖片

        最好將同步生成器重寫為HDL模塊,以便在外部生成R,G和B。同樣,如果X和Y計數器從繪圖區域開始計數,它們將更加有用。 可以在這里找到新文件。

        現在,我們可以使用它在屏幕周圍繪制邊框。

        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)); 
                                    // Draw a border around the screen
        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;
          endend

        現在知道“ PaddlePosition”的值,我們可以顯示槳了。

        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個“熱點”像素,在球每側的中間一個像素。如果物體(邊界或球拍)在球繪制其“熱點”之一的同時重繪自身,則我們知道球的那一側存在碰撞。

        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;       // active only once for every video frame
        always @(posedge clk) UpdateBallPosition <= (CounterY==500) & (CounterX==0); 
        reg ball_dirX, ball_dirY;
        always @(posedge clk)if(UpdateBallPosition)begin
          if(~(CollisionX1 & CollisionX2))        // if collision on both X-sides, don't move in the X direction
          begin
            ballX <= ballX + (ball_dirX ? -1 : 1);
            if(CollisionX2) ball_dirX <= 1; 
            else if(CollisionX1) ball_dirX <= 0;
          end   if(~(CollisionY1 & CollisionY2))        // if collision on both Y-sides, don't move in the Y direction
          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

        哇,其實并不難。



        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 昌吉市| 阿拉善左旗| 教育| 林甸县| 房山区| 吉首市| 台安县| 黄陵县| 宜兰市| 巴彦淖尔市| 成都市| 工布江达县| 呼伦贝尔市| 南部县| 兴文县| 南康市| 龙山县| 莱芜市| 古交市| 卓尼县| 武隆县| 辉县市| 唐山市| 施秉县| 鹰潭市| 阿瓦提县| 八宿县| 连云港市| 左权县| 济宁市| 玛曲县| 岳西县| 巴南区| 星座| 耒阳市| 嘉义市| 山东| 阿拉善右旗| 新郑市| 绥阳县| 汶川县|