差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
乒乓球游戏 [2020/07/08 17:47]
zili
乒乓球游戏 [2020/07/09 09:19] (当前版本)
zili
行 1: 行 1:
 ###​乒乓球比赛 ###​乒乓球比赛
 +[[https://​www.fpga4fun.com/​PongGame.html|Pong Game]] 
 +\\ 
 +\\
 FPGA可以轻松成为视频生成器。 FPGA可以轻松成为视频生成器。
 {{ ::​ponggame.jpg |}} {{ ::​ponggame.jpg |}}
行 9: 行 11:
  
 尽管可以使用其他任何FPGA开发板,但我们都使用[[http://​www.knjn.com/​FPGA-RS232.html|Pluto]] FPGA板。 尽管可以使用其他任何FPGA开发板,但我们都使用[[http://​www.knjn.com/​FPGA-RS232.html|Pluto]] FPGA板。
 +{{ ::​pongdiagram.gif |}}
 \\ \\
  
 \\ \\
 ####​驱动VGA显示器 ####​驱动VGA显示器
-{{ ::​pongdiagram.gif |}} +一个VGA监视器需要5个信号才能显示图片:
-\\ +
-VGA监视器需要5个信号才能显示图片:+
   * R,G和B(红色,绿色和蓝色信号)。   * R,G和B(红色,绿色和蓝色信号)。
   * HS和VS(水平和垂直同步)。   * HS和VS(水平和垂直同步)。
行 27: 行 28:
   * VGA连接器(HS和VS)的引脚13和14是数字信号,因此可以直接从两个FPGA引脚驱动(或通过低阻值电阻,例如10Ω或20Ω)驱动。   * VGA连接器(HS和VS)的引脚13和14是数字信号,因此可以直接从两个FPGA引脚驱动(或通过低阻值电阻,例如10Ω或20Ω)驱动。
   * 引脚1、2和3(R,G和B)是75 are模拟信号,标称值为0.7V。对于3.3V FPGA输出,请使用三个270Ω串联电阻。电阻与监视器输入中的75Ω电阻形成分压器,因此 ​   * 引脚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种颜色。+  ​* 3.3V变为3.3 * 75 /(270 + 75)= 0.72V,非常接近0.7V。以0和1的不同组合来驱动这3个引脚时,最多可以得到8种颜色。
 接地引脚是引脚5、6、7、8和10。 接地引脚是引脚5、6、7、8和10。
 {{ ::​vgaconnector.2.gif |}} {{ ::​vgaconnector.2.gif |}}
行 38: 行 39:
 \\ \\
  
 +\\
 ####​频率发生器 ####​频率发生器
 监视器始终从上到下逐行显示图片。每条线从左到右绘制。\\ 监视器始终从上到下逐行显示图片。每条线从左到右绘制。\\
行 47: 行 49:
 对于标准640x480 VGA视频信号,脉冲频率应为: 对于标准640x480 VGA视频信号,脉冲频率应为:
 ^ 垂直频率(VS) ​      ^ 水平频率(HS) ​        ​^ ​ ^ 垂直频率(VS) ​      ^ 水平频率(HS) ​        ​^ ​
-| 60 Hz(= 60脉冲每秒) ​      |31.5 kHz(= 31500脉冲/​秒) ​       |  +| 60 Hz(= 60脉冲每秒) ​      |31.5 kHz(= 31500脉冲/​秒) ​       |   
- +要创建标准视频信号,需要处理更多细节,例如脉冲的持续时间以及HS和VS之间的关系。在[[http://​martin.hinner.info/​vga/​|此页面]]上获得一个想法。
 \\ \\
  
-要创建标准视频信号,需要处理更多细节脉冲持续时间以及HS和VS之间关系。在此页面上获得一个想法+\\ 
 +####​我们的第一个视频生成器 
 +如今VGA监视器是多同步的,因此可以适应非标准频率-不再需要精确地生成60Hz和31.5KHz(但是,如果您使用是旧(非多同步)VGA监视器,则需要生成精确的频率)\\
  
-我们的第一个视频生成器 +我们从XY计数开始\\
-如今,VGA监视器是多同步的,因此可以适应非标准频率-不再需要精确地生成60Hz31.5KHz(但是,如果您使用的是旧的(非多同步)VGA监视,则需要生成精确的频率)+
  
-让我们从X和Y计数器开始。+<code verilog>​ 
 +reg [9:0] CounterX; 
 +reg [8:0] CounterY; 
 +wire CounterXmaxed = (CounterX==767);​
  
-reg [9:0] CounterX; +always ​@(posedge ​clk) 
-reg [8:0] CounterY; +if(CounterXmaxed)
-电线 CounterXmaxed =(CounterX == 767); +
- +
-总是 ​@posedge ​CLK) +
-如果(CounterXmaxed+
   CounterX <= 0;   CounterX <= 0;
-否则 +else 
-  CounterX <= CounterX +1;+  CounterX <= CounterX + 1;
  
-总是 ​@posedge ​CLK) +always ​@(posedge ​clk) 
-如果(CounterXmaxed +if(CounterXmaxed) 
-    CounterY <= CounterY + 1; +    CounterY <= CounterY + 1;     
-CounterX计数768个值(从0到767),CounterY计数512个值(0到511)。+</​code>​
  
-现在,使用CounterX生成HS,使用CounterY生成VS。使用25MHz时钟,HS的频率为32.5KHz,VS的频率为63.5Hz。脉冲需要激活足够长的时间,以使监视器能够检测到它们。让我们为HS使用16个时钟脉冲(0.64µs),为VS使用完整的水平线长脉冲(768个时钟或30µs)。这比VGA规范所要求的要短,但仍然可以正常工作。+\\
  
-我们D触发器生成HS和VS脉冲以获得无毛刺输出)。+CounterX计数768个值(0到767),CounterY计数512个值0到511)。\\
  
-reg vga_HSvga_VS; +现在,使用CounterX生成HS,使用CounterY生成VS。使用25MHz时钟,HS的频率为32.5KHz,VS的频率为63.5Hz。脉冲需要激活足够长的时间,以使监视器能够检测到它们。让我们为HS使用16个时钟脉冲(0.64µs),为VS使用完整的水平线长脉冲(768个时钟或30µs)。这比VGA规范所要求的要短,但仍然可以正常工作。\\ 
-总是 ​@posedge ​CLK) + 
-开始 +我们从D触发器生成HS和VS脉冲(以获得无毛刺输出)。\\ 
-  vga_HS <=CounterX [94] == 0; //有效16个时钟 + 
-  vga_VS <=CounterY == 0; //有效的768个时钟 +<code verilog>​ 
-结束+reg vga_HSvga_VS; 
 +always ​@(posedge ​clk) 
 +begin 
 +  vga_HS <= (CounterX[9:4]==0)  ​// active for 16 clocks 
 +  vga_VS <= (CounterY==0)  ​// active for 768 clocks 
 +end 
 +</​code>​ 
 +\\
 VGA输出必须为负,因此我们将信号反相。 VGA输出必须为负,因此我们将信号反相。
 +\\
  
-分配 ​vga_h_sync =vga_HS; +<code verilog>​ 
-分配 ​vga_v_sync =vga_VS; +assign ​vga_h_sync = ~vga_HS; 
-最后,我们可以驱动R,G和B信号。首先,我们可以使用X和Y计数器的一些位来获得漂亮的正方形颜色图案...+assign ​vga_v_sync = ~vga_VS; 
 +</​code>​ 
 +\\ 
 +最后,我们可以驱动R,G和B信号。首先,我们可以使用X和Y计数器的一些位来获得漂亮的正方形颜色图案...\\
  
-分配 ​R = CounterY [3] | CounterX == 256+<code verilog>​ 
-分配 ​G =CounterX [5] ^ CounterX [6]CounterX == 256+assign ​R = CounterY[3] | (CounterX==256)
-分配 ​B = CounterX [4] | CounterX == 256;+assign ​G = (CounterX[5] ^ CounterX[6](CounterX==256)
 +assign ​B = CounterX[4] | (CounterX==256); 
 +</​code>​ 
 +\\
 ...然后我们在VGA监视器上得到一张照片! ...然后我们在VGA监视器上得到一张照片!
 +\\
  
-画有用的图片+\\ 
 +####画有用的图片
 最好将同步生成器重写为HDL模块,以便在外部生成R,G和B。同样,如果X和Y计数器从绘图区域开始计数,它们将更加有用。 最好将同步生成器重写为HDL模块,以便在外部生成R,G和B。同样,如果X和Y计数器从绘图区域开始计数,它们将更加有用。
-可以在这里找到新文件。+可以在[[https://​www.fpga4fun.com/​files/​hvsync_generator.zip|这里]]找到新文件。\\
  
-现在,我们可以使用它在屏幕周围绘制边框。+现在,我们可以使用它在屏幕周围绘制边框。\\ 
 +<code verilog>
  
-pong 模块(clkvga_h_syncvga_v_syncvga_Rvga_Gvga_B+module ​pong(clkvga_h_syncvga_v_syncvga_Rvga_Gvga_B)
-输入 ​clk; +input clk; 
-输出 ​vga_h_syncvga_v_syncvga_Rvga_Gvga_B;+output ​vga_h_syncvga_v_syncvga_Rvga_Gvga_B;
  
-在显示区域中连线; +wire inDisplayArea;​ 
-线 [90] CounterX; +wire [9:0] CounterX; 
-线 [80] CounterY+wire [8:0] CounterY;
  
-hvsync_generator syncgen.clkclk)、. vga_h_syncvga_h_sync)、. vga_v_syncvga_v_sync)、. inDisplayAreainDisplayArea) +hvsync_generator syncgen(.clk(clk), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync), 
-                            、. CounterXCounterX)、. CounterYCounterY));+                            ​.inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY));
  
