您好,欢迎进入山西润盛进出口有限公司!

咨询服务热线

15383419322

乒乓球比赛

发布时间:2023-12-22 11:17人气:

FPGA可以轻松成为视频生成器。

乒乓球游戏包括在屏幕上弹跳的球。桨(此处由鼠标控制)使用户能够使球弹回。

尽管可以使用其他任何FPGA开发板,但我们都使用PlutoFPGA板。

驱动VGA显示器

一个VGA监视器需要5个信号才能显示图片:

  • R,G和B(红色,绿色和蓝色信号)。
  • HS和VS(水平和垂直同步)。

R,G和B是模拟信号,而HS和VS是数字信号。

通过FPGA引脚创建VGA视频信号

以下是驱动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 VGA视频信号,脉冲频率应为:

垂直频率(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; wireCounterXmaxed=(CounterX==767); always@(posedgeclk)if(CounterXmaxed) CounterX<=0;else CounterX<=CounterX+1; always@(posedgeclk)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脉冲(以获得无毛刺输出)。

regvga_HS,vga_VS;always@(posedgeclk)begin vga_HS<=(CounterX[9:4]==0);//activefor16clocks vga_VS<=(CounterY==0);//activefor768clocksend

VGA输出必须为负,因此我们将信号反相。

assignvga_h_sync=~vga_HS; assignvga_v_sync=~vga_VS;

最后,我们可以驱动R,G和B信号。首先,我们可以使用X和Y计数器的一些位来获得漂亮的正方形颜色图案…

assignR=CounterY[3]|(CounterX==256); assignG=(CounterX[5]^CounterX[6])|(CounterX==256); assignB=CounterX[4]|(CounterX==256);

…然后我们在VGA监视器上得到一张照片!

画有用的图片

最好将同步生成器重写为HDL模块,以便在外部生成R,G和B。同样,如果X和Y计数器从绘图区域开始计数,它们将更加有用。 可以在这里找到新文件。

现在,我们可以使用它在屏幕周围绘制边框。

modulepong(clk,vga_h_sync,vga_v_sync,vga_R,vga_G,vga_B); inputclk; outputvga_h_sync,vga_v_sync,vga_R,vga_G,vga_B; wireinDisplayArea; wire[9:0]CounterX; wire[8:0]CounterY; hvsync_generatorsyncgen(.clk(clk),.vga_h_sync(vga_h_sync),.vga_v_sync(vga_v_sync), .inDisplayArea(inDisplayArea),.CounterX(CounterX),.CounterY(CounterY)); //Drawaborderaroundthescreen wireborder=(CounterX[9:3]==0)||(CounterX[9:3]==79)||(CounterY[8:3]==0)||(CounterY[8:3]==59); wireR=border;wireG=border;wireB=border; regvga_R,vga_G,vga_B; always@(posedgeclk)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@(posedgeclk)quadAr<={quadAr[1:0],quadA}; always@(posedgeclk)quadBr<={quadBr[1:0],quadB}; always@(posedgeclk)if(quadAr[2]^quadAr[1]^quadBr[2]^quadBr[1])begin if(quadAr[2]^quadBr[1]) begin if(~&PaddlePosition)//makesurethevaluedoesn'toverflow PaddlePosition<=PaddlePosition+1; end else begin if(|PaddlePosition)//makesurethevaluedoesn'tunderflow PaddlePosition<=PaddlePosition-1; endend

现在知道“ PaddlePosition”的值,我们可以显示桨了。

wireborder=(CounterX[9:3]==0)||(CounterX[9:3]==79)||(CounterY[8:3]==0)||(CounterY[8:3]==59); wirepaddle=(CounterX>=PaddlePosition+8)&&(CounterX<=PaddlePosition+120)&&(CounterY[8:4]==27); wireR=border|(CounterX[3]^CounterY[3])|paddle; wireG=border|paddle; wireB=border|paddle;

画球

球需要在屏幕上移动,并在碰到物体(边界或球拍)时反弹。

首先,我们展示球。它是16×16像素的正方形。当CounterX和CounterY到达其坐标时,我们将激活球的绘制。

reg[9:0]ballX;reg[8:0]ballY; regball_inX,ball_inY; always@(posedgeclk)if(ball_inX==0)ball_inX<=(CounterX==ballX)&ball_inY; elseball_inX<=!(CounterX==ballX+16); always@(posedgeclk)if(ball_inY==0)ball_inY<=(CounterY==ballY); elseball_inY<=!(CounterY==ballY+16); wireball=ball_inX&ball_inY;

现在进行碰撞。那是这个项目的困难部分。

我们可以检查球相对于屏幕上每个对象的坐标,并确定是否存在碰撞。但是随着对象数量的增加,这将很快成为一场噩梦。

取而代之的是,我们定义4个“热点”像素,在球每侧的中间一个像素。如果物体(边界或球拍)在球绘制其“热点”之一的同时重绘自身,则我们知道球的那一侧存在碰撞。

wireborder=(CounterX[9:3]==0)||(CounterX[9:3]==79)||(CounterY[8:3]==0)||(CounterY[8:3]==59); wirepaddle=(CounterX>=PaddlePosition+8)&&(CounterX<=PaddlePosition+120)&&(CounterY[8:4]==27); wireBouncingObject=border|paddle;//activeiftheborderorpaddleisredrawingitself regCollisionX1,CollisionX2,CollisionY1,CollisionY2; always@(posedgeclk)if(BouncingObject&(CounterX==ballX)&(CounterY==ballY+8))CollisionX1<=1; always@(posedgeclk)if(BouncingObject&(CounterX==ballX+16)&(CounterY==ballY+8))CollisionX2<=1; always@(posedgeclk)if(BouncingObject&(CounterX==ballX+8)&(CounterY==ballY))CollisionY1<=1; always@(posedgeclk)if(BouncingObject&(CounterX==ballX+8)&(CounterY==ballY+16))CollisionY2<=1;

(我通过从未重置碰撞触发器来简化了上面的代码,下面提供了完整的代码)。

现在,我们更新球的位置,但每个视频帧仅更新一次。

regUpdateBallPosition;//activeonlyonceforeveryvideoframe always@(posedgeclk)UpdateBallPosition<=(CounterY==500)&(CounterX==0); regball_dirX,ball_dirY; always@(posedgeclk)if(UpdateBallPosition)begin if(~(CollisionX1&CollisionX2))//ifcollisiononbothX-sides,don'tmoveintheXdirection begin ballX<=ballX+(ball_dirX?-1:1); if(CollisionX2)ball_dirX<=1; elseif(CollisionX1)ball_dirX<=0; endif(~(CollisionY1&CollisionY2))//ifcollisiononbothY-sides,don'tmoveintheYdirection begin ballY<=ballY+(ball_dirY?-1:1); if(CollisionY2)ball_dirY<=1; elseif(CollisionY1)ball_dirY<=0; end end

最后,我们可以将所有内容整合在一起。

wireR=BouncingObject|ball|(CounterX[3]^CounterY[3]); wireG=BouncingObject|ball; wireB=BouncingObject|ball; regvga_R,vga_G,vga_B; always@(posedgeclk)begin vga_R<=R&inDisplayArea; vga_G<=G&inDisplayArea; vga_B<=B&inDisplayArea; end

哇,其实并不难。

  • 联系方式
  • 传 真:
  • 手 机:15383419322
  • 电 话:15383419322
  • 地 址:山西太原市杏花岭区解放路 175 号万达中心 A 座 33 楼 3301 室
友情链接
plc控制器
自动化设备
自动化设备
伺服驱动器
在线咨询

咨询电话:

15383419322

  • 微信扫码 关注我们

Copyright © 2022-2024 山西润盛进出口有限公司 版权所有 晋ICP备2021008479号-14

晋公网安备 14010702070906号

扫一扫咨询微信客服
15383419322