基於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;
}
最後是顯示效果圖