欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验

程序员文章站 2022-07-04 18:51:52
...
使用了STM32CubeMX及Keil(HAL库)
材料:stm32开发板、0.96寸OLED模块(ssd1306 12864 SPI)、4×4矩阵键盘、杜邦线、st-link


实验原理:

1、 OLED通过SPI2显示游戏画面(使用了某商家驱动,包括oled.c、oled.h、oledfont.h)

2、 矩阵键盘实现按键事件判断(只用矩阵键盘行2、行3)
S11(上:w) S12( 下:s) S8( 左:a) S16( 右:d) S4( 确认:o)

3、贪吃蛇游戏的原理是普通的贪吃蛇游戏的算法

实验设计:


PA4 PA5 PA6 PA7输出低电平 
PC4 PC5为下降沿触发外部中断,并且设置为上拉   
一行中有按键按下则触发中断,进行消抖,然后把一行中4个按键对应的PA依次置为高电平,检测4个按键中哪个按键被按下

具体按图连接
用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验

OLED按照下图连

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验








关于游戏:

蛇的最大长度为36个方块


关于贪吃蛇游戏画面:
1、 标题界面(实现在play和help之间循环选择)
左边64×64部分为标题,右边64×64部分为菜单
2、 帮助界面
提供帮助信息
3、 游戏界面
左边64×64部分为游戏显示,右边64×64部分为分数显示
左边64×64部分留了宽度为4像素的空白,以及宽度为4像素的围墙

蛇由方块(8×8像素,填充部分为6×6像素)组成,撑满围墙内需要36块食物为一个方块(8×8像素,填充部分为2×2像素)


关于这个的贪吃蛇游戏的规则:

初始长度为3,方向为右
每秒移动1个长度单位(匀速)
通过按键,方向可以改变为蛇前进方向的左边或右边(有bug,在往新方向移动之前)
随机生成食物
吃到食物长度+1
碰到墙壁或身体结束游戏(失败)
【长度达到最长长度结束游戏(成功?)


实验过程:
1、测试OLED基本功能

商家给的驱动没有图片显示,于是写了个简单粗暴的图片显示函数,定义在oled.c里,顺便在oled.h中声明(图片取模后就存在一个数组里。。。我放在了oledfont.h里)

//画图

void OLED_PIC1616(const unsigned char pic[][16]){
	int i,j,k,temp;
	for(i=0;i<64;i++){
		for(j=0;j<16;j++){
			temp=pic[i][j];
			for(k=0;k<8;k++){
				OLED_DrawPoint(j*8+k,i,temp&0x80); //写好的设置单个像素是否填充的函数
				temp=temp<<1;

			}
		}
	}
	
}


图片取模方式如下(至于阴码、阳码,根据具体的效果来选):

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验


原图(阳码):

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验


显示效果:

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验
2、测试矩阵键盘的功能
出现了按键时的抖动、释放可能多次触发了中断的问题

3、写代码测试

修复了一些bug







STM32CubeMX中的设置:

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验

用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验




主要代码(以下只有USER CODE BEGIN里的代码):
/* USER CODE BEGIN Includes */
#include "oled.h"
#include <stdlib.h>
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
extern const unsigned char pic1[][16]; //title图片
extern const unsigned char pic2[][16]; //lose图片
extern const unsigned char pic3[][16]; //win图片
const uint16_t gpio[]={GPIO_PIN_4,GPIO_PIN_5,GPIO_PIN_6,GPIO_PIN_7};
const char key5[]={'d','s','a','o'}; //d:right a:left w:up s:down o:ok
uint8_t scene=0; //0:title 1:help 2:game
uint8_t select=0; //0:run game 1:run help
uint8_t length=3; //蛇的长度
//以下坐标都是方块左上角坐标
uint8_t head_x=32; //头部方块x坐标
uint8_t head_y=24; //头部方块y坐标
uint8_t snake_x[36]={32,24,16}; //身体方块x坐标
uint8_t snake_y[36]={24,24,24}; //身体方块y坐标
uint8_t direction='d'; //方向
uint8_t food=1;//判断食物存在
uint8_t food_x;//食物x坐标
uint8_t food_y;//食物y坐标
char score[3];//分数(长度)【用于打印】
/* USER CODE END PV */

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void draw_food(void);
void draw_score(void);
void generateFood(void);
void snake_init(void);
uint8_t game(void);
void init_title(void);
/* USER CODE END PFP */

 /* USER CODE BEGIN 2 */
	__HAL_SPI_ENABLE(&hspi2);
	OLED_Init();//初始化OLED      
	srand(1);
	init_title();
  /* USER CODE END 2 */

/* USER CODE BEGIN 3 */
		
 if(scene==2){
	uint8_t flag;
	 snake_init();
	 while(1){            //游戏循环
		 HAL_Delay(1000);
		 flag=game();
		 if(flag==0){
			 OLED_PIC1616(pic2);
			 HAL_Delay(5000);
			 init_title();
			 scene=0;
			 break;
		 }
			else if(flag==2){
				OLED_PIC1616(pic3);
				HAL_Delay(5000);
				init_title();
				scene=0;
				break;
			 }
		 
		 
	 }
 }
  }
  /* USER CODE END 3 */

