STM32F407基于陀螺仪的贪吃蛇小游戏(全网最详细)

一、前言

      本次设计我用MPU6050在KEIL 5的环境下改进了贪吃蛇小游戏,用的开发板是STM32F407,本次设计综合利用多种ARM外设接口实现对外部设备信息的采集、处理及显示,实现了贪吃蛇游戏实现的基本功能,并且针对游戏体验感,采用了陀螺仪,并将其固定在开发板上,以达到晃动开发板即可实现贪吃蛇的移动的功能。

二、设计内容

  • 本次设计涉及到软件和硬件两个部分,硬件部分主要是外接模块MPU6050与开发板的接线,软件部分涉及到TFTLCD、MPU6050、trc、I2C等模块以及游戏程序的编程。

  • 软件部分设计程序关键在于表示蛇的图形及蛇的移动。用一个小矩形快表示蛇的一节身体,身体每长一节,增加一个矩形块,蛇身初始化用三节表示。移动时必须从蛇头开始,所以蛇不能向相反的方向移动,如果没有更改方向,蛇自行在当前方向上前移,但当晃动开发板,蛇头将朝着该晃动方向移动,一步移动一节身体,所以当确定方向后,先确定蛇头的位置,而后蛇的身体随蛇头移动,图形的实现是从蛇头新位置开始画出蛇,这时,由于未清屏的原因,原来的蛇的位置和新蛇的位置差一个单位,所以看起来蛇多一节身体,所以将蛇的最后一节用背景色覆盖。食物的出现与消失也是画矩形块和覆盖矩形块。为了便于理解,定义两个结构体:食物与蛇。

图1:硬件框图

三、硬件接线

      由于板子自带的MPU6050不太好用,我是外接的器件,开发板自带的陀螺仪如果好使的话可以不用外界哈٩(๑òωó๑)۶

四、硬件设计

      本次设计使用了模块TFTLCD来进行游戏界面的显示,MPU6050来测量,USART1串口来实现通信相关配置。

1. TFTLCD模块

硬件原理:

      TFT-LCD是薄膜晶体管液晶显示器英文 thin film transistor-liquidcrystal display字头的缩写。TFT液晶为每个像素都设有一个薄膜晶体管(TFT),每个像素都可以通过点脉冲直接控制,因而每个节点都相对独立,并可以连续控制,不仅提高了显示屏的反应速度,同时可以精确控制显示色阶。

      本次实践的TFTLCD采用4.3寸模块,其分辨率为480*854,TFTLCD模块采用2*17的2.54 公排针与外部连接,此TFTLCD模块采用16 位的并口方式与外部连接,之所以不采用8位的方式,是因为彩屏的数据量比较大,尤其在显示图片的时候,如果用8位数据线,就会比16 位方式慢一倍以上,所以选择16 位的接口。该模块的80并口有以下信号线:

      CS:TFTLCD片选信号。

      WR:向TFTLCD写入数据控制。

      RD:从TFTLCD读取数据控制。

      RS:命令/数据选择( 0,读写命令;1,读写数据)。

      DB[15: 0]:16 位双向数据线。

      RST:TFTLCD复位。

作用功能:

      TFTLCD模块的RST信号线是直接接到STM32F407的复位脚上的,并不由软件控制。在本次实践中,TFTLCD主要用于显示图片信息和提示信息,以及制作的游戏界面。

2. MPU6050模块

硬件原理:

      陀螺仪是用高速回转体的动量矩敏感壳体相对惯性空间绕正交于自转轴的一个或二个轴的角运动检测装置。

      MPU6050内部整合了3轴陀螺仪和3轴加速度传感器,并且含有一个第二IIC接口,可用于连接外部磁力传感器,并利用自带的数字运动处理器(DMP: DigitalMotion Processor)硬件加速引擎,通过主IIC接口,向应用端输出完整的9轴融合演算数据。

图2 MPU6050三轴角(姿态角)

硬件特点:

      以数字形式输出 6 轴或 9 轴(需外接磁传感器)的旋转矩阵、四元数(quaternion)、欧拉角格式(Euler Angle forma)的融合演算数据(需 DMP 支持)。

      具有 131 LSBs/° /sec 敏感度与全格感测范围为±250、±500、±1000 与±2000° /sec的 3 轴角速度感测器(陀螺仪)。

      集成可程序控制,范围为±2g、±4g、±8g 和±16g 的 3 轴加速度传感器。

      移除加速器与陀螺仪轴间敏感度,降低设定给予的影响与感测器的飘移。

      自带数字运动处理(DMP: Digital Motion Processing)引擎可减少 MCU 复杂的融合演算数据、感测器同步化、姿势感应等的负荷。

