说在前头:整个代码工程以及实验报告在个人上传资源中,如有需要请去下载,博客中只是部分代码,完整的代码工程
《单片机原理与实践》课程大作业
“老鼠吃蛋糕”技术报告
摘 要
单片机开发小游戏很普遍,而且网上也有各种开源文件。至于我,开始想实现个迷宫,后来发觉迷宫太静态了,拓展空间不大。后来想到了方块扩散,感觉方块扩散实现步数存在着最优路径,但尝试后发现难度极大且涉及深度学习算法,于是就两者结合设计了“老鼠吃蛋糕”这存在最优路径且拓展性高的游戏。
该游戏采用触摸屏、显示屏、红外遥控、串口通信、数码管、定时器、蜂鸣器、LED灯等外设来实现该游戏,功能相对广泛。其中显示屏用作游戏界面以及交互界面的显示,红外用来控制,串口是信息显示,定时器实时处理并作相应的反馈,蜂鸣器和LED则是指示作用。其中各个模块的功能相对简单,但结合起来就不简单了,具体的困难会在后续章节中提出。
关键词: 迷宫;方块扩散;最优路径;串口;定时器
1. 系统方案
本系统主要由显示屏、触摸屏、红外遥控、串口通信、数码管等模块组成。由于是在学校下发的单片机开发板上设计,所以并不涉及各个模块的品牌、类型选择,下面简述一下游戏某一功能实现模块的选择。
1.1游戏操纵方案的论证与选择
方案一:触摸屏控制。触摸屏控制是比较普遍的,比如我们的智能手机以及部分笔记本电脑,但对于该开发板来说并不适用。主要有两点:① 由于制作工艺及器件的老化存在着噪点,即使采用中值法去噪效果较好,但那么小的显示屏摆放好几个操作按键也不免会误触碰;② 由于显示屏本身就是240*400大小,若再放置好几个按键则对后期的游戏设计界面有很大的影响。
方案二:红外遥控。比如以前的彩色电视就可以通过红外遥控来玩自带的俄罗斯方块、推箱子之类的游戏。用红外来控制效果也确实不错,既不用那么近盯着显示屏,也可以选择个舒服的姿势玩游戏,唯一的缺点就是偶尔会解码错误,主要是因为人为按按键不规则以及信号传送过程会有干扰。
经过对以上两种方案的对比,选择方案二相对来说更为可靠。既可以避免触摸屏的噪点问题和游戏界面的影响,红外遥控的解码错误也是在可接受的范围内。
1.2 游戏相关信息显示方案的论证与选择
方案一:显示屏显示。提示信息和游戏界面在一处显示对玩家来说比较方便,边观察游戏界面就可以看到提示信息。但同样出现界面大小问题,不能显示太多信息,同时由于不支持输出显示中文,所以对于游戏玩家不太方便。
方案二:串口助手显示。可以通过PC端串口助手接受单片机串口传送的信息,既能输出显示中文,又有较大的显示界面,查看信息比较容易,但是游戏玩家需要不断地在显示屏和PC端串口助手界面间转换视角,容易分神,不利于游戏的控制。
方案三:数码管显示。数码管只能显示数字及字符,显示内容比较单一,但是数码管比较醒目,可以用来显示时间之类的信息。
综合以上三种方案,决定使用三种方案来显示不同的信息。用方案一中显示屏来显示游戏开始界面、关卡选择以及游戏结束界面;方案二串口助手来显示游戏开始,游戏操作说明,游戏操作过程的步数、时间信息以及游戏的相关提示信息;方案三数码管醒目地显示倒计时,对游戏玩家有提示作用。
1.3 系统方案
1.3.1.系统方框图
1.3.2系统工作原理
本系统开始在显示屏显示指导信息,等待玩家触摸显示屏或红外遥控来进行游戏相关的选择和控制,在游戏开始后定时器开始计时并通过数码管来显示,在操纵期间显示屏右上角也会提供倒计时的显示来提示玩家,PC端串口助手会显示相关操作信息来帮助玩家进行游戏。其中红外接收管接受遥控信息(PN4中断),进行解码得到控制信息并进行相应的程序处理,如此循环反复。玩家若及时地控制“老鼠”吃到“蛋糕”则挑战成功,显示屏和PC端串口助手会显示胜利的相关信息,蜂鸣器和LED也会作出反应;反之则挑战失败,会有失败的相应信息输出显示。
2. 设计与论证
2.1 系统的硬件设计
由于使的单片机开发板为学校下发,故没有进行系统的硬件设计,只是进行了硬件模块的使用选择。
2.2 系统软件设计
3. 测试
3.1硬件电路的测试方法
由于使用的单片机开发板为学校下发,故没有进行系统的硬件设计,只是进行了硬件模块的性能测试及性能测试,下面主要对触摸屏及红外遥控的性能进行简要说明。
触摸屏:由于制作工艺及器件的老化存在着噪点,即使采用中值法去噪效果较好,但那么小的显示屏若要真正设计几个触控按键难免会误触碰,对于时间紧张的游戏来说无疑是不适用的,所以只是在开始界面提供了触摸功能。
红外遥控:用红外来控制效果也确实不错,既能较远距离进行控制,又能有个好姿态。唯一不足的就是偶尔会出现解码错误的情况,这跟程序中有多个中断以及红外信号传输存在干扰有关,经过对解码时序进行合理的修改后,虽然效果改善了,但仍存在,不过是在可接受范围内。
3.2 项目特色说明
该游戏的特色主要有三点:
① 数码管多位数字同时显示。该开发板上的五个米字管是由三块PCA9557PWR芯片同时控制一片LEDS05441芯片来进行显示的,所以一次只能控制一个米字管,所以很多同学的10~15只用16进制的A~F来表达。所以要“同时”显示两个米字管,只能通过让它们交替显示的频率非常快,快到我们人眼观察不到。刚开始把程序段放在主程序的while(1)循环里,但这样严重干扰了红外遥控的中断信号接收,整个程序的时序是乱的,然后开了个定时器,通过设置定时器的频率来控制米字管,但又碰到了中断优先级问题,于是又折腾了一番,一切解决之后,终于可以同时显示两位数字了,三位、四位也同理。现在回过头感觉这几个问题很简单,但开始遇到问题、分析问题、解决问题花费了较多时间,感觉收获蛮多。
② 游戏本身的设定。刚开始想实现个迷宫,后来发觉迷宫太静态了,拓展空间不大。后来想到了方块扩散,感觉方块扩散实现步数存在着最优路径,于是就想把最优路径找出来,但尝试后发现难度极大且涉及深度学习算法,于是就两者结合设计了“老鼠吃蛋糕”这存在最优路径且拓展性高的游戏。第一关是最基础的,也没什么亮点;第二关也只是加了常见的“转向块”,对游戏难度有一定的提升;第三关则采用了三个分块区域,只有经过一定路径使用“传送块”功能才能进行区域的跳转,为什么要区域跳转呢?因为我在定时器里设计了一个随机函数,定时器的频率决定了蛋糕在三个区域跳转的频率,然后随机函数则是进一步决定蛋糕跳转频率以及跳转区域,这游戏玩法想了挺久才想到的,自身感觉这样的游戏设定蛮好的。
③ 模块的集成。该游戏涉及了课上实验的较多模块,如触摸屏、显示屏、红外遥控、串口通信、数码管、定时器、蜂鸣器、LED灯,虽然这些模块并不那么难,但是要把各个模块结合起来用并应用在游戏当中就不那么容易了,之间有严格的逻辑关系以及单独情况的处理,就像上面说的中断优先级问题一样,其他模块也存在相互干扰的情况,比如中断里面不能再嵌套中断服务函数以及延时函数,否则严重影响程序的时序,严重可能使板子进入自锁状态。
3.3 测试结果
测试结果在视频和附录二中有详细说明,这里就不再重复了。
1)陈朋. Cortex-M4实验指导书. 浙江工业大学出版社. 2014.9
2)陈朋. Cortex-M4原理与实践. 浙江工业大学出版社. 2014.9
3) 王日明, 廖锦松, 申柏化. 轻松玩转ARM 微控制器--基于Kinetis K60. 北京航空航天大学出版社. 2014.9
4) 刘火良, 杨森. STM32库开发实战指南. 机械工业出版社. 2017.2
附录一 电路原理图
米字管电路图
红外IR电路图
蜂鸣器电路图
TFTLCD液晶显示电路图
附录二 实物图
注:第三关游戏界面,其中黄色箭头指的是“传送块”,红色箭头是“蛋糕”随机出现的位置,深蓝色箭头是“老鼠”所在位置。
注:游戏说明界面,深蓝色箭头所指内容是游戏说明,黄色箭头是“退出”键,品红箭头是关卡“easy”键,黑色箭头是关卡“mid”键,白色箭头是关卡“hard”键。
注:这是游戏开始界面,白色是游戏名,黑色是游戏说明引导键,红色是“退出”键,黄色是“easy”键,绿色是“mid”键,橙色是“hard”键。
注:PC端串口助手关于游戏相关信息的显示界面。
注:游戏第二关界面,红色箭头为“转向块”,黄色箭头为“蛋糕”,橙色箭头为“老鼠”。
注:游戏第一关界面,红色箭头为“老鼠”,黄色箭头为“蛋糕”。
注明:完整代码和实验报告在博主的个人资源中,博客只是简单描述,如有需要自行前往下载,完整代码工程
代码附录
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_epi.h"
#include "inc/hw_nvic.h"
#include "inc/hw_ints.h"
#include "driverlib/epi.h"
#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/pin_map.h"
#include "driverlib/systick.h"
#include "driverlib/interrupt.h"
#include "driverlib/ssi.h"
#include "driverlib/fpu.h"
#include "driverlib/timer.h"
#include "utils/uartstdio.h"
#include "TFTinit/TFT_400x240_OTM4001A_16bit.h"
//#include "TFTinit/picture.h"
#include "TOUCHinit/TOUCH_TSC2046.h"
#include "EPIinit/EPIinit.h"
#include "math.h"
#include "inc/hw_i2c.h"
#include "driverlib/i2c.h"
*********************************************************************
// System clock rate in Hz.
//
//*****************************************************************************
/触摸屏//
uint32_t g_ui32SysClock;
extern uint32_t GetData[6];
uint32_t N=0;//打中的只数
uint32_t TouchXData[6];
uint32_t TouchYData[6];
uint32_t TouchZData[6]; //Z is for pressure measurement
#define _NOP() _nop()
//*********************************************************************
//*********************************************************************
#define I2C0_MASTER_BASE 0x40020000
#define I2C0_SLAVE_BASE 0x40020000
//*********************************************************************
// 地址、寄存器等定义部分
//*********************************************************************
//*********************************************************************
//
// 设定slave(从)模块的地址。这是一个7-bit的地址加上RS位,具体形式如下:
// [A6:A5:A4:A3:A2:A1:A0:RS]
// RS位是一个指示位,如果RS=0,则说明是主发送数据,从接收数据;RS=1说明是主接收数据,从发送数据
//
//*********************************************************************
//U21控制4个米字管和特殊管脚的亮灭
#define I2C0_ADDR_TUBE_SEL 0x30 //00110000
//U22控制米字管7~14管脚对应的码段
#define I2C0_ADDR_TUBE_SEG_LOW 0x32 //00110010
//U23控制米字管15~18管脚对应的码段
#define I2C0_ADDR_TUBE_SEG_HIGH 0x34 //00110100
//U24控制LED光柱
//PCA9557内部寄存器,也称子地址
#define PCA9557_REG_INPUT 0x00
#define PCA9557_REG_OUTPUT 0x01
#define PCA9557_REG_PolInver 0x02
#define PCA9557_REG_CONFIG 0x03
//*************************************************************************************
#define NUM 0
#define EASY_I 8
#define EASY_J 7
#define MID_I 14
#define MID_J 8
#define HARD_I 26
#define HARD_J 12
/红外///
volatile uint32_t ui32SysClock;
volatile uint32_t error= 0;
volatile uint32_t count= 0;
unsigned char IrData[4];
int begin_easy_x=50,begin_easy_y=120;
int begin_mid_x=40,begin_mid_y=60;
int begin_hard_x=30,begin_hard_y=2;
int easy_i = 1,easy_j = 3,easy_end_x=6,easy_end_y=3;
int mid_i = 9,mid_j = 1,mid_end_x=6,mid_end_y=3;
int hard_i = 6,hard_j = 3;hard_cake_i=2,hard_cake_j=7;
int easy_time = 70,mid_time = 230,hard_time = 600,easy_R,easy_G,easy_B;
int mid_tra_x1 = 4,mid_tra_x2 = 6,mid_tra_y1=4,mid_tra_y2=5;
int easy_end = 0,mid_end=0;hard_end=0,easy_begin=0,mid_begin=0,hard_begin=0;
int hand_u=0,hand_d=0,hand_r=0,hand_l=0;
int kong_flag=0,time=0,time_flag=0;
int flag=0,cake_flag;
int touch_flag=0;
int level=0;
int min(int a,int b)
{
if(a<b) return a;
else return b;
}
int max(int a,int b)
{
if(a<b) return b;
else return a;
}
long hand(int color)
{
if((color==1)||(color==2)||(color==4)) return 0X7D7C;//浅蓝色
if(color==5) return 0xF800;//蓝色
if(color==3) return 0xFFFF;//白
}
//1 背景;2 老鼠;3 蛋糕;4 转向块;5 传送洞
int piexl_easy_ori[8][7]={ {0,0,0,0,0,0,0},
{0,0,1,2,0,0,0},
{0,1,1,1,1,1,0},
{0,1,1,1,0,1,0},
{0,0,0,0,0,1,0},
{0,1,1,1,1,1,0},
{0,1,1,3,0,0,0},
{0,0,0,0,0,0,0} };
int piexl_mid_ori[14][8]={ {0,0,0,0,0,0,0,0},{0,0,1,1,0,1,0,0},{0,0,1,1,1,1,1,0},{0,1,1,1,1,1,1,0},{0,1,1,0,4,1,0,0},
{0,1,1,1,1,1,1,0},{0,0,1,3,1,4,1,0},{0,0,1,0,1,1,1,0},{0,0,1,0,0,0,0,0},{0,2,1,1,1,0,0,0},
{0,0,1,1,1,1,1,0},{0,1,1,1,1,1,1,0},{0,1,1,1,1,0,0,0},{0,0,0,0,0,0,0,0} };
int piexl_hard_ori[26][12]={ {0,0,0,0,0,0,0,0,0,0,0,0},{0,1,1,1,0,1,1,0,0,0,0,0},
{0,1,0,1,1,1,1,3,0,0,0,0},{0,1,1,1,0,1,1,0,0,0,0,0},
{0,1,1,1,1,1,1,5,0,0,0,0},{0,1,0,1,1,1,1,0,0,0,0,0},
{0,1,1,2,1,1,1,0,0,0,0,0},{0,1,0,0,1,1,1,1,0,0,0,0},
{0,1,1,1,1,1,1,1,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,1,0,0,0,1,1,1,0,0},{0,0,5,1,1,1,1,1,1,1,1,0},
{0,1,1,1,1,1,1,1,0,1,1,0},{0,1,1,1,1,1,1,0,0,0,0,0},
{0,1,0,1,1,1,1,1,0,0,0,0},{0,1,1,1,1,1,0,1,0,0,0,0},
{0,1,1,1,1,1,1,1,0,0,0,0},{0,0,0,1,3,1,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0},
{0,1,1,1,0,0,0,1,1,0,0,0},{0,1,0,1,1,1,1,1,1,0,0,0},
{0,1,1,1,1,5,0,1,1,0,0,0},{0,1,1,0,1,1,1,1,0,0,0,0},
{0,1,0,1,1,0,1,3,1,0,0,0},{0,1,1,1,1,0,1,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0} };
int piexl_easy[8][7],piexl_mid[14][8],piexl_hard[26][12];
void recover(int a,int b)
{
int i,j;
if(a==8)
{
for(i=0;i<a;i++)
for(j=0;j<b;j++)
{
piexl_easy[i][j] = piexl_easy_ori[i][j];
// piexl_easy[i][j][1] = 0;
}
easy_i=1;
easy_j=3;
}
else if(a==14)
{
for(i=0;i<a;i++)
for(j=0;j<b;j++)
piexl_mid[i][j] = piexl_mid_ori[i][j];
mid_i=9;
mid_j=1;
}
else if(a==26)
{
for(i=0;i<a;i++)
for(j=0;j<b;j++)
{
piexl_hard[i][j]= piexl_hard_ori[i][j];
}
hard_i=6;
hard_j=3;
}
}
void exit_recover()
{
TFTLCD_ShowString(52,180," exit(0) ",RED,LIGHTBLUE);
TFTLCD_ShowString(52,230," easy(1) ",RED,LIGHTBLUE);
TFTLCD_ShowString(52,280," mid (3) ",RED,LIGHTBLUE);
TFTLCD_ShowString(52,330," hard(9) ",RED,LIGHTBLUE);
easy_begin=0;
mid_begin=0;
hard_begin=0;
easy_time = 80;
mid_time = 200;
hard_time = 600;
hand_u=0;
hand_l=0;
hand_r=0;
hand_d=0;
touch_flag=0;
recover(8,7);
recover(14,8);
recover(26,12);
IntDisable(INT_TIMER0B);
TimerDisable(TIMER0_BASE, TIMER_B);
}
void end_game()
{
TFTLCD_CLEAR(BLACK);
TFTLCD_ShowString(52,30," Time over ! ",WHITE,LIGHTBLUE);
TFTLCD_ShowString(52,80," you fail ",RED,LIGHTBLUE);
UARTprintf("很遗憾,游戏时间到,闯关失败!可重玩一次或进入下一关或直接退出。\n");
UARTprintf("退出--0;简单--1;中等--3;困难--9\n");
exit_recover();
}
void instructions()
{
TFTLCD_CLEAR(BLACK);
TFTLCD_ShowString(52,20,"this is a instructions about games:you need to control the mouse by 2|4|6|8 to eat the cake. Only complete the game within a certain time, can you beat the game.",RED,LIGHTBLUE);
UARTprintf("游戏说明:该游戏总共有三关,通过红外遥控可选择关卡。你所假扮的“老鼠”必须在规定时间内通过利用各种道具“吃到蛋糕”,这样才算挑战成功.其中有转向块和传送块,可以对“老鼠”位置进行改变,然后最后一关蛋糕还用到的随机函数来确定蛋糕在哪个位置。\n");
TFTLCD_ShowString(52,180," exit(0) ",RED,LIGHTBLUE);
TFTLCD_ShowString(52,230," easy(1) ",RED,LIGHTBLUE);
TFTLCD_ShowString(52,280," mid (3) ",RED,LIGHTBLUE);
TFTLCD_ShowString(52,330,&#