-//在屏幕 +// Draw a border around the screen 
-导线边框=CounterX [93] == 0|| 周围绘制边框 (CounterX [93] == 79|| CounterY [83] == 0|| CounterY [83] == 59+wire border ​(CounterX[9:3]==0|| (CounterX[9:3]==79|| (CounterY[8:3]==0|| (CounterY[8:3]==59)
-线 R =边界+wire R = border
-线 G =边界; +wire G = border; 
-线B =边框;+wire B = border;
  
-reg vga_Rvga_Gvga_B; +reg vga_Rvga_Gvga_B; 
-总是 ​@posedge ​CLK) +always ​@(posedge ​clk) 
-开始 +begin 
-  vga_R <= RinDisplayArea;​ +  vga_R <= R inDisplayArea;​ 
-  vga_G <= GinDisplayArea;​ +  vga_G <= G inDisplayArea;​ 
-  vga_B <= BinDisplayArea;​ +  vga_B <= B inDisplayArea;​ 
-最终+end
  
 endmodule endmodule
-画桨 +</​code>​ 
-让我们使用鼠标在屏幕上左右移动操纵杆。+\\
  
-该解码器正交页面显示的秘密代码如下:+####​画桨 
 +让我们使用鼠标在屏幕上左右移动操纵杆\\
  
-reg [8:0] PaddlePosition;​ +[[https://​www.fpga4fun.com/​QuadratureDecoder.html|解码器正交]]页面显示的秘密。代码如下\\
-reg [2:0quadAr,quadBr;​ +
-总是 @(posedge CLK)quadAr <= {quadAr [1:0],QUADA};​ +
-总是 @(posedge CLK)quadBr <= {quadBr [10],QUADB};​+
  
-总是 ​@posedge ​CLK) +<code verilog>​ 
-如果(quadAr [2] ^ quadAr [1] ^ quadBr [2] ^ quadBr [1] +reg [8:0] PaddlePosition;​ 
-开始 +reg [2:0] quadAr, quadBr; 
-  ​,如果(quadAr [2] ^ quadBr [1] +always @(posedge clk) quadAr <= {quadAr[1:​0],​ quadA}; 
-  ​开始 +always @(posedge clk) quadBr <= {quadBr[1:​0],​ quadB}; 
-    ​,如果(〜&PaddlePosition//使确保该值不会溢出+ 
 +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;       PaddlePosition <= PaddlePosition + 1;
-  ​结束, +  ​end 
-  ​否则 +  ​else 
-  ​开始, +  ​begin 
-    ​如果(| PaddlePosition//确保该值不下溢 +    ​if(|PaddlePosition)        ​// make sure the value doesn'​t underflow 
-      PaddlePosition <= PaddlePosition-1;​ +      PaddlePosition <= PaddlePosition - 1; 
-  ​年底 +  ​end 
-结束 +end 
-现在知道“ PaddlePosition”的值,我们可以显示桨了。+</​code>​
  
-线边界=(CounterX [9:3] == 0)|| (CounterX [9:3] == 79)|| (CounterY [8:3] == 0)|| (CounterY [8:3] == 59); +现在知道“ ​PaddlePosition”的值,我们可以显示桨了。\\
-线板=(CounterX>​ = PaddlePosition ​+ 8)&&​(CounterX <= PaddlePosition + 120)&&​(CounterY [8:4] == 27);+
  
-线 R =边框CounterX [3] CounterY [3])| 桨; +<code verilog>​ 
-线 G =边界+wire border ​(CounterX[9:​3]==0) |(CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:​3]==59)
-线 B =边界| 桨; +wire paddle ​(CounterX>​=PaddlePosition+8) && (CounterX<​=PaddlePosition+120) && (CounterY[8:​4]==27);
-画球 +
-球需要在屏幕上移动,并在碰到物体(边界或球拍)时反弹。+
  
-首先,我们展示球。它是16x16像素的正方形。当CounterXCounterY到达其坐标时,我们将激活球的绘制。+wire R = border | (CounterX[3] ^ CounterY[3]) | paddle; 
 +wire G = border | paddle; 
 +wire B = border | paddle; 
 +</​code>​ 
 +\\
  
-reg [9:0] ballX; +####画球 
-reg [8:0] ballY; +球需要在屏幕上移动并在碰到物体(边界或球拍)时反弹。\\
-reg ball_inXball_inY;+
  
-是 @(posedge CLK) +首先,我们展示球。它16x16像素的正方形。当CounterX和CounterY到达其坐标时,我们将激活球的绘制。\\
-如果(ball_inX == 0)ball_inX <=(CounterX ​== ballX)ball_inY;​ 否则 ball_inX <​=!(CounterX == ballX + 16);+
  
-总是 @(posedge CLK) +<code verilog> 
-如果(ball_inY == 0)ball_inY <​=(CounterY ==巴利)否则 ball_inY <​=!(CounterY == ballY + 16);+reg [9:0] ballX; 
 +reg [8:0] ballY
 +reg ball_inX, ball_inY;
  
-钢丝球= ball_inXball_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);​
  
-取而代之的是,我们定义4个“热点”像素,在球每侧的中间一个像素。如果物体(边界或球拍)在球绘制其“热点”之一的同时重绘自身,则我们知道球的那一侧存在碰撞。+wire ball = ball_inX & ball_inY;
  
-线边界=(CounterX [9:3] == 0)|| (CounterX [9:3] == 79)|| (CounterY [8:3] == 0)|| (CounterY [8:3] == 59); +</code>
-线板=(CounterX>​ = PaddlePosition + 8)&&​(CounterX ​<= PaddlePosition + 120)&&​(CounterY [8:4] == 27); +
-线 BouncingObject =边框| 桨; //​积极的,如果边界或桨重绘本身+
  
-REG CollisionX1,CollisionX2,CollisionY1,CollisionY2;​ 如果(BouncingObject&(CounterX == ballX)&(CounterY == ballY + 8))CollisionX1 <= 1 则 +现在进行碰撞。那这个项目的困难部分。\\
-是 @(posege clk); 如果(BouncingObject&(CounterX == ballX + 16)&(CounterY == ballY + 8))CollisionX2 <= 1 则总是 @(posege clk); 总是 @(+
  
-posege clk)如果(BouncingObject&(CounterX == ballX + 8)&(CounterY == ballY))CollisionY1 <= 1; 如果(BouncingObject&(CounterX == ballX + 8)&(CounterY == ballY + 16))CollisionY2 <= 1 则 +们可以检查球相对于屏幕每个对象坐标并确定是否存在碰撞。但是随着对象数量增加,这将很快成为一场噩梦\\
-总是 @(posege clk); +
-通过从未重置碰撞触发器来简化了代码下面提供了完整代码)+
  
-现在,我们更新球的位置每个视频帧仅更新+取而代之的是,我们定义4个“热点”像素在球侧的中间一像素。如果物体(边界或球拍)在球绘制其“热点”之的同时重绘自身,则我们知道球的那一侧存在碰撞\\
  
-reg UpdateBallPosition;​ //​活性只有一次每个视频帧 
-总是 @(posedge CLK)UpdateBallPosition <​=(CounterY == 500)&(CounterX == 0); 
  
-reg ball_dirX,ball_dirY;​ +<code verilog>​ 
-总是 ​@(posege ​clk +wire border = (CounterX[9:​3]==0) || (CounterX[9:​3]==79) || (CounterY[8:​3]==0) || (CounterY[8:​3]==59);​ 
-ifUpdateBallPosition +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 
-  if(〜(CollisionX1CollisionX2))//如果两个X面都发生碰撞,则不要沿X方向移动 + 
-  ​开始 +reg CollisionX1,​ CollisionX2,​ CollisionY1,​ CollisionY2;​ 
-    ballX <= ballX +ball_dirX-1: 1+always @(posedge clk) if(BouncingObject & (CounterX==ballX ) & (CounterY==ballY+ 8)) CollisionX1<​=1;​ 
-    ​如果(CollisionX2ball_dirX <= 1; 否则 ​ifCollisionX1ball_dirX <= 0; 如果 +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;​ 
 +</​code>​ 
 + 
 +(我通过从未重置碰撞触发器来简化了上面的代码下面提供了完整的代码)。\\ 
 + 
 +现在,我们更新球的位置,但每个视频帧仅更新一次。\\ 
 + 
 + 
 +<code verilog>​ 
 + 
 +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 direction 
 +  ​begin 
 +    ballX <= ballX + (ball_dirX ​-1 1)
 +    ​if(CollisionX2ball_dirX <= 1; else if(CollisionX1ball_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 
 +</​code>​
  
-  (〜(CollisionY1&CollisionY2))//​如果两个Y侧都发生碰撞,则不要沿Y方向移动,则 
-  开始 
-    ballY <= ballY +(ball_dirY?-1:1);​ 
-    如果(CollisionY2)ball_dirY <= 1; 否则 if(CollisionY1)ball_dirY <= 0; 
-  年底 
-结束 
 最后,我们可以将所有内容整合在一起。 最后,我们可以将所有内容整合在一起。
 +\\
  
-线 R = BouncingObject | 球 (CounterX [3] ^ CounterY [3]+<code verilog>​ 
-线 G = BouncingObject | +wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3])
-线 B = BouncingObject | ;+wire G = BouncingObject | ball
 +wire B = BouncingObject | ball;
  
-reg vga_Rvga_Gvga_B; +reg vga_Rvga_Gvga_B; 
-总是 ​@posedge ​CLK) +always ​@(posedge ​clk) 
-开始 +begin 
-  vga_R <= RinDisplayArea;​ +  vga_R <= R inDisplayArea;​ 
-  vga_G <= GinDisplayArea;​ +  vga_G <= G inDisplayArea;​ 
-  vga_B <= BinDisplayArea;​ +  vga_B <= B inDisplayArea;​ 
-结束 +end 
-哇,毕竟并不难。 +</​code>​
-完整的文件是pong.zip,并与hvsync_generator.zip一起使用+
  
-也可以使用HDMI来运行乒乓游戏。+哇,其实并不难。 
 +\\ 
 +完整的文件是[[https://​www.fpga4fun.com/​files/​pong.zip|pong.zip]],并与[[https://​www.fpga4fun.com/​files/​hvsync_generator.zip|hvsync_generator.zip]]一起使用 
 +\\ 
 +也可以使用[[https://​www.fpga4fun.com/​HDMI.html|HDMI]]来运行乒乓游戏。
  
-轮到您尝试了!+##轮到您尝试了! 
 +\\
  
-链接 +\\ 
-使用FPGA和两个电阻器生成NTSC复合视频+####链接 
 +  * [[http://​excamera.com/​articles/​15/​ntsc.html|使用FPGA和两个电阻器生成NTSC复合视频]]