引脚说明:

      SCL、SDA:是连接IIC接口,MCU通过这个IIC接口来控制MPU6050,此时MPU6050作为一个IIC从机设备,接单片机的I2C_SCL。

      XCL、XDA:辅助IIC用来连接其他器件,可用来连接外部从设备,比如磁传感器,这样就可以组成一个九轴传感器,不需要连接单片机。

      AD0:地址管脚,可以不接单片机。当MPU6050作为一个IIC从机设备的时候,有8位地址,高7位的地址是固定的,就是WHOAMI寄存器的默认——0x68,最低的一位是由AD0的连线决定的。

      AD0接GND时,高8位的最后一位是0,所以iic从机地址是0x68;

      AD0接VCC时,高8位的最后一位是1,所以iic从机地址是0x69。

      INT:数据输出的中断引脚,可以不接单片机,准备好数据之后,通过中断告诉STM32,从而获取数据。

      VCC:接3.3V或5V电源

      GND:接地

     本次设计中VCC:接5V电源,GND:接地,SCL:主IIC时钟线 (PB10),SDA:主IIC数据线 (PB11),AD0:地址线,接3V地址为0x68,接地地址为0x69(PA15,高电平,地址为0x68)

硬件功能:

      实现对角速度的测量,得到PITCH、ROLL、YAW角,并将其分别作为X、Y、Z轴坐标,在实现贪吃蛇转向时用于判断。

五、软件设计

main.c

设计思路:

       导入库函数,并定义陀螺仪和贪吃蛇相关数据的变量,定义贪吃蛇的相关结构体。对USART1、TFTLCD、MPU6050进行初始化,然后显示游戏主界面。当进入游戏后,每一秒中将调用一次定时中断函数,用于测量陀螺仪的数据,而陀螺仪测量数据同时通过串口传输到电脑,便于观测。进入游戏后,将自动生成食物点,而蛇将会以一定速度向右移动,此时根据获得的陀螺仪数据进行判断。如果pitch即Y轴角度小于-20,那么表示方向向左,大于20表示方向向右,如果roll即X轴角度小于-10,那么表示方向向上,大于10表示方向向下。当蛇进行移动时,不断生成新的蛇身,并用背景色填充原蛇身坐标,以达到移动的效果。而当蛇出了框架边界,则判断为蛇撞墙死亡,生命值清零,初始生命值为4,游戏成绩为0,若蛇头经过蛇身,则判断为蛇咬住自己,生命值将减1。而当蛇吃到了食物,则将蛇身的长度加1,表示身体长长一节,食物被吃掉的瞬间产生新的食物点。

算法思想及操作步骤:
  1. 主函数部分
  • 初始化相关模块,包括SysTick_Init、USART1_Init、TFTLCD_Init、MPU6050_Init、IIC_Init、TIM2_Init。

  • 利用LCD_Clear显示背景颜色,并调用LCD_ShowPicture显示游戏主界面的封面图片,利用LCD_ShowString显示提示信息“Waiting”。

  • 调用RTC_Conifg对RTC进行初始化,并打开时钟,利用RTC_Set_WakeUp (RTC_WakeUpClock_SPRE_16bits,0) 配置WAKE UP中断,即周期性地唤醒定时器,1秒钟中断一次。

  • 再次对背景颜色清屏,并调用show()函数进入游戏界面。show函数中图5 主函数部分程序流程图定义了游戏画面的框架以及成绩生命值,并在进入游戏后显示到TFTLCD屏上。

  • 调用play()函数,即可玩游戏。

图3 主函数部分程序流程图

  1. 中断函数部分
  • 先对陀螺仪相关变量进行定义,包括欧拉角pitch,roll,yaw,分别代表着Y、X、Z轴。

  • 通过usart1_niming_report将数据传送给匿名四轴上位机软件,便于得到姿态图。并通过mpu6050_send_data发送加速度传感器数据和陀螺仪数据,然后通过串口1上报结算后姿态数据给电脑。

  • 当进入定时中断时,即TIM2_IRQHandler(),调用touch()函数。

  • touch()函数用于显示输出陀螺仪数据,因为数据为有符号的浮点数据,在tftlcd上显示时需要将符号分开,单独显示为字符,调用LCD_ShowChar和LCD_ShowNum即可。如果为负数,则显示负号,如果为正数,则直接显示整数和小数部分,小数点单独显示。