/* USER CODE BEGIN 4 */
//画出标题界面
void init_title(void){  
	OLED_Clear();
	OLED_PIC1616(pic1);
	OLED_ShowString(64,4,"play",16);
	OLED_ShowString(64,24,"help",16);
	OLED_ShowString(104,20*select+4,"<-",16);
}



//画食物
void draw_food(void){
	
	OLED_Fill(food_x+3,food_y+3,food_x+5,food_y+5,1);
}




//写分数
void draw_score(void){
		sprintf(score,"%d",length);
		OLED_ShowString(64,24,score,16);
}


//生成食物坐标
void generateFood(void){
	int i;
	do{
		food_x=8*(rand()%6)+8;
		food_y=8*(rand()%6)+8;
		for(i=0;i<length;i++){
				if(food_x==snake_x[i]&&food_y==snake_y[i]){
					break;
				}
		}
	}while(i<length);
}


//进入游戏进行初始化
void snake_init(void){
	int i;
	length=3; 
	head_x=32; 
	head_y=24; 
	snake_x[0]=32; 
	snake_x[1]=24;
	snake_x[2]=16;
	snake_y[0]=24;
	snake_y[1]=24;
	snake_y[2]=24;
	direction='d';
	generateFood();
	food=1;
	OLED_Clear();
	OLED_Fill(4,4,60,60,1);
	OLED_Fill(8,8,56,56,0);
	
	for(i=0;i<length;i++){
		OLED_Fill(snake_x[i]+1,snake_y[i]+1,snake_x[i]+6,snake_y[i]+6,1);
	}
	draw_food();
	OLED_ShowString(64,4,"length:",16);
	draw_score();
}


//游戏循环
uint8_t game(void){ 
	int i;
	//更改头部坐标
	switch(direction){
		case 'w':head_y-=8;break;
		case 's':head_y+=8;break;
		case 'd':head_x+=8;break;
		case 'a':head_x-=8;break;
	}
		
	//撞墙判断
	if(head_x==0||head_x==56||head_y==0||head_y==56){
		return 0;
	}
	
	//更改身体坐标
	for(i=length-1;i>=0;i--){
		snake_x[i+1]=snake_x[i];
		snake_y[i+1]=snake_y[i];
	}
		snake_x[0]=head_x;
		snake_y[0]=head_y;
	
	//撞身判断
	for(i=1;i<length;i++){
		if(head_x==snake_x[i]&&head_y==snake_y[i]){
			return 0;
		}
	}
	
	
	//吃判断
	if(head_x==food_x&&head_y==food_y){
		food=0;
		length++;
		if(length==36){ //长度最大,结束游戏
			return 2;
		}
		draw_score();
	}
	else{//若没有吃到,就消去最后一个方块
		OLED_Fill(snake_x[length],snake_y[length],snake_x[length]+8,snake_y[length]+8,0);
	}
	OLED_Fill(head_x+1,head_y+1,head_x+6,head_y+6,1); //画头
	
	
	//是否需要生成食物
	if(food==0){
		generateFood();
		draw_food();
		food=1;
	}
	
	
	return 1;
}


void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
	UNUSED(GPIO_Pin);
	char key='n';
	//确定键值
	if(GPIO_Pin == GPIO_PIN_4){
		HAL_Delay(50);
		if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_4) == 0){
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
				if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_4) == 1){
					key='w';
				}
				HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);	
				while(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_4) == 0);
		}
		
	}
	else{
			HAL_Delay(50);
			if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5) == 0){
				int i;
				for(i=0;i<4;i++){
					HAL_GPIO_WritePin(GPIOA,gpio[i],GPIO_PIN_SET);
					if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5) == 1){
						key=key5[i];
						HAL_GPIO_WritePin(GPIOA,gpio[i],GPIO_PIN_RESET);
						break;
					}
					HAL_GPIO_WritePin(GPIOA,gpio[i],GPIO_PIN_RESET);
				}
				while(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5) == 0);
			}
		}
	
		//根据界面进行不同的操作
		switch(scene){
			case 0://标题界面
				if(key=='w'||key=='s'){//选择
					OLED_Fill(104,20*select+4,120,20*select+20,0);
					if(select){select=0;}
					else{select=1;}
					OLED_ShowString(104,20*select+4,"<-",16);
				}
				if(key=='o'){//确认
					if(select){
						scene=1;
						OLED_Clear();
						OLED_ShowString(0,0,"normal play...",16);
					}
					else{
						scene=2;
					}
				}
				break;
			case 1://帮助界面
				if(key=='o'){//确认
					scene=0;
					init_title();
				}
				break;
			case 2://游戏界面
				switch(direction){//改方向
					case 'w':
					case 's':
						if(key=='a'||key=='d'){
							direction=key;
						}
						break;
					case 'a':
					case 'd':
						if(key=='w'||key=='s'){
							direction=key;
						}
						break;
				}
				break;
		}
		
}
/* USER CODE END 4 */


实验成果:




用STM32F103RCT6+0.96寸OLED模块(ssd1306 12864 SPI)+4×4矩阵键盘实现贪吃蛇游戏的实验
(↑还没赢过。。。)


基本实现了游戏功能,然而这个游戏还是有bug的(比如长按按键可以暂停、可以快速掉头进行自杀),暂时没改。





参考资料:普通的贪吃蛇游戏算法 http://blog.csdn.net/fengsser/article/details/8474331
相关标签: stm32