弹珠游戏
弹珠游戏
1、总体设计
如图 1 所示,为本次设计的原理图,一共设计了 5 个小模块,最后利用顶层模块
实例化各小模块得到整个设计,开发平台用 vivado。
各模块简单介绍:
ip_clk:时钟分频模块:利用时钟 IP 核产生 25MHz 的时钟。
timer:VGA 驱动模块,主要产生驱动 VGA 的行时序 clk_h 和场时序 clk_v,并
在屏幕扫描像素点时读取颜色数据 data[11:0],利用 rgb[11:0]输出相应的颜色,并输
出扫描坐标点(h_site,v_site),以便后续利用。
init:初始化弹珠运动方向模块,为了游戏的多样化,随机产生一个弹珠的初始
化运动方向数据。
crash_move:碰撞反弹及动态坐标模块,根据弹珠初始化运动方向和按键数据,
实时判断弹珠和挡板的中心点的动态坐标,并进行碰撞判断、游戏结束判断。
color_data:颜色数据模块,根据当前屏幕扫描点所在位置,判断扫描点是否进
入弹珠或挡板显示范围,在不同范围内设置不同的颜色数据。
图 1 总体设计原理图
1
弹珠游戏
2、各模块详细介绍
2.1 时钟分频模块
模块图如图 2 所示,利用 IP 核产生 25MHz 时钟,方便后续利用,过程简单,不
再赘述,程序可参见附录。
图 2 ip_clk 模块原理图
2.2 VGA 驱动模块
2.2.1 VGA 驱动原理
VGA 接口:如图 3 所示,是 Nexys4™ Artix-7 FPGA 开发板的 VGA 接口图。该
接口一共 15 个针状接口,其中 1、2、3 接口分别是红、绿、蓝颜色数据接口,每个
颜色数据接口占 4bit 信号;13 接口是行时序接口,负责行扫描;14 接口是场时序接
口,负责列扫描;其它接口均为接地接口,实验中可以悬空。
图 3 VGA 接口图
2
弹珠游戏
VGA 扫描方式:如图 4 所示,VGA 驱动显示时采用逐行扫描,需要行时序和场
时序,一个行时序周期完成一行图像的扫描,一个场时序周期完成一帧图像的扫描,
一帧图像显示像素为 640×480,每秒显示 60 帧图像。根据图 4,显示像素 640×480
对应的显示时序为 800×521,所以时钟频率为 800×521×60,约为 25MHz。
一个行时序周期 Ts 包括同步脉冲 Tpw、显示后沿 Tbp、显示时序段 Tdisp 和显
示前沿 Tfp,它们占用的时钟周期个数分别为 96、48、640 和 16,一共是 800 个时钟
周期。场时序周期类似,只是其不同时序部分的长短是按行时序周期的个数表示的,
正如图 3“Lines”一列。
图 4 VGA 扫描时序图
2.2.2 VGA 驱动模块设计
模块框图见图 5。首先,利用 25MHz 时钟,产生上述的的行时序 clk_h 和场时序
clk_v,另外还要设置一个显示使能信号 lcd_en,它只在显示时序段 Tdisp 内有效,据
此可设定出实时扫描屏幕像素点的坐标(h_cnt,v_cnt),还可以在 lcd_en 有效时取
颜色数据 data 到 rgb 中,然后显示在屏幕上。具体各信号的产生过程可参见附录中的
具体程序。
3
弹珠游戏
图 5 VGA 驱动模块 timer 框图
2.3 初始化弹珠运动方向模块
模块框图见图 6。为了避免弹珠一直上下或者左右运动,必须给弹珠一个斜向的
运动方向,为了游戏多元化,定义了 12 个初始化方向,由 move_state[1:0]、h_move[1:0]
和 v_move[1:0]共同决定,其中 move_state[1:0]表示运动方向对弹珠行列坐标的增减
影响,共有 0、1、2、3 四种状态,如图 7 所示,0 状态表示弹珠的行列坐标均会增
加,记为 0(+,+),同样其它方向为 1(+,-)、2(-,+)、3(-,-)。h_move[1:0]
和 v_move[1:0]指的是弹珠一次运动的行列像素距离,用(h_move[1:0],v_move[1:0])
表示,一共设了(1,1)、(1,2)、(2,1)三种。
为了从 12 个方向中随机选一个,定义了一个计数器,计数器按照拨码开关低电
平的时间不同在 1~12 之间循环,当拨码开关高电平时,按照计数值选择一个运动方
向。具体设计过程可参见附录中的相关程序。
图 6 初始化运动方向模块框图
图 7 move_state 状态图
4
弹珠游戏
2.4 碰撞反弹及动态坐标模块
原理框图如图 8 所示,将场时序 clk_v 作为输入时钟,每个场时序周期,也就是
每 帧 图 像 都 计 算 更 新 一 次 弹 珠 和 挡 板 的 坐 标 。move_state[1:0] 、h_move[1:0] 和
v_move[1:0]是上个模块设定的弹珠初始化运动方向数据,key 是挡板运动按键,h_ball
和 v_ball 是弹珠中心点的行列坐标,h_board 和 v_board 是挡板中心点的行列坐标,
die 是游戏结束信号。
模块内部首先定义一个弹珠和挡板的初始位置,然后根据弹珠位置坐标判断游戏
是否结束、弹珠是否发生碰撞反弹,若游戏结束则游戏结束信号有效,若发生碰撞反
弹,则确定新的弹珠运动方向,最后根据弹珠的运动方向和挡板按键计算新的弹珠和
挡板位置坐标。
其中弹珠每次碰撞反弹后都会定义一个新的弹珠初始化坐标和运动方向,还有一
个计数器判断在这个方向上弹珠运动的时钟周期个数,这样,在这个方向运动的弹珠,
其初始化坐标加上计数器和单个时钟的运动距离的乘积便等于新的坐标,即:新坐标
=初始化坐标+时钟周期数*单个时钟运动距离。
具体判断和计算过程参见下面的程序注解。
图 8 碰撞反弹及动态坐标模块图
关键程序:
always @(posedge clk_v or negedge rst_n) begin
//复位后定义一个初始化弹珠和挡板坐标
if(!rst_n) begin
state<=move_state;//用 state 取出上个模块定义的初始化运动方向数据之一
die<=0;
h_ball_init<=10'd320;//弹珠初始化坐标
v_ball_init<=10'd80;
h_ball<=10'd320;//弹珠实时动态坐标
5
弹珠游戏
v_ball<=10'd80;
h_board<=10'd320;//挡板初始化坐标
v_board<=10'd435;
count<=1;
end
else begin
if(v_ball>=474) die<=1;//若弹珠接触底部屏幕,则游戏结束
else begin
//弹珠碰撞判断,可参见上个模块的图 7,便于理解
case(state)
//参见图 7,0 状态时弹珠向右下方运动,只可能碰到挡板或屏幕右边界才会反弹
0:
if(h_ball>=h_board-35 && h_ball<=h_board+35 && v_ball==424)
/*若碰到挡板,会向右上方运动,进入状态 1,并定义新的弹珠初始化
坐标和计数器重新计时*/
begin state<=1;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
else if(h_ball>=634)
/*若碰到屏幕有边界,会向左下方运动,进入状态 2,并定义新的弹珠
初始化坐标和计数器重新计时*/
begin state<=2;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
else//无碰撞,状态不变,计数器加一
begin state<=state;count<=count+1;end
1://状态 1 下只可能碰到屏幕上边界或右边界,然后反弹进入状态 0 或 3
if(v_ball<=7)
begin state<=0;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
else if(h_ball>=634)
begin state<=3;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
else
begin state<=state;count<=count+1;end
2: //状态 2 下只可能碰到挡板或屏幕左边界,然后反弹进入状态 3 或 0
if(h_ball>=h_board-35 && h_ball<=h_board+35 && v_ball==424)
begin state<=3;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
else if(h_ball<=7)
begin state<=0;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
6
弹珠游戏
else
begin state<=state;count<=count+1;end
3: //状态 3 下只可能碰到屏幕上边界或左边界,然后反弹进入状态 2 或 1
if(v_ball<=7)
begin state<=2;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
else if(h_ball<=7)
begin state<=1;h_ball_init<=h_ball;v_ball_init<=v_ball;count<=1;end
else
begin state<=state;count<=count+1;end
endcase
end
case(state)
//新坐标=初始化坐标+时钟周期数*单个时钟运动距离,不同 state 状态下加或减不同
0:begin h_ball<=h_ball_init+count*h_move;v_ball<=v_ball_init+count*v_move;end
1:begin h_ball<=h_ball_init+count*h_move;v_ball<=v_ball_init-count*v_move;end
2:begin h_ball<=h_ball_init-count*h_move;v_ball<=v_ball_init+count*v_move;end
3:begin h_ball<=h_ball_init-count*h_move;v_ball<=v_ball_init-count*v_move;end
endcase
case(key)//根据按键计算新的挡板位置
//按右键,未接触屏幕右边界时才移动
2'b01: if(h_board <= 603) h_board<=h_board+3;
//按左键,未接触屏幕左边界时才移动
2'b10: if(h_board >= 37) h_board<=h_board-3;
//其它情况,状态锁存,不移动挡板
endcase
end
end
2.5 颜色数据模块
模块图见图 9,h_ball 和 v_ball 是弹珠行列坐标,h_board 和 v_board 是挡板行列
坐标,h_site 和 v_site 是当前扫描点坐标,die 是游戏结束信号。首先根据 die 信号判
断游戏是否结束,若结束,data 取蓝色,否则判断扫描坐标(h_site,v_site)所处范
围,若在弹珠范围内 data 取黄色,若在挡板范围内 data 取红色,若在其它地方 data
取白色。具体过程参见附录中程序。
7
弹珠游戏
图 9 颜色数据模块图
2.6 顶层模块
顶层模块实例化上述 5 个小模块,实际上只是实现各模块之间的端口连线,连接
图见图 1,具体程序见附录。
8