图4 中断函数部分程序流程图

  1. 贪吃蛇游戏部分
  • 定义蛇的结构体,包括蛇的坐标(X和Y),蛇的长度,蛇的生命和蛇移动的方向。


        struct Snake
        {
            s16 X[SNAKE_Max_Long];
            s16 Y[SNAKE_Max_Long];
            u8 Long;//蛇的长度
            u8 Life;//蛇的生命值
            u8 Direction;//蛇移动的方向。
        }snake;
  • 定义食物的结构体,包括食物生成点的坐标,食物的标志位yes,当yes为0表示有食物,为1表示无食物需要出现新的食物。


        struct Food
        {
            u8 X;//食物横坐标
            u8 Y;//食物纵坐标
            u8 Yes;//食物标志位
        }food;
  • 定义游戏等级分数结构体,包括游戏的分数和游戏生命标志位,游戏分数初始化为0,游戏生命值初始化为4,生命标志位为1时表示游戏结束。


        struct Game
        {
            u16 Score;//分数
            u8 Life;//生命    
        }game;
  • 初始化蛇的相关变量,包括蛇的长度,蛇的生命标志位,蛇的起始方向向右,初始分数,蛇的生命值,食物的标志位,以及蛇的坐标。

  • 进入while循环中,当出现新的食物,即Yes为1,则在设定的区域内显示食物。添加随机种子,采用的是RTC时钟即srand(rtc_sec)随机生成不超过框架的坐标,生成坐标后将Yes置为0.

  • 再次对食物进行判断,有食物就显示,在TFTLCD上显示为红色填充像素。

  • 对陀螺仪测量的数值进行判断,如果pitch,即Y轴小于-20,则修改蛇的方向:snake.Direction = 2;当pitch大于20,修改方向为:snake.Direction = 1;当roll,即X轴小于-10时,修改方向为:snake.Direction = 3,当roll大于10时,修改方向为:snake.Direction = 4。

  • 判断蛇的运动方向,switch(snake.Direction),为1时表示向右运动,将蛇的坐标snake.X[0]+=12即可;同样为2时表示向左运动,将snake.X[0]-=12;为3时表示向上运动,将snake.Y[0]-=12;为4时表示向下运动,将snake.Y[0]+=12,并通过LCD_Fill填充蛇身像素。

  • 通过判断蛇的坐标是否出框可以判断蛇是否撞墙,如果snake.X[0]<0||snake.X[0]>460||snake.Y[0]<0||snake.Y[0]>500,则将蛇的snake.Life置为1表示死亡。

  • 再次判断蛇的身体,如果自身的任意坐标值与蛇头坐标相等就认为是自身碰撞,蛇的生命值减1,如果蛇的生命标志为1,或者蛇的生命值为0,则说明蛇死亡,调用gameover()函数显示游戏结束。

  • 接着判断蛇是否吃到了食物,即当蛇的坐标与食物坐标重合,则说明吃到食物,消除食物点像素,并加长蛇身,再将食物标志位Yes置1,此时分数值加10,并在TFTLCD上显示生命值和分数值。

图5 贪吃蛇游戏部分程序流程图

main.c详细代码


#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "time.h"
#include "tftlcd.h"
#include "key.h"
#include "snake.h"
#include "rtc.h"
#include "stdlib.h"
#include "mpu6050.h"
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h" 

#define SNAKE_Max_Long 300//蛇的最大长度 
u8 pause=0;
u8 start=0;


u8 j=0;
    //u8 key;
    u8 report=1;
    float pitch,roll,yaw;         //欧拉角
    short aacx,aacy,aacz;        //加速度传感器原始数据
    short gyrox,gyroy,gyroz;    //陀螺仪原始数据
    short temp;                    //温度
    u8 res;
    
void usart1_send_char(u8 c)
{
    while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); 
    USART_SendData(USART1,c);   

} 

