1) 设计一款在以51单片机为核心的硬件系统中运行的推箱子游戏。游戏一共4关,功能包括:显示游戏开始界面、游戏界面、游戏结束界面;
2) 按键处理包括移动小人和移动箱子,通过移动上下左右键来控制小人的移动,通过小人推动箱子,把箱子推到指定的目的地为过关,箱子只能推不能拉;
3) 具有蓝牙控制功能
4) 据有时间计时,显示玩家的总游戏耗时。
。
(1) 需要具有51单片机、蓝牙模块、12864LCD液晶模块
(2) 需要了解LCD显示图片和文字的原理和设计思路。
(3) 需要了解数码管显示的接线原理和设计思路。
(4) 需要了解定时器模块的原理和设计思路。
(5) 需要了解蓝牙模块的原理和设计思路。
1. LCD12864的引脚结构:
图1 LCD12864引脚结构图
(1) LCD12864显示驱动程序:
图2 LCD12864显示驱动程序
(2) LCD12864指令集:
在使用LCD前必须对其进行初始化操作,初始化的内容包括光标指针设置,图形区首地址,图形区宽度,显示方式设置,图形方式显示,标形状设置。
(1) LCD12864连线图:
LCD12864内置连接实验电路如图3所示。(我的51单片机所需LCD12864直接插入插口即可)
图3 LCD12864内置连接图
(2) 动态数码管连接图:
动态数码管连接实验原理路如图4所示。
图4 动态数码管接连线原理图
(3) 蓝牙模块原理图
蓝牙模块连接实验原理路如图5所示。(其中RXDTXD在烧入程序时需要先互换连接)
图5 蓝牙模块管接连线原理图
(4) 独立按键原理图:
独立按键连接实验原理路如图6所示。
图6 独立按键接连线原理图
(5) 实际连线图:
图6 实际连线图
1) 游戏元素设计:
对于出现在显示画面中的游戏元素总共有6种:墙壁,箱子,小人,目的地,空白,箱子在目的地。每个元素在液晶上以一个字符显示,即8×8的点阵。在字模提取软件设计各个元素,并获得元素的显示码。显示码的每8位构成字符的一行,8就行有8个字节。其中1表示该点显示,0表示该点不显示。
小人 墙壁 箱子
目标 箱子到达目标 空白
我通过定义一个二维数组elements[][8],存储这些点阵信息。其中第一个地址为元素代号。
unsigned char codeelements[][8]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //空白
0x20,0x96,0xDF,0x7B,0x7B,0xDF,0x96,0x20, //人物
0x70,0x77,0x77,0x07,0x77,0x77,0x77,0x77, //砖头
0xFF,0xC3,0xA5,0x99,0x99,0xA5,0xC3,0xFF, //箱子
0x00,0x18,0x24,0x4A,0x52,0x24,0x18,0x00, //目标
0xFF,0xBD,0xDB,0xE7,0xE7,0xDB,0xBD,0xFF, //箱子到达目标
};
2) 元素的显示
由于LCD12864是按以列1字符(8点),行1点来定位的,所以显示时,要先定义一个for循环将i设置为0到7,来定位显示字符位的列字符和行字符首地址,即行=0xb0+i,列从0x10,0x00开始;显示一行数据时,需要定义两层for循环,首循环先确定显示元素内循环确定对应元素所要生成的8个字符,即elements[元素代号][k],就可以在液晶的指定字符位置上显示该元素了。
3) 游戏关卡设计
游戏每关都是在8×8个字符组成的区域中进行的,只要在每一个字符显示相应的元素,就可以组成游戏界面。定义一个三维数组elements1[game][8][8],存储每一关每一个字符位上要显示的元素,其中game表示关卡。
4) 游戏流程
游戏中,程序循环执行if语句判断按键是否按下或者是否有对应蓝牙输入,并做出相应判断。整个程序中,最关键的部分是要根据玩家的输入,控制小人、箱子的移动。
这里由于上下左右移动原理类似我只介绍向上移动人物的原理:
a) 在向上移动人物的函数中我首先通过扫描函数确定人物所在位置(x,y)。
b) 在确定人物位置后,我定义两个循环来确定临时存储元素的三维数elements2中人物上方位置(x+1,y)所在元素是什么。当上方所在元素是空白时,将人物所在位置元素设置为空白,(x+1,y)所在位置设置为人物。类似如此,当人物上方时砖头时不做任何改变,当人物上方位置为箱子时要考虑人和箱子一起移动的情况:这时需要进入内置if语句来判断箱子上方物体是什么,由于简便程序这里未考虑有两个箱子、目标的情况。
c) 判断完毕并改变完元素位置后若仍无到达目标则调用changement()函数来显示新的元素图像。
d) 当本次移动出现达到目标的情况时需要将game关卡数加一,由于一共设计了4个关卡,当game不等于4时会通过调用printelement()函数直接显示下一关的地图。
e) 我需要知道整个图像中人所在的位置,我通过定义一个二维数组,用于保存当前游戏状态每个字符位的元素,当玩家按键输入时,改变该数组中相应得数据,再将此二维数组每个数据代码的元素显示出来,就可以出现小人,箱子移动的效果。
每关开始,进行游戏初始化。将关卡数据写入上述二维数组中,并记入小人的位置(man_x,man_y)。
程序只要根据小人移动方向周围的元素信息,结合游戏规则,只要判断能够移动箱子、小人的情况,才显示出来,而不用对不能移动的情况进行判定,这大大简化了程序的编写。
游戏流程框图如下(以向上移动为例,其他方向类似):
图7向上移动流程图
5) 计时模块设计
我通过定时器0中断来计时,
a) 设计定时器0中断初始化函数Timer0Init(),设置IT1=1为下降沿触发;设置EX1=1打开INT1的中断允许;设置TMOD|=0X01来选择为定时器0模式,工作方式1,仅用TR0打开启动;设置TH0=0Xd8,TL0=0Xf0来给定时器赋初值,定时10ms;设置ET0=1打开定时器0中断允许;设置EA=1打开总中断。
b) 设置定时器0中断函数,先为定时器写入初值10ms;将ssec++记录已经过10ms当ssec等于100时将sec++记录过了1秒,由于为了简便程序,总计时只设置了99s。
6) 蓝牙模块设计
a) 设计串口中断函数InitUART():初始化串口波特率9600,打开外部中断
b) 设计中断实现函数:通过RI确定是否有输入,当有输入时先将RI清0,并用m2记录输入数据。(需要注意程序下载进51单片机时蓝牙模块连线为RXD接P3.0(内置RXD),TXD接P3.0(内置TXD),当实际通过蓝牙写入数据时,需要将RXDTXD互换连接)
7) 主程序初始化
a) 调用Lcd12864_Init()函数将LCD软件初始化:调用reset命令、设置左右方向为正常方向、设置上下方向为反向、设置字体为黑色北京为白色、设置像素为正常显示、设置偏压为1/7、设置增压、设置背光对比度等。
b) 调用Lcd12864_ClearScreen()函数清除屏幕。
c) 调用蓝牙模块初始化函数
d) 调用定时器中断初始化函数
e) 设计while(1)循环等待按键、蓝牙输入或等待中断。
图8 主函数程序流程图
函数名: | 函数作用: |
delay1(u16 p) | 延时函数1,用于短延时 |
delay(u16 t) | 延时函数2,用于t ms延时 |
printelement() | 用于LCD生成图片,其内容为先通过elements1数组选择元素,在通过elements数组生成对应元素的每个字符位数据 |
changement()
| 用于LCD生成改变后图片,其内容为先通过elements2数组选择元素,在通过elements数组生成对应元素的每个字符位数据 |
InitUART() | 蓝牙模块串口初始化 |
Timer0Init() | 定时器模块初始化设计 |
DigDisplay() | 位选显示数码管数字 |
datapros0() | 数码管初始内容为0 |
datapros1() | 设计数码管内容为计时时间 |
scan_x() | 遍历所选关卡的元素找到人物所在位置并返回行数 |
scan_y() | 遍历所选关卡的元素找到人物所在位置并返回列数 |
key_up() | 调整人物位置向上移动 |
key_down() | 调整人物位置向下移动 |
key_left() | 调整人物位置向左移动 |
key_right() | 调整人物位置向右移动LcdSt7565_WriteCmd |
LcdSt7565_WriteCmd | 打开LCD模块写入对应命令 |
LcdSt7565_WriteData | 打开LCD模块写入对应数据 |
Lcd12864_Init() | LCD模块初始化设计 |
Lcd12864_ClearScreen | LCD模块清屏Lcd12864_Write16CnCHAR |
Lcd12864_Write16CnCHAR | LCD模块显示设置的汉字 |
(1) st7565.c
#include"st7565.h"
//函数名: LCD12864_WriteCmd函数功能:写入一个命令到12864
void LcdSt7565_WriteCmd(cmd)
{LCD12864_CS = 0; //chip select,打开片选LCD12864_RD = 1; //disable read,读失能 LCD12864_RS = 0; //select command,选择命令LCD12864_RW = 0; //select write,选择写模式_nop_();_nop_();DATA_PORT = cmd; //put command,放置命令_nop_();_nop_();LCD12864_RW = 1; //command writing ,写入命令
}// 函 数 名: LcdSt7565_WriteData 函数功能 : 写入一个数据到12864
void LcdSt7565_WriteData(dat)
{ LCD12864_CS = 0; //chip select,打开片选LCD12864_RD = 1; //disable read,读失能 LCD12864_RS = 1; //select data,选择数据LCD12864_RW = 0; //select write,选择写模式_nop_();_nop_();DATA_PORT = dat; //put data,放置数据_nop_();_nop_();LCD12864_RW = 1; //data writing,写数据
}
// 函 数 名: LCD12864_Init 函数功能 初始化12864
void Lcd12864_Init()
{uchar i;LCD12864_RSET = 0;for (i=0; i<100; i++);LCD12864_CS = 0;LCD12864_RSET = 1;//--软件初始化--//LcdSt7565_WriteCmd(0xE2); //resetfor (i=0; i<100; i++); //延时一下//0xA0段(左右)方向选择正常方向(0xA1为反方向)--//LcdSt7565_WriteCmd(0xA0); //ADC select segment direction //0xC8普通(上下)方向选择选择反向,0xC0为正常方向--// LcdSt7565_WriteCmd(0xC8); //Common direction //0xA6为设置字体为黑色,背景为白色---////--0xA7为设置字体为白色,背景为黑色---//LcdSt7565_WriteCmd(0xA6); //reverse display//0xA4像素正常显示,0xA5像素全开--//LcdSt7565_WriteCmd(0xA4); //normal display//0xA3偏压为1/7,0xA2偏压为1/9--//LcdSt7565_WriteCmd(0xA2); //bias set 1/9//-这个是个双字节的命令,0xF800选择增压为4X;--////--0xF801,选择增压为5X,其实效果差不多--// LcdSt7565_WriteCmd(0xF8); //Boost ratio setLcdSt7565_WriteCmd(0x01); //x4//-这个是个双字节命令,高字节为0X81,低字节可以--////--选择从0x00到0X3F。用来设置背景光对比度。---/LcdSt7565_WriteCmd(0x81); //V0 a setLcdSt7565_WriteCmd(0x23);//选择调节电阻率--//LcdSt7565_WriteCmd(0x25); //Ra/Rb set//电源设置。--//LcdSt7565_WriteCmd(0x2F);for (i=0; i<100; i++);//设置显示开始位置--//LcdSt7565_WriteCmd(0x40); //start line//开启显示--//LcdSt7565_WriteCmd(0xAF); // display onfor (i=0; i<100; i++);}// 函 数 名: LCD12864_ClearScreen 函数功能: 清屏12864
void Lcd12864_ClearScreen(void)
{uchar i, j;for(i=0; i<8; i++){//--设置Y的坐标--////--Y轴有64个,一个坐标8位,也就是有8个坐标--////所以一般我们使用的也就是从0xB0到0x07,就够了--// LcdSt7565_WriteCmd(0xB0+i); //--设置X坐标--////--当你的段初始化为0xA1时,X坐标从0x10,0x04到0x18,0x04,一共128位--////--当你的段初始化为0xA0时,X坐标从0x10,0x00到0x18,0x00,一共128位--////--在写入数据之后X坐标的坐标是会自动加1的,我们初始化使用0xA0所以--////--我们的X坐标从0x10,0x00开始---//LcdSt7565_WriteCmd(0x10); LcdSt7565_WriteCmd(0x00); //--X轴有128位,就一共刷128次,X坐标会自动加1,所以我们不用再设置坐标for(j=0; j<128; j++){LcdSt7565_WriteData(0x00); //如果设置背景为白色时,清屏选择0XFF}}
}//函 数 名: LCD12864_Write16CnCHAR 函数功能: 在12864上面书写16X16的汉字
#ifdef CHAR_CODE
#include"charcode.h"
uchar Lcd12864_Write16CnCHAR(uchar x, uchar y, uchar *cn)
{uchar j, x1, x2, wordNum;//--Y的坐标只能从0到7,大于则直接返回--//if(y > 7){return 0;}//--X的坐标只能从0到128,大于则直接返回--//if(x > 128){return 0;}y += 0xB0; //求取Y坐标的值//--设置Y坐标--//LcdSt7565_WriteCmd(y);while ( *cn != '