基于SCT89C52的贪吃蛇游戏制作
本文主要描述各个模块的软件原理部分,开发板电路部分略微提及。
下载链接:https://download.csdn.net/download/xiaoshixiu/11813752
一、 控制器
-
红外发射管和红外接收管
红外通信是指以红外线作为载波的通信,我们常可以在遥控器的顶端看到红外二极管,也就是上图。如果晶振频率较高,有时候需要对晶振分频后由红外发射器发出。一般红外频率为38KHZ。
二进制信号调制方式有多种,包括PCM脉冲编码调制,PWM脉冲宽度调制,PPM脉冲位置调制。
红外接收器目前基本都已经将接收、放大、限幅、检波、整形等功能集成到一个器件中,如下图。三个脚分别为电源正、电源负、数据输出。
-
红外接收器的数据输出端接到MCU的数据接收端口,MCU需要解析二进制信号,也就是一段区分遥控器按键的二进制信号。具体数据格式以及各段时长如下:
对于单一的遥控器而言,可以仅仅使用数据码来区分不同的按键,使用数据反码来进行校验,数据反码就是数据码取反。 -
为了准确接收红外信号,需要对各个部分时长做范围检查,起始码是一段9ms的高电平加上4.5ms的低电平/而后续的每一个数据包括一个低电平和一个高电平,如下图,数据0的低电平持续0.56ms,高电平大约0.56ms。数据1的低电平持续0.56ms,高电平持续1.69ms。
- 软件部分如下。
void onInt0() interrupt 0
{
int threshold=0;
int i=0;
int times=0;
delay(500);//5ms
//判断起始码低电平是否持续9ms
if(p32==1){
return;
}
//10ms持续监测起始码低电平
threshold=1000;
while(p32==0 && threshold!=0){
delay(1);
threshold--;
};
if(threshold==0){
return;
}
//5.5ms持续监测起始码高电平
threshold=550;
while(p32==1 && threshold!=0){
delay(1);
threshold--;
};
if(threshold==0){
return;
}
//receive data code
//后续32位数据进行32次循环
for(i=0;i<32;i++)
{
//0.6ms监测数据位的低电平
threshold=60;
while(p32==0 && threshold>0){
delay(1);
threshold--;
}
//根据高电平时长判断是位0还是位1,并把数据保存到数组infra_data中
threshold=100;
times=0;
while(p32==1 && threshold!=0){
delay(10);
threshold--;
times++;
}
if(threshold==0){
return;
}
if(times>=8){
infra_data[i/8]|=0x01;
}
if((i+1)%8 != 0)
{
infra_data[i/8]<<=1;
}
}
if(infra_data[2]!=~infra_data[3])
{
return;
}
}
二、 显示器
1 二极管阵列
贪吃蛇的显示采用8X8二极管阵列来显示。二极管的驱动采用74HC595进行数据位输入,位选信号则是直接使用MCU的P0输出端口。595的电路图和位选电路图如下所示。595是一个串行输入转并行输出的器件,可以将一段串行连续输入的数据转为并行输出。驱动方式主要通过11/12/14口。14口表示串行数据输入,11口表示移位寄存器输入,也就是输入并保存一位数据,需要注意11口和14口同步,当11口持续一定时间(通常几十微妙)高电平,14端口的数据会保存到一位寄存器中。持续8次后8位移位寄存器将保存好8位数据,然后12端口持续一个高电平和一个低电平,会使移位寄存器的数据输出到存储寄存器中,也就是通过并行输出。
2 软件部分如下
sbit _RCLK=P3^5;
sbit _SRCLK=P3^6;
sbit _SER=P3^4;
void init595(void)
{
//上升沿有效
_RCLK=0;
_SRCLK=0;
}
void _595out(unsigned char out)
{
int i=0;
for(i=0;i<8;i++)
{
_SER=out>>7;//右移7位,高位数据赋值
out<<=1;//左移一位,下次循环保存的将是第二位数据
_SRCLK=1;//移位寄存器保存数据
_nop_;
_SRCLK=0;
}
_RCLK=1;//存储寄存器保存数据
_nop_;
_RCLK=0;
}
三、 贪吃蛇逻辑
此部分属于纯软件部分,但也是最复杂的部分。下面直接把所有代码进行解释。
#include"reg52.h"
#include"intrins.h"
#define NUM_1 0x30
#define NUM_2 0x18
#define NUM_3 0x7A
#define NUM_4 0x10
#define NUM_5 0x38
#define NUM_6 0x5A
#define NUM_7 0x42
#define NUM_8 0x4A
#define NUM_9 0x52
typedef unsigned char u8;
sbit p32=P3^2;
u8 infra_data[4]={0};
sbit _RCLK=P3^5;
sbit _SRCLK=P3^6;
sbit _SER=P3^4;
static int panel_index=0;
unsigned char random_seed=0;
unsigned char isGameOver=0;
void endGame(){
EA=0;
}
void init595(void)
{
//上升沿有效
_RCLK=0;
_SRCLK=0;
}
void initTimer()
{
//IE
EA=1;
ET0=1;
//TCON
TR0=1;
//TMOD
TMOD|=0x01;
TH0=0xFC;
TL0=0x18;
}
void delay(int size) //10us
{
while(size--);
}
void displayInMills(int ms)
{
int i=0;
int j=0;
for(j=0;j<ms;j++)
{
while(i!=100)
{
i++;
}
i=0;
}
}
void _595out(unsigned char out)
{
int i=0;
for(i=0;i<8;i++)
{
_SER=out>>7;
out<<=1;
_SRCLK=1;
_nop_;
_SRCLK=0;
}
_RCLK=1;
_nop_;
_RCLK=0;
}
//采用Window思想,不能每次只显示一条蛇,而是需要将所有蛇的像素拼接到二极管阵列panel中,然后采用类似垂直扫描的思想每次显示一行,循环8次,将8x8都显示出来。
void _displaypanel(unsigned char *_coord)
{
int i=0;
for(i=0;i<8;i++)
{
P0= 0xff - (0x01<<i) ;
_595out(_coord[i]);
displayInMills(1);
_595out(0); //重要,消除影响
}
}
unsigned char snakePanel[8]={0x00,0x00,0x00,0,0,0,0,0}; //表示led阵列,可表示64个点的情况
#define SNAKE_LENGTH 8
unsigned char snakePos[SNAKE_LENGTH]={0x13,0x12,0x11,0,0,0,0,0};//表示坐标轴,每个元素表示坐标位置,高位行,低位列。蛇长最高为8
//unsigned char robotSnakePos[SNAKE_LENGTH]={0,0,0x33,0x32,0x31,0,0,0};
unsigned char robotSnakePos[SNAKE_LENGTH]={0x73,0x72,0x71,0,0,0,0,0};
//d_snake是否包含d_pos,判断是否碰撞
unsigned char isSnakeCrash(unsigned char d_pos,unsigned char *d_snake){
int i=0;
for(i=0;i<8;i++){
if(d_snake[i]!=0 && d_snake[i]==d_pos){
return 1;
}
}
return 0;
}
unsigned char getSnakeLength(unsigned char *snake){
int i=0;
unsigned char length=0;
for(i=0;i<8;i++){
if(snake[i]==0){
break;
}
length++;
}
return length;
}
//1:add success,0:game over and success,2:cross board
unsigned char addSnakeLength(unsigned char *snake,unsigned char size)
{
int i=0;
unsigned char length=0;
length=getSnakeLength(snake);
if(SNAKE_LENGTH<(i+size)){
return 0;
}
//由于硬件限制,增长受到限制
for(i=0;i<size;i++){
if(length>=3)
{
// bit is equal
//需要判断贪吃蛇与边界垂直时,增加长度会增长到阵列外
if(snake[length-1+i]>>4==snake[length-2+i]>>4){
if(snake[length-1+i]%16 ==0 || snake[length-1+i]%16 ==0xf)
{
return 2;
}
else{
if(snake[length-1+i]<snake[length-2+i]){
snake[length+i]=snake[length-1+i]-1;
}
else{
snake[length+i]=snake[length-1+i]+1;
}
}
}
else{
if(snake[length-1+i]>>4 ==0 || snake[length-1+i]>>4 ==0xf)
{
return 2;
}
else{
if(snake[length-1+i]<snake[length-2+i]){
snake[length+i]=snake[length-1+i]-0x10;
}
else{
snake[length+i]=snake[length-1+i]+0x10;
}
}
}
}
}
return 1;
}
void initSnakePanel()
{
int i=0;
for(i=0;i<8;i++)
{
snakePanel[i]=0;
}
}
void appendSnakePosToPanel(unsigned char *snake)
{
int i=0;
unsigned char high=0,low=0;
for(i=0;i<SNAKE_LENGTH;i++)
{
low=snake[i] & 0x0F;
high=snake[i] & 0xF0;
if(snake[i]!=0)
{
snakePanel[low-1]|= 0x01<<((high>>4) - 1);
}
}
}
enum action{
action_null=-1,
up,
right,
down,
left,
};
char randomAction(){
return random_seed%4;
}
void resetUserSnake(unsigned char *snake)
{
snake[0]=0x13;
snake[1]=0x12;
snake[2]=0x11;
snake[3]=0;
snake[4]=0;
snake[5]=0;
snake[6]=0;
snake[7]=0;
}
//当贪吃蛇碰撞时进行重置
void resetRobotSnake(unsigned char *snake)
{
snake[0]=0x73;
snake[1]=0x72;
snake[2]=0x71;
snake[3]=0;
snake[4]=0;
snake[5]=0;
snake[6]=0;
snake[7]=0;
}
unsigned char isUser=0;
//贪吃蛇左移
//贪吃蛇的移动需要判断是否是用户控制的贪吃蛇还是电脑生成的贪吃蛇,用于判断胜负
void onSnakeUp(unsigned char *snake)
{
int i=0;
unsigned char *other_snake;
if(isUser){
other_snake=robotSnakePos;
}
else{
other_snake=snakePos;
}
for(i=SNAKE_LENGTH-1;i>=0;i--)
{
if(i==0)
{
if(snake[i]>>4 == 0x8 || isSnakeCrash(snake[i]+0x10,other_snake))
{
if(isSnakeCrash(snake[i]+0x10,other_snake))
{
//碰撞时直接增加被碰撞的蛇的长度,这里省略了被撞蛇去“吃“的玩法
if(addSnakeLength(other_snake,getSnakeLength(snake))==0){
isGameOver=1;
endGame();
return;
}
}
if(isUser)
{
resetUserSnake(snake);
return;
}
else
{
resetRobotSnake(snake);
return;
}
}
snake[i]+=0x10;
}
else
{
if(snake[i]!=0)
snake[i]=snake[i-1];
}
}
}
void onSnakeRight(unsigned char *snake)
{
int i=0;
unsigned char *other_snake;
if(isUser){
other_snake=robotSnakePos;
}
else{
other_snake=snakePos;
}
for(i=SNAKE_LENGTH-1;i>=0;i--)
{
if(i==0)
{
if(snake[i]%16 == 0 || isSnakeCrash(snake[i]-0x01,other_snake))
{
if(isSnakeCrash(snake[i]-0x01,other_snake))
{
if(addSnakeLength(other_snake,getSnakeLength(snake))==0){
isGameOver=1;
endGame();
return;
}
}
if(isUser)
{
resetUserSnake(snake);
return;
}
else
{
resetRobotSnake(snake);
return;
}
}
snake[i]-=0x01;
}
else
{
if(snake[i]!=0)
snake[i]=snake[i-1];
}
}
}
void onSnakeDown(unsigned char *snake)
{
char i=0;
char ret=0;
unsigned char *other_snake;
if(isUser){
other_snake=robotSnakePos;
}
else{
other_snake=snakePos;
}
for(i=SNAKE_LENGTH-1;i>=0;i--)
{
if(i==0)
{
if(snake[i]>>4 == 0 || isSnakeCrash(snake[i]-0x10,other_snake))
{
if(isSnakeCrash(snake[i]-0x10,other_snake))
{
if(addSnakeLength(other_snake,getSnakeLength(snake))==0){
isGameOver=1;
endGame();
return;
}
}
if(isUser)
{
resetUserSnake(snake);
return;
}
else
{
resetRobotSnake(snake);
return;
}
}
snake[i]-=0x10;
}
else
{
if(snake[i]!=0)
snake[i]=snake[i-1];
}
}
}
void onSnakeLeft(unsigned char *snake)
{
int i=0;
unsigned char *other_snake;
if(isUser){
other_snake=robotSnakePos;
}
else{
other_snake=snakePos;
}
for(i=SNAKE_LENGTH-1;i>=0;i--)
{
if(i==0)
{
if(snake[i]%16 == 8 || isSnakeCrash(snake[i]+0x01,other_snake))
{
if(isSnakeCrash(snake[i]+0x01,other_snake))
{
if(addSnakeLength(other_snake,getSnakeLength(snake))==0){
isGameOver=1;
endGame();
return;
}
}
if(isUser)
{
resetUserSnake(snake);
return;
}
else
{
resetRobotSnake(snake);
return;
}
}
snake[i]+=0x01;
}
else
{
if(snake[i]!=0)
snake[i]=snake[i-1];
}
}
}
enum action ir_to_action()
{
switch(infra_data[2]){
case NUM_2:
return up;
case NUM_6:
return right;
case NUM_8:
return down;
case NUM_4:
return left;
default:
return action_null;
}
return action_null;
}
void onTimer()
{
initSnakePanel();
isUser=1;
switch(ir_to_action())
{
case up:
//onUp();
onSnakeUp(snakePos);
break;
case right:
//onRight();
onSnakeRight(snakePos);
break;
case down:
//onDown();
onSnakeDown(snakePos);
break;
case left:
//onLeft();
onSnakeLeft(snakePos);
break;
default:
//
break;
}
isUser=0;
switch(randomAction())
{
case up:
//onUp();
onSnakeUp(robotSnakePos);
break;
case right:
//onRight();
onSnakeRight(robotSnakePos);
break;
case down:
//onDown();
onSnakeDown(robotSnakePos);
break;
case left:
//onLeft();
onSnakeLeft(robotSnakePos);
break;
default:
//
break;
}
appendSnakePosToPanel(robotSnakePos);
appendSnakePosToPanel(snakePos);
}
//由于生成的电脑需要产生随机动作,但是C51本身并没有产生随机值得方法,手动生成一个随机值至少需要一个随机种子,这里的随机种子直接使用定时器的值。可能并不是完全随机的。
void random_seed_change()
{
if(random_seed!=0xff){
random_seed++;
}else{
random_seed=0;
}
}
//0.5s timer
void onInterrupt() interrupt 1
{
static int i=0;
TH0=0xFC;
TL0=0x18;
i++;
if(i==500)
{
onTimer();
i=0;
}
random_seed_change();
}
//int 0
/*now test infra*/
void initInt0()
{
EA=1;
EX0=1;
IT0=1;
p32=1;
infra_data[2]=NUM_4;
}
//IR
void onInt0() interrupt 0
{
int threshold=0;
int i=0;
int times=0;
delay(500);
if(p32==1){
return;
}
threshold=1000;
while(p32==0 && threshold!=0){
delay(1);
threshold--;
};
if(threshold==0){
return;
}
threshold=550;
while(p32==1 && threshold!=0){
delay(1);
threshold--;
};
if(threshold==0){
return;
}
//receive data code
for(i=0;i<32;i++)
{
threshold=60;
while(p32==0 && threshold>0){
delay(1);
threshold--;
}
//if(threshold==0){
// return;
//}
threshold=100;
times=0;
while(p32==1 && threshold!=0){
delay(10);
threshold--;
times++;
}
if(threshold==0){
return;
}
if(times>=8){
infra_data[i/8]|=0x01;
}
if((i+1)%8 != 0)
{
infra_data[i/8]<<=1;
}
}
if(infra_data[2]!=~infra_data[3])
{
return;
}
}
int main(void)
{
init595();
initTimer();
initInt0();
while(1)
{
_displaypanel(snakePanel);
random_seed_change();
}
return;
}
最后是显示效果图