//´传送数据给匿名四轴上位机软件
//fun:功能字
//data:数据缓存区
//len:data区有效数据的个数
void usart1_niming_report(u8 fun,u8*data,u8 len)
{
    u8 send_buf[32];
    u8 i;
    if(len>28)return;    
    send_buf[len+3]=0;    
    send_buf[0]=0X88;    ·
    send_buf[1]=fun;    
    send_buf[2]=len;
    for(i=0;i<len;i++)send_buf[3+i]=data[i];            //¸
    for(i=0;i<len+3;i++)send_buf[len+3]+=send_buf[i];    //    
    for(i=0;i<len+4;i++)usart1_send_char(send_buf[i]);    //·发送数据到串口1 
}


//·发送加速度传感器数据和陀螺仪数据
//aacx,aacy,aacz:x,y,z三个方向上面的加速度值
//gyrox,gyroy,gyroz:x,y,z三个方向上面的陀螺仪值
void mpu6050_send_data(short aacx,short aacy,short aacz,short gyrox,short gyroy,short gyroz)
{
    u8 tbuf[12]; 
    tbuf[0]=(aacx>>8)&0XFF;
    tbuf[1]=aacx&0XFF;
    tbuf[2]=(aacy>>8)&0XFF;
    tbuf[3]=aacy&0XFF;
    tbuf[4]=(aacz>>8)&0XFF;
    tbuf[5]=aacz&0XFF; 
    tbuf[6]=(gyrox>>8)&0XFF;
    tbuf[7]=gyrox&0XFF;
    tbuf[8]=(gyroy>>8)&0XFF;
    tbuf[9]=gyroy&0XFF;
    tbuf[10]=(gyroz>>8)&0XFF;
    tbuf[11]=gyroz&0XFF;
    usart1_niming_report(0XA1,tbuf,12);
}    

void usart1_report_imu(short aacx,short aacy,short aacz,short gyrox,short gyroy,short gyroz,short roll,short pitch,short yaw)
{
    u8 tbuf[28]; 
    u8 i;
    for(i=0;i<28;i++)tbuf[i]=0;//清0
    tbuf[0]=(aacx>>8)&0XFF;
    tbuf[1]=aacx&0XFF;
    tbuf[2]=(aacy>>8)&0XFF;
    tbuf[3]=aacy&0XFF;
    tbuf[4]=(aacz>>8)&0XFF;
    tbuf[5]=aacz&0XFF; 
    tbuf[6]=(gyrox>>8)&0XFF;
    tbuf[7]=gyrox&0XFF;
    tbuf[8]=(gyroy>>8)&0XFF;
    tbuf[9]=gyroy&0XFF;
    tbuf[10]=(gyroz>>8)&0XFF;
    tbuf[11]=gyroz&0XFF;    
    tbuf[18]=(roll>>8)&0XFF;
    tbuf[19]=roll&0XFF;
    tbuf[20]=(pitch>>8)&0XFF;
    tbuf[21]=pitch&0XFF;
    tbuf[22]=(yaw>>8)&0XFF;
    tbuf[23]=yaw&0XFF;
    usart1_niming_report(0XAF,tbuf,28);
} 
//蛇结构体
struct Snake
{
    s16 X[SNAKE_Max_Long];
    s16 Y[SNAKE_Max_Long];
    u8 Long;//蛇长度
    u8 Life;//蛇生命标志位
    u8 Direction;//移动方向
}snake;

//食物结构体
struct Food
{
    u8 X;//横坐标
    u8 Y;//纵坐标
    u8 Yes;//标志位
}food;

//游戏等级分数
struct Game
{
    u16 Score;//·分数
    u8 Life;//生命值    
}game;


