本实验实现了一个打地鼠小游戏。
电路连接
代码
//ch451.h
#ifndef __CH451_H__
#define __CH451_H__
#include<reg51.h>
#define CH451_RESET 0x0201 //复位
#define CH451_LEFTMOV 0x0300 //设置移动方式-作移
#define CH451_LEFTCYC 0x0301 //设置移动方式-左循
#define CH451_RIGHTMOV 0x0302 //设置移动方式-右移
#define CH451_RIGHTCYC 0x0303 //设置移动方式-右循
#define CH451_SYSOFF 0x0400 //关显示、键盘、看门狗
#define CH451_SYSON1 0x0401 //开显示
#define CH451_SYSON2 0x0403 //开显示、键盘
#define CH451_SYSON3 0x0407 //开显示、键盘、看门狗功能
#define CH451_DSP 0x0500 //设置默认显示方式
#define CH451_BCD 0x0580 //设置BCD译码方式
#define CH451_TWINKLE 0x0600 //设置闪烁控制
#define CH451_DIG0 0x0800 //数码管位0显示
#define CH451_DIG1 0x0900 //数码管位1显示
#define CH451_DIG2 0x0a00 //数码管位2显示
#define CH451_DIG3 0x0b00 //数码管位3显示
#define CH451_DIG4 0x0c00 //数码管位4显示
#define CH451_DIG5 0x0d00 //数码管位5显示
#define CH451_DIG6 0x0e00 //数码管位6显示
#define CH451_DIG7 0x0f00 //数码管位7显示
#define USE_KEY 1//如果使用键盘中断请定义
sbit ch451_load = P1^3; //串行命令加载,上升延激活
sbit ch451_din = P1^1; // 串行数据输出,接CH451的数据输入
sbit ch451_dclk = P1^2; //串行数据时钟上升延激活
sbit ch451_dout = P3^2; //INT0对应P3.2,键盘中断和键值数据输入,接CH451的数据输出
extern unsigned char ch451_key;// 全局变量,存放键盘中断中读取的键值
void ch451_init();
void ch451_write(unsigned int command);
unsigned char ch451_read();
void ch451_inter();
#endif
//ch451.c
#include "ch451.h"
//初始化子程序
void ch451_init()
{
ch451_din=0; //先低后高,选择4线输入
ch451_din=1;
#ifdef USE_KEY
IT1=0; //设置下降沿触发
IE1=0; //清中断标志
PX1=0; //设置低优先级
EX1=1; //开中断
#endif
}
//*****************************************************
//输出命令子程序
//定义一无符号整型变量存储12字节的命令字。
void ch451_write(unsigned int command)
{
unsigned char i;
#ifdef USE_KEY
EX1=0; //禁止键盘中断
#endif
ch451_load=0; //命令开始
for(i=0;i<12;i++){ //送入12位数据,低位在前
ch451_din=command&1;
ch451_dclk=0;
command>>=1;
ch451_dclk=1; //上升沿有效
}
ch451_load=1; //加载数据
#ifdef USE_KEY
EX1=1;
#endif
}
#ifdef USE_KEY
//*************************************************
//输入命令子程序,MCU从451读一字节
unsigned char ch451_read()
{
unsigned char i;
unsigned char command,keycode; //定义命令字,和数据存储器
EX1=0; //关中段
command=0x07; //输入读451命令字
ch451_load=0;
for(i=0;i<4;i++){
ch451_din=command&1; //送入最低位
ch451_dclk=0;
command>>=1; //往右移一位
ch451_dclk=1; //产生时钟上升沿锁通知CH451输入位数据
}
ch451_load=1; //产生加载上升沿通知CH451处理命令数据
keycode=0; //清除keycode
for(i=0;i<7;i++){
keycode<<=1; //数据移入keycode,高位在前,低位在后
keycode|=ch451_dout; //从高到低读入451的数据
ch451_dclk=0; //产生时钟下升沿通知CH451输出下一位
ch451_dclk=1;
}
IE1=0; //清中断标志
EX1=1;
return(keycode); //反回键值
}
//*************************************************
//中断子程序 使用中断0
void ch451_inter() interrupt 0//外部中断0(INT0)对应interrupt 0 ,外部中断1(INT1)对应interrupt 2
{
unsigned char i; //定义循环变量
unsigned char command,keycode; //定义控制字寄存器,和中间变量定时器
command=0x07; //读取键值命令的高4位0111B
ch451_load=0; //命令开始
for(i=0;i<4;i++){
ch451_din=command&1; //低位在前,高位在后
ch451_dclk=0;
command>>=1; //右移一位
ch451_dclk=1; //产生时钟上升沿锁通知CH451输入位数据
}
ch451_load=1; //产生加载上升沿通知CH451处理命令数据
keycode=0; //清除keycode
for(i=0;i<7;i++){
keycode<<=1; //数据作移一位,高位在前,低位在后
keycode|=ch451_dout; //从高到低读入451的数据
ch451_dclk=0; //产生时钟下升沿通知CH451输出下一位
ch451_dclk=1;
}
ch451_key=keycode; //保存上次的键值,读取的按键代码与按键键值的关系需要测试
IE0=0; //清中断标志
}
//***********************************************
#endif
//lcd12864.h
#ifndef _LCD12864_h_
#define _LCD12864_h_
/**************************************************************
iO口宏定义区
***************************************************************/
sbit CS =P0^0;//对应实验箱上IO1 RS
sbit SID=P0^1;//对应实验箱上IO2 RW
sbit SCK=P0^2;//对应实验箱上IO3 E
void write_command( unsigned char Cbyte ); //写入指令函数
void write_data( unsigned char Dbyte ); //写入指令数据
void lcd_init( void ); //显示屏初始化
void lcd_clear_txt( void ); //显示屏清屏
void location_xy_12864(unsigned char x,unsigned char y);
void put_str(unsigned char row,unsigned char col,unsigned char *puts);
void put_char(unsigned char row,unsigned char col,unsigned char put);
void lcd_display_picture(unsigned char p[][16]);
#endif
//lcd12864.c
#include <reg51.h>
#include <intrins.h>
#include "lcd12864.h"
/**************************************************************
//串行方式控制
/*******************************************************************
常量声明区
********************************************************************/
unsigned char code AC_TABLE[]={ //坐标编码
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
};
/****************************************************************
发送一个字节
*****************************************************************/
void send_byte(unsigned char dbyte)
{
unsigned char i;
for(i=0;i<8;i++)
{
SCK = 0;
dbyte=dbyte<<1;
SID = CY;
SCK = 1;
SCK = 0;
}
}
/**********************************************************
接收一个字节
***********************************************************/
unsigned char receive_byte(void)
{
unsigned char i,temp1,temp2;
temp1=temp2=0;
for(i=0;i<8;i++)
{
temp1=temp1<<1;
SCK = 0;
SCK = 1;
SCK = 0;
if(SID) temp1++;
}
for(i=0;i<8;i++)
{
temp2=temp2<<1;
SCK = 0;
SCK = 1;
SCK = 0;
if(SID) temp2++;
}
return ((0xf0&temp1)+(0x0f&temp2));
}
/****************************************************************
检查忙状态
******************************************************************/
void check_busy( void )
{
do send_byte(0xfc); //11111,RW(1),RS(0),0
while(0x80&receive_byte());
}
/******************************************************************
写一个字节的指令
*******************************************************************/
void write_command( unsigned char Cbyte )
{
CS = 1;
check_busy();
send_byte(0xf8); //11111,RW(0),RS(0),0
send_byte(0xf0&Cbyte);
send_byte(0xf0&Cbyte<<4);
CS = 0;
}
/*************************************************************
写一个字节的数据
**************************************************************/
void write_data( unsigned char dbyte )
{
CS = 1;
check_busy();
send_byte(0xfa); //11111,RW(0),RS(1),0
send_byte(0xf0&dbyte);
send_byte(0xf0&dbyte<<4);
CS = 0;
}
/******************************************************************
lcd初始化函数
*******************************************************************/
void lcd_init( void )
{
write_command(0x30);
write_command(0x03);
write_command(0x0c);
write_command(0x01);
write_command(0x06);
}
/*******************************************************************************************************
设定光标函数
********************************************************************************************************/
void location_xy_12864(unsigned char x,unsigned char y)
{
switch(x)
{
case 0:
x=0x80;break;
case 1:
x=0x90;break;
case 2:
x=0x88;break;
case 3:
x=0x98;break;
default:
x=0x80;
}
y=y&0x07;
write_command(0x30);
write_command(y+x);
write_command(y+x);
}
/***********************************************************************************
清除文本
************************************************************************************/
void lcd_clear_txt( void )
{
unsigned char i;
write_command(0x30);
write_command(0x80);
for(i=0;i<64;i++)
write_data(0x20);
location_xy_12864(0,0);
}
/****************************************************************************************
显示字符串
*****************************************************************************************/
void put_str(unsigned char row,unsigned char col,unsigned char *puts)
{
write_command(0x30);
write_command(AC_TABLE[8*row+col]);
while(*puts != '\0')
{
if(col==8)
{
col=0;
row++;
}
if(row==4) row=0;
write_command(AC_TABLE[8*row+col]);
write_data(*puts);
puts++;
if(*puts != '\0')
{
write_data(*puts);
puts++;
col++;
}
}
}
/****************************************************************************************
显示字符
*****************************************************************************************/
void put_char(unsigned char row,unsigned char col,unsigned char put)
{
write_command(0x30);
write_command(AC_TABLE[8*row+col]);
write_data(put);
}
/****************************************************************************************
显示图片
*****************************************************************************************/
void lcd_display_picture(unsigned char p[][16])
{
unsigned char x,y,a,b,c;
write_command(0x34); // رջ ͼ
x = 0x80;
y = 0x80;
for(c=0;c<2;c++)//先画上半屏,再画下半屏
{
for(a=0;a<32;a++)
{
write_command(y+a);
write_command(x);
for(b=0;b<16;b++)
write_data(p[a+c*32][b]);
}
x=0x88;
}
write_command(0x36); // ͼ
write_command(0x30);
}
//main.c
/********************************************************************************************************
打地鼠游戏
********************************************************************************************************/
#include < reg51.h >
#include < stdlib.h >
#include <stdio.h>
#include "ch451.h"
#include "lcd12864.h"
unsigned char key_value = 0; //存放按键值
unsigned char free_num= 0; //随机数的变量
unsigned char flag_1s;//达到1s的标志
char space[5]={0x20,0x20,0x20,0x20,'\0'};//空格串
int sec=20;//秒数
char s_sec[3];//将秒数转换成为字符串
int score=0;//分数
char s_score[5];//将分数转换成为字符串
enum GAME_STATE{PLAY,OVER};
enum GAME_STATE game_state=OVER;
/********************************************************************************************************
游戏开始时或者每次打死地鼠之后,需要刷新地鼠的位置
********************************************************************************************************/
void refresh() {
//关闭敲死的地鼠
put_str(0,0,"田");
put_str(0,1,"田");
put_str(0,2,"田");
put_str(0,3,"田");
put_str(1,0,"田");
put_str(1,1,"田");
put_str(1,2,"田");
put_str(1,3,"田");
put_str(2,0,"田");
put_str(2,1,"田");
put_str(2,2,"田");
put_str(2,3,"田");
switch(free_num){
case 0:put_str(0,0,"鼠"); break;
case 1:put_str(0,1,"鼠"); break;
case 2:put_str(0,2,"鼠"); break;
case 3:put_str(0,3,"鼠"); break;
case 4:put_str(1,0,"鼠"); break;
case 5:put_str(1,1,"鼠"); break;
case 6:put_str(1,2,"鼠"); break;
case 7:put_str(1,3,"鼠"); break;
case 8:put_str(2,0,"鼠"); break;
case 9:put_str(2,1,"鼠"); break;
case 10:put_str(2,2,"鼠"); break;
case 11:put_str(2,3,"鼠"); break;
default:break;
}
}
/********************************************************************************************************
游戏状态机
********************************************************************************************************/
void game_state_machine(unsigned char key){
switch(game_state){
case OVER:
if(key==12){//开始键
game_state=PLAY;//修改状态为PLAY
//每次开始后重新加载界面
lcd_clear_txt();
put_str(0,0,"田");
put_str(0,1,"田");
put_str(0,2,"田");
put_str(0,3,"田");
put_str(1,0,"田");
put_str(1,1,"田");
put_str(1,2,"田");
put_str(1,3,"田");
put_str(2,0,"田");
put_str(2,1,"田");
put_str(2,2,"田");
put_str(2,3,"田");
put_str(3,0,"开始");
put_str(3,3,"分数");
put_str(0,5,"倒计时");
refresh(); //刷新free_num对应的地鼠
TMOD=0x01;//计时器0和计时器1均为方式1
TH0=0x4b;
TL0=0x83;// (65536-19331)/(0.9241M)=0.05s,19331=0x4b83 ,20次中断为1秒
ET0=1;
TR0=1;//启用定时器0
sec=20;//20秒倒计时
}
break;
case PLAY:
if(key==15){//结束码
char end[10];
game_state=OVER;
lcd_clear_txt();
put_str(1,2, "游戏结束");
sprintf(end,"分数%d!",score);
put_str(2,2,end);
put_str(3,0,"继续");
score=0;
ET0=0;
TR0=0;//关闭定时器0
}else if(key==free_num){
//新生成一个随机数
unsigned char temp=(unsigned char)(rand() % 12); //生成一个0到11之间的随机数
while(free_num==temp)temp=(unsigned char)(rand() % 12);
free_num=temp;
refresh(); //刷新free_num对应的地鼠
put_str(3,5,space);//暂时清除分数
score++;//打到相应的地鼠,分数加一
sprintf(s_score,"%d",score);
put_str(3,5,s_score);
}else{
put_str(3,5,space);//暂时清除分数
score--;//没有打到相应的地鼠,分数减一
sprintf(s_score,"%d",score);
put_str(3,5,s_score);
}
break;
}
return;
}
/**************************************************
转换子程序:将中断获取的按键代码转换为键值
**************************************************/
unsigned char tran(unsigned char key){
key&=0x3f;//只保留低六位
if (key<4)return key;
else if(key<12)return key-4;
else if(key<20)return key-8;
else if(key<28)return key-12;
}
/*********************************************************
定时器T0中断
*********************************************************/
void InterruptTimer0() interrupt 1
{
TH0=0x4b;
TL0=0x83;
flag_1s++;
if(flag_1s>19)
{
flag_1s=0; //清0
sec--;
put_str(1,5,space);//先清为空串
sprintf(s_sec,"%d",sec);
put_str(1,5,s_sec);
if(sec==0)game_state_machine(15);//给游戏状态机发送结束码
}
}
/*********************************************************
主函数
*********************************************************/
void main() {
IE=0x81;//EA 空 空 ES ET1 EX1 ET0 EX0
TCON=0x01;//TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0(IT0=1表示外部中断0位边沿触发)
lcd_init();
ch451_init(); //初始化
ch451_write(0x402);//设定系统参数:0100000[CKHF][DPLR][WDOG][KEYB][DISP]B,键盘有效,关闭显示
ch451_write(0x580);//580H=010110000000B,右数第八位表示BCD译码方式
lcd_clear_txt();
put_str(0,0,"田");
put_str(0,1,"田");
put_str(0,2,"田");
put_str(0,3,"田");
put_str(1,0,"田");
put_str(1,1,"田");
put_str(1,2,"田");
put_str(1,3,"田");
put_str(2,0,"田");
put_str(2,1,"田");
put_str(2,2,"田");
put_str(2,3,"田");
put_str(3,0,"开始");
ch451_key=0xff;
while (1) {
if(ch451_key!=0xff){
key_value=ch451_key;
key_value=tran(key_value);
game_state_machine(key_value);
ch451_key=0xff;
}
}
}
运行结果演示