差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
乒乓球游戏 [2020/07/09 09:01] zili |
乒乓球游戏 [2020/07/09 09:19] (当前版本) zili |
||
---|---|---|---|
行 1: | 行 1: | ||
###乒乓球比赛 | ###乒乓球比赛 | ||
[[https://www.fpga4fun.com/PongGame.html|Pong Game]] | [[https://www.fpga4fun.com/PongGame.html|Pong Game]] | ||
+ | \\ | ||
+ | \\ | ||
FPGA可以轻松成为视频生成器。 | FPGA可以轻松成为视频生成器。 | ||
{{ ::ponggame.jpg |}} | {{ ::ponggame.jpg |}} | ||
行 26: | 行 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 |}} | ||
行 37: | 行 39: | ||
\\ | \\ | ||
+ | \\ | ||
####频率发生器 | ####频率发生器 | ||
监视器始终从上到下逐行显示图片。每条线从左到右绘制。\\ | 监视器始终从上到下逐行显示图片。每条线从左到右绘制。\\ | ||
行 46: | 行 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之间的关系。在[[http://martin.hinner.info/vga/|此页面]]上获得一个想法。 | ||
\\ | \\ | ||
- | |||
####我们的第一个视频生成器 | ####我们的第一个视频生成器 | ||
- | 如今,VGA监视器是多同步的,因此可以适应非标准频率-不再需要精确地生成60Hz和31.5KHz(但是,如果您使用的是旧的(非多同步)VGA监视器,则需要生成精确的频率)。 | + | 如今,VGA监视器是多同步的,因此可以适应非标准频率-不再需要精确地生成60Hz和31.5KHz(但是,如果您使用的是旧的(非多同步)VGA监视器,则需要生成精确的频率)。\\ |
- | \\ | + | |
- | 让我们从X和Y计数器开始。 | + | |
- | --- | + | 让我们从X和Y计数器开始。\\ |
<code verilog> | <code verilog> | ||
行 73: | 行 72: | ||
always @(posedge clk) | always @(posedge clk) | ||
if(CounterXmaxed) | if(CounterXmaxed) | ||
- | CounterY <= CounterY + 1; | + | CounterY <= CounterY + 1; |
- | + | ||
</code> | </code> | ||
\\ | \\ | ||
- | CounterX计数768个值(从0到767),CounterY计数512个值(0到511)。 | + | 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脉冲(以获得无毛刺输出)。 | + | |
- | \\ | + | |
- | <code verilog> | + | 现在,使用CounterX生成HS,使用CounterY生成VS。使用25MHz时钟,HS的频率为32.5KHz,VS的频率为63.5Hz。脉冲需要激活足够长的时间,以使监视器能够检测到它们。让我们为HS使用16个时钟脉冲(0.64µs),为VS使用完整的水平线长脉冲(768个时钟或30µs)。这比VGA规范所要求的要短,但仍然可以正常工作。\\ |
- | reg vga_HS,vga_VS; | + | 我们从D触发器生成HS和VS脉冲(以获得无毛刺输出)。\\ |
- | 总是 @(posedge CLK) | + | |
- | 开始 | + | |
- | vga_HS <=(CounterX [9:4] == 0); //有效16个时钟 | + | |
- | vga_VS <=(CounterY == 0); //有效的768个时钟 | + | |
- | 结束 | + | |
+ | <code verilog> | ||
+ | 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 clocks | ||
+ | end | ||
</code> | </code> | ||
\\ | \\ | ||
行 101: | 行 96: | ||
<code verilog> | <code verilog> | ||
- | + | assign vga_h_sync = ~vga_HS; | |
- | 分配 vga_h_sync =〜vga_HS; | + | assign vga_v_sync = ~vga_VS; |
- | 分配 vga_v_sync =〜vga_VS; | + | |
</code> | </code> | ||
\\ | \\ | ||
- | 最后,我们可以驱动R,G和B信号。首先,我们可以使用X和Y计数器的一些位来获得漂亮的正方形颜色图案... | + | 最后,我们可以驱动R,G和B信号。首先,我们可以使用X和Y计数器的一些位来获得漂亮的正方形颜色图案...\\ |
- | \\ | + | |
<code verilog> | <code verilog> | ||
- | + | assign R = CounterY[3] | (CounterX==256); | |
- | 分配 R = CounterY [3] | (CounterX == 256); | + | assign G = (CounterX[5] ^ CounterX[6]) | (CounterX==256); |
- | 分配 G =(CounterX [5] ^ CounterX [6])| (CounterX == 256); | + | assign B = CounterX[4] | (CounterX==256); |
- | 分配 B = CounterX [4] | (CounterX == 256); | + | |
</code> | </code> | ||
\\ | \\ | ||
行 124: | 行 114: | ||
####画有用的图片 | ####画有用的图片 | ||
最好将同步生成器重写为HDL模块,以便在外部生成R,G和B。同样,如果X和Y计数器从绘图区域开始计数,它们将更加有用。 | 最好将同步生成器重写为HDL模块,以便在外部生成R,G和B。同样,如果X和Y计数器从绘图区域开始计数,它们将更加有用。 | ||
- | 可以在这里找到新文件。 | + | 可以在[[https://www.fpga4fun.com/files/hvsync_generator.zip|这里]]找到新文件。\\ |
- | \\ | + | |
- | 现在,我们可以使用它在屏幕周围绘制边框。 | + | |
- | \\ | + | |
+ | 现在,我们可以使用它在屏幕周围绘制边框。\\ | ||
<code verilog> | <code verilog> | ||
- | pong 模块(clk,vga_h_sync,vga_v_sync,vga_R,vga_G,vga_B); | + | module pong(clk, vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B); |
- | 输入 clk; | + | input clk; |
- | 输出 vga_h_sync,vga_v_sync,vga_R,vga_G,vga_B; | + | output vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B; |
- | 在显示区域中连线; | + | wire inDisplayArea; |
- | 线 [9:0] CounterX; | + | wire [9:0] CounterX; |
- | 线 [8:0] CounterY; | + | wire [8:0] CounterY; |
- | hvsync_generator syncgen(.clk(clk)、. vga_h_sync(vga_h_sync)、. vga_v_sync(vga_v_sync)、. inDisplayArea(inDisplayArea) | + | hvsync_generator syncgen(.clk(clk), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync), |
- | 、. CounterX(CounterX)、. CounterY(CounterY)); | + | .inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY)); |
- | //在屏幕 | + | // Draw a border around the screen |
- | 导线边框=(CounterX [9:3] == 0)|| 周围绘制边框 (CounterX [9:3] == 79)|| (CounterY [8:3] == 0)|| (CounterY [8:3] == 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_R,vga_G,vga_B; | + | reg vga_R, vga_G, vga_B; |
- | 总是 @(posedge CLK) | + | always @(posedge clk) |
- | 开始 | + | begin |
- | vga_R <= R&inDisplayArea; | + | vga_R <= R & inDisplayArea; |
- | vga_G <= G&inDisplayArea; | + | vga_G <= G & inDisplayArea; |
- | vga_B <= B&inDisplayArea; | + | vga_B <= B & inDisplayArea; |
- | 最终 | + | end |
endmodule | endmodule | ||
- | |||
</code> | </code> | ||
\\ | \\ | ||
- | \\ | + | ####画桨 |
+ | 让我们使用鼠标在屏幕上左右移动操纵杆。\\ | ||
- | + | 该[[https://www.fpga4fun.com/QuadratureDecoder.html|解码器正交]]页面显示的秘密。代码如下:\\ | |
- | ####画桨 | + | |
- | 让我们使用鼠标在屏幕上左右移动操纵杆。 | + | |
- | \\ | + | |
- | 该[[https://www.fpga4fun.com/QuadratureDecoder.html|解码器正交]]页面显示的秘密。代码如下: | + | |
- | \\ | + | |
<code verilog> | <code verilog> | ||
+ | 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}; | ||
- | reg [8:0] PaddlePosition; | + | always @(posedge clk) |
- | reg [2:0] quadAr,quadBr; | + | if(quadAr[2] ^ quadAr[1] ^ quadBr[2] ^ quadBr[1]) |
- | 总是 @(posedge CLK)quadAr <= {quadAr [1:0],QUADA}; | + | begin |
- | 总是 @(posedge CLK)quadBr <= {quadBr [1:0],QUADB}; | + | if(quadAr[2] ^ quadBr[1]) |
- | + | begin | |
- | 总是 @(posedge CLK) | + | if(~&PaddlePosition) // make sure the value doesn't overflow |
- | 如果(quadAr [2] ^ quadAr [1] ^ quadBr [2] ^ quadBr [1]) | + | |
- | 开始 | + | |
- | ,如果(quadAr [2] ^ quadBr [1]) | + | |
- | 开始 | + | |
- | ,如果(〜&PaddlePosition)//使确保该值不会溢出 | + | |
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 |
</code> | </code> | ||
- | 现在知道“ PaddlePosition”的值,我们可以显示桨了。 | + | 现在知道“ PaddlePosition”的值,我们可以显示桨了。\\ |
- | \\ | + | |
<code verilog> | <code verilog> | ||
- | 线边界=(CounterX [9:3] == 0)|| (CounterX [9:3] == 79)|| (CounterY [8:3] == 0)|| (CounterY [8:3] == 59); | + | wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59); |
- | 线板=(CounterX> = PaddlePosition + 8)&&(CounterX <= PaddlePosition + 120)&&(CounterY [8:4] == 27); | + | wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27); |
- | + | ||
- | 线 R =边框| (CounterX [3] ^ CounterY [3])| 桨; | + | |
- | 线 G =边界| 桨; | + | |
- | 线 B =边界| 桨; | + | |
+ | wire R = border | (CounterX[3] ^ CounterY[3]) | paddle; | ||
+ | wire G = border | paddle; | ||
+ | wire B = border | paddle; | ||
</code> | </code> | ||
+ | \\ | ||
+ | ####画球 | ||
+ | 球需要在屏幕上移动,并在碰到物体(边界或球拍)时反弹。\\ | ||
- | ####画球 | + | 首先,我们展示球。它是16x16像素的正方形。当CounterX和CounterY到达其坐标时,我们将激活球的绘制。\\ |
- | 球需要在屏幕上移动,并在碰到物体(边界或球拍)时反弹。 | + | |
- | \\ | + | |
- | 首先,我们展示球。它是16x16像素的正方形。当CounterX和CounterY到达其坐标时,我们将激活球的绘制。 | + | |
- | \\ | + | |
<code verilog> | <code verilog> | ||
+ | reg [9:0] ballX; | ||
+ | reg [8:0] ballY; | ||
+ | reg ball_inX, ball_inY; | ||
- | reg [9:0] ballX; | + | always @(posedge clk) |
- | reg [8:0] ballY; | + | if(ball_inX==0) ball_inX <= (CounterX==ballX) & ball_inY; else ball_inX <= !(CounterX==ballX+16); |
- | reg ball_inX,ball_inY; | + | |
- | 总是 @(posedge CLK) | + | always @(posedge clk) |
- | 如果(ball_inX == 0)ball_inX <=(CounterX == ballX)ball_inY; 否则 ball_inX <=!(CounterX == ballX + 16); | + | if(ball_inY==0) ball_inY <= (CounterY==ballY); else ball_inY <= !(CounterY==ballY+16); |
- | 总是 @(posedge CLK) | + | wire ball = ball_inX & ball_inY; |
- | 如果(ball_inY == 0)ball_inY <=(CounterY ==巴利); 否则 ball_inY <=!(CounterY == ballY + 16); | + | |
- | + | ||
- | 钢丝球= ball_inX&ball_inY; | + | |
</code> | </code> | ||
- | 现在进行碰撞。那是这个项目的困难部分。 | + | 现在进行碰撞。那是这个项目的困难部分。\\ |
- | \\ | + | |
- | 我们可以检查球相对于屏幕上每个对象的坐标,并确定是否存在碰撞。但是随着对象数量的增加,这将很快成为一场噩梦。 | + | |
- | \\ | + | |
- | 取而代之的是,我们定义4个“热点”像素,在球每侧的中间一个像素。如果物体(边界或球拍)在球绘制其“热点”之一的同时重绘自身,则我们知道球的那一侧存在碰撞。 | + | |
- | \\ | + | |
+ | 我们可以检查球相对于屏幕上每个对象的坐标,并确定是否存在碰撞。但是随着对象数量的增加,这将很快成为一场噩梦。\\ | ||
- | <code verilog> | + | 取而代之的是,我们定义4个“热点”像素,在球每侧的中间一个像素。如果物体(边界或球拍)在球绘制其“热点”之一的同时重绘自身,则我们知道球的那一侧存在碰撞。\\ |
- | 线边界=(CounterX [9:3] == 0)|| (CounterX [9:3] == 79)|| (CounterY [8:3] == 0)|| (CounterY [8:3] == 59); | ||
- | 线板=(CounterX> = PaddlePosition + 8)&&(CounterX <= PaddlePosition + 120)&&(CounterY [8:4] == 27); | ||
- | 线 BouncingObject =边框| 桨; //积极的,如果边界或桨重绘本身 | ||
- | REG CollisionX1,CollisionX2,CollisionY1,CollisionY2; 如果(BouncingObject&(CounterX == ballX)&(CounterY == ballY + 8))CollisionX1 <= 1 则 | + | <code verilog> |
- | 总是 @(posege clk); 如果(BouncingObject&(CounterX == ballX + 16)&(CounterY == ballY + 8))CollisionX2 <= 1 则总是 @(posege clk); 总是 @( | + | 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); | |
- | posege clk)如果(BouncingObject&(CounterX == ballX + 8)&(CounterY == ballY))CollisionY1 <= 1; 如果(BouncingObject&(CounterX == ballX + 8)&(CounterY == ballY + 16))CollisionY2 <= 1 则 | + | wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself |
- | 总是 @(posege clk); | + | |
+ | 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; | ||
</code> | </code> | ||
+ | (我通过从未重置碰撞触发器来简化了上面的代码,下面提供了完整的代码)。\\ | ||
+ | |||
+ | 现在,我们更新球的位置,但每个视频帧仅更新一次。\\ | ||
- | (我通过从未重置碰撞触发器来简化了上面的代码,下面提供了完整的代码)。 | ||
- | \\ | ||
- | 现在,我们更新球的位置,但每个视频帧仅更新一次。 | ||
- | \\ | ||
<code verilog> | <code verilog> | ||
- | reg UpdateBallPosition; //活性只有一次每个视频帧 | + | reg UpdateBallPosition; // active only once for every video frame |
- | 总是 @(posedge CLK)UpdateBallPosition <=(CounterY == 500)&(CounterX == 0); | + | always @(posedge clk) UpdateBallPosition <= (CounterY==500) & (CounterX==0); |
- | reg ball_dirX,ball_dirY; | + | reg ball_dirX, ball_dirY; |
- | 总是 @(posege clk) | + | always @(posedge clk) |
- | if(UpdateBallPosition) | + | if(UpdateBallPosition) |
- | 开始 | + | begin |
- | if(〜(CollisionX1&CollisionX2))//如果两个X面都发生碰撞,则不要沿X方向移动 | + | if(~(CollisionX1 & CollisionX2)) // if collision on both X-sides, don't move in the X direction |
- | 开始 | + | begin |
- | ballX <= ballX +(ball_dirX?-1: 1); | + | ballX <= ballX + (ball_dirX ? -1 : 1); |
- | 如果(CollisionX2)ball_dirX <= 1; 否则 if(CollisionX1)ball_dirX <= 0; 如果 | + | if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0; |
- | 结束 | + | end |
- | + | ||
- | (〜(CollisionY1&CollisionY2))//如果两个Y侧都发生碰撞,则不要沿Y方向移动,则 | + | |
- | 开始 | + | |
- | ballY <= ballY +(ball_dirY?-1:1); | + | |
- | 如果(CollisionY2)ball_dirY <= 1; 否则 if(CollisionY1)ball_dirY <= 0; | + | |
- | 年底 | + | |
- | 结束 | + | |
+ | 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> | </code> | ||
行 286: | 行 258: | ||
<code verilog> | <code verilog> | ||
+ | wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3]); | ||
+ | wire G = BouncingObject | ball; | ||
+ | wire B = BouncingObject | ball; | ||
- | 线 R = BouncingObject | 球 (CounterX [3] ^ CounterY [3]); | + | reg vga_R, vga_G, vga_B; |
- | 线 G = BouncingObject | 球; | + | always @(posedge clk) |
- | 线 B = BouncingObject | 球; | + | begin |
- | + | vga_R <= R & inDisplayArea; | |
- | reg vga_R,vga_G,vga_B; | + | vga_G <= G & inDisplayArea; |
- | 总是 @(posedge CLK) | + | vga_B <= B & inDisplayArea; |
- | 开始 | + | end |
- | vga_R <= R&inDisplayArea; | + | |
- | vga_G <= G&inDisplayArea; | + | |
- | vga_B <= B&inDisplayArea; | + | |
- | 结束 | + | |
</code> | </code> | ||
- | 哇,毕竟并不难。 | + | 哇,其实并不难。 |
\\ | \\ | ||
完整的文件是[[https://www.fpga4fun.com/files/pong.zip|pong.zip]],并与[[https://www.fpga4fun.com/files/hvsync_generator.zip|hvsync_generator.zip]]一起使用 | 完整的文件是[[https://www.fpga4fun.com/files/pong.zip|pong.zip]],并与[[https://www.fpga4fun.com/files/hvsync_generator.zip|hvsync_generator.zip]]一起使用 | ||
行 307: | 行 277: | ||
也可以使用[[https://www.fpga4fun.com/HDMI.html|HDMI]]来运行乒乓游戏。 | 也可以使用[[https://www.fpga4fun.com/HDMI.html|HDMI]]来运行乒乓游戏。 | ||
- | ###轮到您尝试了! | + | ##轮到您尝试了! |
+ | \\ | ||
+ | \\ | ||
####链接 | ####链接 | ||
* [[http://excamera.com/articles/15/ntsc.html|使用FPGA和两个电阻器生成NTSC复合视频]] | * [[http://excamera.com/articles/15/ntsc.html|使用FPGA和两个电阻器生成NTSC复合视频]] | ||