void gameover()
{
    start=0;//停止游戏·
    FRONT_COLOR=RED;
    LCD_ShowString(180,120,tftlcd_data.width,tftlcd_data.height,24,"GAME OVER!");
    /*
    Test_Show_CH_Font24(160,65,0,RED);        
    Test_Show_CH_Font24(190,65,1,RED);        
    Test_Show_CH_Font24(220,65,2,RED);        
    Test_Show_CH_Font24(250,65,3,RED);        
    Test_Show_CH_Font24(280,65,4,RED);
*/    
    //FRONT_COLOR=BLACK;
    BACK_COLOR=0xFD0F;
    LCD_ShowString(386,520,tftlcd_data.width,tftlcd_data.height,24,"0");    
}
void touch(void)
{    
        if(mpu_dmp_get_data(&pitch,&roll,&yaw)==0)
        { 
          temp=MPU6050_Get_Temperature();    
            MPU6050_Get_Accelerometer(&aacx,&aacy,&aacz);    //得到加速度传感器数据
            MPU6050_Get_Gyroscope(&gyrox,&gyroy,&gyroz);    //得到陀螺仪数据
            if(report)mpu6050_send_data(aacx,aacy,aacz,gyrox,gyroy,gyroz);
            if(report)usart1_report_imu(aacx,aacy,aacz,gyrox,gyroy,gyroz,(int)(roll*100),(int)(pitch*100),(int)(yaw*10));
            if((j%10)==0)
                { 
                if(temp<0)
                    {
                    LCD_ShowChar(320+48,650,'-',16,0);        //显示负号
                    temp=-temp;        //转为正数
                    }else LCD_ShowChar(320+48,650,' ',16,0);        //去掉负数 
                    printf("检测温度为:%d度\r\n",temp);
                    LCD_ShowNum(320+48+8,650,temp/100,3,16);        //显示整数部分    
                    LCD_ShowNum(320+48+40,650,temp%10,1,16);        //显示小数部分 
                    temp=pitch*10;
                    if(temp<0)
                    {
                        LCD_ShowChar(320+48,670,'-',16,0);        
                        temp=-temp;        
                    }else LCD_ShowChar(320+48,670,' ',16,0);         
                    printf("Pitch£º%d\r\n",temp);
                    LCD_ShowNum(320+48+8,670,temp/10,3,16);            
                    LCD_ShowNum(320+48+40,670,temp%10,1,16);        / 
                    temp=roll*10;
                    if(temp<0)
                    {
                        LCD_ShowChar(320+48,690,'-',16,0);        
                        temp=-temp;        
                    }else LCD_ShowChar(320+48,690,' ',16,0);         
                    printf("Roll£º%d\r\n",temp);
                    LCD_ShowNum(320+48+8,690,temp/10,3,16);        
                    LCD_ShowNum(320+48+40,690,temp%10,1,16);         
                    temp=yaw*10;
                    if(temp<0)
                    {
                        LCD_ShowChar(320+48,710,'-',16,0);        
                        temp=-temp;        
                    }else LCD_ShowChar(320+48,710,' ',16,0);         
                    printf("Yaw£º%d\r\n",temp);
                    LCD_ShowNum(320+48+8,710,temp/10,3,16);        
                    LCD_ShowNum(320+48+40,710,temp%10,1,16);          
                        
                }
            }            
        j++;
            
}
void TIM2_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM2,TIM_IT_Update)!= RESET)
    {
        touch();
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);    
    }
}  

u16 i,n;4
u8 life_buf[2];
u8 socre_buf[4];
int b;
//ÍæÓÎÏ·
void play()
{
    
    
    FRONT_COLOR=BLACK;
    res=mpu_dmp_init();

    while(mpu_dmp_init())
    {

        LCD_ShowString(280,600,tftlcd_data.width,tftlcd_data.height,16,"MPU6050 Error!");
        delay_ms(200);
    }
    
    LCD_ShowString(60,600,tftlcd_data.width,tftlcd_data.height,16,"Made by: No.Zero");
       
    LCD_ShowString(280,600,tftlcd_data.width,tftlcd_data.height,16,"MPU6050 OK!");
    LCD_ShowString(280,620,tftlcd_data.width,tftlcd_data.height,16,"K_UP:UPLOAD ON/OFF");
    FRONT_COLOR=0x18CE 
  
     LCD_ShowString(280,650,tftlcd_data.width,tftlcd_data.height,16," Temp:         .  C");    
     LCD_ShowString(280,670,tftlcd_data.width,tftlcd_data.height,16,"Pitch(Y):      .  C");    
     LCD_ShowString(280,690,tftlcd_data.width,tftlcd_data.height,16," Roll(X):      .  C");     
     LCD_ShowString(280,710,tftlcd_data.width,tftlcd_data.height,16," Yaw (Z):      .  C");    
    
    
    printf("MPU6050 OK!\r\n");

    snake.Long=3;
    snake.Life=0;//Éß»¹»î×Å
    snake.Direction=1;
    game.Score=0;
    game.Life=4;
    food.Yes=1;
    snake.X[0]=12;snake.Y[0]=24;
    snake.X[1]=12;snake.Y[1]=24;

    while(1)
    {
        //key=KEY_Scan(0);
    //    if(key==KEY_UP)
        /*
      {
            report=!report;
            if(report)LCD_ShowString(10,170,tftlcd_data.width,tftlcd_data.height,16,"UPLOAD ON ");
            else LCD_ShowString(10,170,tftlcd_data.width,tftlcd_data.height,16,"UPLOAD OFF");
        }
        */
        
        
            if(food.Yes==1)
            {
                while(1)
                {
                        //在设定的区域内        
                        //food.X=12+rand()%(240/12)*12;
                        //food.Y=12+rand()%(160/12)*12;
                        //srand(rtc_sec);
                      srand(b);
                      b++;
                        if(b>300)
                            b=0;
                        food.X=(12+(rand()%19)*12);
                        food.Y=(12+(rand()%20)*12);
                    //    LCD_ShowString(100,450,tftlcd_data.width,tftlcd_data.height,16,(u8 *)food.X);//ÏÔʾ³É¼
                    //    LCD_ShowString(140,450,tftlcd_data.width,tftlcd_data.height,16,(u8 *)food.Y);
                        for(n=0;n<snake.Long;n++)
                        {
                            if(food.X==snake.X[n]&&food.Y==snake.Y[n])
                                break;
                        }
                        if(n==snake.Long)
                        food.Yes=0;    
                        break;
                    }
            }
                
                if(food.Yes==0)//有食物就显示
                {    
                    LCD_Fill(food.X,food.Y,food.X+10,food.Y+10,RED);
                }
                //取得需要重新画的蛇的节数
                for(i=snake.Long-1;i>0;i--)
                {
                    snake.X[i]=snake.X[i-1];
                    snake.Y[i]=snake.Y[i-1];
                }
                
                if (pitch < -20)//向左
                            {
                                snake.Direction = 2;
                
                            }
                            else if (pitch > 20)//向右
                            {
                                snake.Direction = 1;
                    
                            }
                            else if (roll < -10)//向上
                            {
                                snake.Direction = 3;
                            
                            }
                            else if (roll > 10)//向下
                            {
                                snake.Direction = 4;
                            }
                
                switch(snake.Direction)
                {
                    case 1:snake.X[0]+=12;break;//向右运动
                    case 2:snake.X[0]-=12;break;//向左运动
                    case 3:snake.Y[0]-=12;break;//向上运动
                    case 4:snake.Y[0]+=12;break;//向下运动¯
                }
                for(i=0;i<snake.Long;i++)
                LCD_Fill(snake.X[i],snake.Y[i],snake.X[i]+10,snake.Y[i]+10,0x18CE);//画出蛇
                while(pause==1){};
                delay_ms(2000);
                LCD_Fill(snake.X[snake.Long-1],snake.Y[snake.Long-1],snake.X[snake.Long-1]+10,snake.Y[snake.Long-1]+10,0xFD0F);//Ïû³ýÉßÉí        
                        
                    
                //判断是否撞墙
                if(snake.X[0]<0||snake.X[0]>460||snake.Y[0]<0||snake.Y[0]>500)
                    snake.Life=1;//蛇死掉了
        
                //当蛇的身体超过3节后判断蛇自身的碰撞
                for(i=3;i<snake.Long;i++)
                {
                    if(snake.X[i]==snake.X[0]&&snake.Y[i]==snake.Y[0])//自身的任一坐标值与蛇头坐标相等就认为是自身碰撞
                        game.Life-=1;
                }
                if(snake.Life==1||game.Life==0)//以上两种判断以后如果蛇死掉了跳出内循环,重新开始
                {
                    gameover();
                    break;
                }    
                //判断蛇是否吃到了食物
                if(snake.X[0]==food.X&&snake.Y[0]==food.Y)
                {
                    LCD_Fill(food.X,food.Y,food.X+10,food.Y+10,0xFD0F);//把吃到的食物消除
                    if(!((snake.Long==SNAKE_Max_Long)&&(snake.Long==SNAKE_Max_Long)))
                    snake.Long++;//蛇的身体长一节
                    game.Score+=10;
                    socre_buf[0]=game.Score/100+0x30;
                    socre_buf[1]=game.Score%100/10+0x30;
                    socre_buf[2]=game.Score%100%10+0x30;
                    socre_buf[3]='\0';
                    FRONT_COLOR=BLACK;
                    BACK_COLOR=0xFD0F;
        
                    LCD_ShowString(156,520,tftlcd_data.width,tftlcd_data.height,24,socre_buf);//Ï显示成绩¨    
                    food.Yes=1;//需要重新显示食物
                }
                life_buf[0]=game.Life%10+0x30;
                life_buf[1]='\0';
            
                LCD_ShowString(386,520,tftlcd_data.width,tftlcd_data.height,24,life_buf);//显示生命值    
        }    
}


int main()
{    

    SysTick_Init(168);
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组,分2组
    //LED_Init();
    USART1_Init(9600);
    TFTLCD_Init();            //TFTLCD初始化    
    KEY_Init();
    MPU6050_Init();                    //初始化MPU6050
    
    LCD_Clear(0x18CE);
    LCD_ShowPicture(0, 0, 240, 400);
  LCD_ShowString(200,500,tftlcd_data.width,tftlcd_data.height,24,"Waiting...");    
    delay_ms(3000);
    TIM2_Init(50,8399);
    RTC_Config();
    RTC_Set_WakeUp(RTC_WakeUpClock_CK_SPRE_16bits,0);//配置WAKE UP中断,1秒钟中断一次
  LCD_Clear(0xFD0F);
    show();//玩游戏界面
    play();//玩游戏·
}

六、效果展示

      进入游戏主界面:(游戏主界面的图片有些小,所以可以自己重新导入开发板对应尺寸的图片哈,我绝对不会承认是因为我懒才没有换的,好叭,其实是因为在TFTLCD上面,图片都是以像素点着色来进行显示的,所以你除非能将一整张图片全部转换为对应颜色的像素点,否则很难在开发板上显示图片,就这左上角一小块就有三万行的颜色代码,你敢信

      进入游戏界面,晃动开发板,改变贪吃蛇方向,右下角显示陀螺仪信息:

      当蛇撞上自身,生命值减1:

      当蛇撞上框架,游戏结束:

七、设计总结

      基于STM32F407单片机和陀螺仪设计的贪吃蛇游戏,可以充分发挥单片机的性能,带来实际操作的游戏体验感,比按键更胜一筹。在本次设计中,我完成了各种模块的功能结合,比如TFTLCD的显示,需要显示图片信息和各种提示信息;比如在游戏的过程中需要时刻判断下一次用户的操作方向,所以用上了time定时中断;比如在游戏中生成食物也需要随机数,用到了rtc时钟;比如外接设备陀螺仪与开发板之间的数据传输需要通过I2C来进行,保证数据的可靠性,而在电脑端查看陀螺仪的姿态图形也需要串口来实现信息交互。

      同时,贪吃蛇游戏程序本身也需要我不断地去思考其中的游戏逻辑和思路,生成蛇和食物、蛇吃食物会变长、蛇撞到自己会减生命值、蛇撞墙会死亡等,我完善了整个小游戏的流程,在设计过程中,我也遇到了很多问题,但最终通过不断的尝试和分析,查找各种可以参考的解决办法和资料,最终都得以解决。

      比如在进行TFTLCD显示时,程序没有报错但在开发板上无法得到有效显示,在进行反复研究后,发现tftlcd.c文件函数不全面,而ustart.h文件需要进行更改,同时system.h文件需要加上开启中断和关闭中断的程序,而且对于keil5软件,需要在设置环境中将C/C++目录下的C99 Mode模式开启,才能有效在开发板上进行TFTLCD的显示。

      而在编写贪吃蛇小游戏时,需要使用陀螺仪的测量数据来进行方向的判断,但是如果只是将用于获取陀螺仪测量数据的代码放在判断方向的函数中时,小游戏只能进行一次对陀螺仪的判断,一旦蛇改变方向后将无法再次获得新的测量数据。当时将该程序加上while(1)的循环,并通过break试图跳出该循环,但结果是延迟太长,且跳出循环后无法再进入循环,仍然不能得到时刻的测量数据。因为测量数据是需要实时得到的,所以在经过进一步思考后,我认为测量数据其实是不用每时每刻都需要对它进行获取,只要没隔一小段时间获取即可,所以采用了定时中断,每隔一秒获取一次测量数据,解决了转向的问题。

八、一些废话

      这个设计是我在一年前做的了,所以现在重新进行总结可能有些细节已经记不清了,(๑ó﹏ò๑),而且程序本身功能也没那么多,比如还可以加入音效、困难度选择模式、游戏控制按键等。害,第一次写博客,哪里有错欢迎指出或者私我哈,求大佬指点(ฅ•﹏•ฅ)。。。

  • 8
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布零酱

阿里嘎多!谢谢打赏嗷喵~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值