单片机贪食蛇课程设计报告

单片机课程设计报告

目录

1设计任务及要求... 1

2总体设计思路及功能描述... 1

3  各部分软硬件设计原理及方案详细说明... 2

3.1 人机接口电路... 2

3.2单片机与PC机通信电路... 4

3.3 其他部分电路说明... 5

3.4 软件模块设计... 5

3.4.1 LCD初始化... 5

3.4.2 键盘扫描程序... 7

3.4.3 显示16*16点阵汉字... 7

3.4.4  食物的随机出现... 7

3.4.5     8X8 点阵LED工作原理说明... 8

3.4.6          锁存器(74HC573)... 8

3.5软件编译... 10

4  调试的步骤及调试过程中出现的问题以及解决方法... 10

4.1 PROTEUS仿真... 10

4.2  硬件的安装... 10

4.3  调试注意事项... 11

4.3.1  硬件调试注意事项... 11

4.3.2  软件调试注意事项... 11

5 设计心得体会... 11

6 附录... 12

6.1总原理图... 12

6.2单片机程序代码... 13

7 参考文献... 22


正文:

1设计任务及要求

本设计以51系列单片机STC89C52为控制核心,以点阵液晶显示模块、键盘为人机接口,实现了一个贪食蛇游戏机。通过本设计,令读者掌握利用单片机开发简单电子产品的基本技能,熟悉原理图绘制、仿真、软件设计、优化以及系统调试的基本方法,为进一步设计开发更为复杂的嵌入式模拟/数字混合系统打下一定的基础。

“贪食蛇”又称为“贪吃蛇”,是一种益智小游戏。其游戏规则比较简单,就是一条小蛇,不停地在屏幕上游走去吃屏幕上出现的蛋,越吃越长,只要蛇头碰到屏幕四周或者碰到自己的身子,小蛇就立即毙命并结束游戏。本作品有上下左右四个按键来控制蛇头的移动方向,另有一个复位按键控制程序的重启,

游戏界面方案一:采用分辨率为128×64的液晶显示屏

方案二:和8*8点阵显示。

2总体设计思路及功能描述

如图ChpNum-3所示,贪食蛇软件主要分成三个部分:主程序、外部中断服务程序、定时中断服务程序。主程序的作用是一些初始化工作及蛇体动作执行、食物的随机产生、得分累计、图像显示等。外部中断服务程序的功能是识别按键。定时中断服务程序的作用是定时产生步进信号。外部中断服务程序与主程序之间联系的纽带是全局变量MovDirection,键盘中断服务程序每次执行都要把按键对应的方向更新到此变量,而主程序每次步进方向都以此变量为依据。定时中断服务程序通过全局变量IsToStep与主程序联系起来,主程序只有在IsToStep为1时才让蛇体步进,且步进后将该变量置0,定时中断服务程序每隔一段时间为IsToStep置位,使主程序得到步进信号。

图ChpNum-3 程序流程

主程序首先进行LCD和定时器的初始化,绘制好游戏界面后打开外部中断并启动定时器,进入主循环。主循环等待蛇体步进信号IsToStep(由定时中断服务程序设置),得到步进信号后根据当前方向MovDirection控制蛇体向前步进。步进后判断当前蛇头是否碰到食物,若碰到,将食物与蛇体合并,并产生新的食物再进入首身相碰判断;若未碰到食物,直接进入首身相碰判断。若首身未相碰则将IsToStep清零、更新得分后回到主循环;否则退出游戏。

定时器中断服务程序作用是定时产生步进信号,因硬件定时最大值不够蛇体步进最小间隔时间,我们用多次硬件定时来产生一个步进信号。设计全局变量p,每硬件中断触发一次p减1。当p减到0时置IsToStep为1并对p重新赋值。值得注意的是,p的重新赋值应参考蛇体长度,若蛇体长度越长p值应越小,即步进间隔越短,蛇体移动速度越快,游戏难度越大。具体做法见参考代码。

外部中断服务程序用于按键识别并更新前进方向全局变量MovDirection。首先取出键盘检测位的值再确定贪食蛇要改变的方向。当贪食蛇正向上或向下移动时,按下上下方向键,键值都不进行处理;而贪食蛇正向左或向右移动时,按下左右方向键,键值都不进行处理。

3  各部分软硬件设计原理及方案详细说明

3.1 人机接口电路

本游戏机游戏界面由液晶显示模块呈现。液晶显示模块中,最主要的就是LCD液晶屏。根据LCD液晶屏显示内容的不同,液晶显示模块可以分为数显液晶模块、点阵字符液晶模块和点阵图形液晶模块3种。本设计使用点阵图形液晶模块OCM12864。OCM12864液晶显示模块是128×64点阵型液晶显示模块,可显示各种字符及图形,可与CPU直接连接,具有8位标准数据总线、6条控制线及电源线,各引脚的信号说明参见表ChpNum-1。

表ChpNum-1 OCM12864引脚说明

    单片机与液晶显示器的连接电路如图ChpNum-1所示,片选信号CS1与CS2接P2.4和P2.3引脚,RS、R/W、E分别接引脚P2.2、P2.1、P2.0。VEE驱动负电压输出,而V0接10K电位器,对LCD亮度进行调节。51单片机的P0口为了实现准3态,采用了OC输出,也就是集电极悬空输出。这种电路结构,只有下拉能力,高电平输出没有电流。在高电平时表现为高阻态,加上拉电阻,就会失去高阻态,变成 1、0 两态。因此在P0口与OCM12864的I/O口之间接上10K的排阻。

用户通过四个按键控制蛇头的上下左右移动, 接法如图ChpNum-1所示。四个按键尾端均接地,任意按键的按下均会使与门输出由高电平变成低电平,从而触发中断。此时,中断服务程序通过检测P2.6口与P2.7口的电平可以识别是哪个按键被按下。若P2.6和P2.7同时为低电平,表明是UP键按下;若同时为高电平,表明DOWN键按下;若P2.6为高,P2.7为低则表明是LEFT键被按下;若P2.6为低,P2.7为高则表明RIGHT键被按下。

游戏音效由蜂鸣器实现,接法如图ChpNum-1所示。由于单片机输出脚的驱动电流有限,我们用单片机P2.5脚输出信号控制PNP三极管S8550的通断,当P2.5输出高电平时,三极管截止,当P2.5输出低电平时,三极管导通。因此,通过在P2.5脚输出不同频率的方波信号,可使得蜂鸣器发出不同频率的声音。

图ChpNum-1 人机接口电路

3.2单片机与PC机通信电路

本设计采用的STC89C52单片机具备ISP下载功能,无需用到编程器,编译好的单片机程序可以通过串口下载到单片机。单片机与PC机串行通信采用RS-232C标准。因为RS-232C接口信号不是标准的TTL电平,单片机与PC机通过RS-232C串口进行通信时,必须进行电平转换。虽然可以用分立元件组成RS-232C与TTL电平之间的转换电路,但是现在更多的是使用集成电路来完成,这里使用MAX232集成电路。如图ChpNum-2所示,MAX232需要外接4只0.1uF电容,或者1uF的电解电容。之所以需要电容,是因为RS-232电平是工作在大约-9V~+9V之间,需要电容将5V电压转换成RS-232电平需要的+10V和-10V。单片机侧安装了DB9物理连接器,可直接通过串口线连接到PC的串口。

图ChpNum-2 RS-232信号适配电路

3.3 其他部分电路说明

电源电路、复位和时钟电路比较简单,具体连接请参见电路原理总图。

供电部分采用7805将输入9~12V电压转换为5V输出,输入输出端接220uF电解电容,使整流后的脉动直流电压变成相对比较稳定的直流电压。由于大容量的电解电容一般具有一定的电感,对高频及脉冲干扰信号不能有效地滤除,所以两端并联一只容量为0.1uF的电容,以滤除高频及脉冲干扰。

复位电路含有上电复位和按键复位功能,按键复位用于游戏结束后游戏的重新启动。系统插电瞬间电源通过10K电阻给10uF电容充电电平,充电电流在10K电阻上产生压降,于是电阻上端产生一个维持几百毫秒的高电平输入到RESET对单片机进行复位,电容充电满后经过电阻电流为0,复位失效。类似地,当开关按下时,强制产生一个高电平对单片机进行复位。

时钟电路使用12MHz的晶振,晶振的两引脚处接入两个22pF的瓷片电容来削减谐波对电路稳定性的影响。

3.4 软件模块设计

3.4.1 LCD初始化

    在对LCD进行初始化时,先要检查读忙碌标志位,当BF为1表示内部操作正在进行,0表示允许指令操作。具体指令说明如下:

(1)  显示开/关设置

功能:设置屏幕显示开/关。DB0=H,开显示;DB0=L,关显示。不影响显示RAM(DD RAM)中的内容。

(2)  设置显示起始行

功能:执行该命令后,所设置的行将显示在屏幕的第一行。显示起始行是由Z地址计数器控制的,该命令自动将A0-A5位地址送入Z地址计数器,起始地址可以是0-63范围内任意一行。Z地址计数器具有循环计数功能,用于显示行扫描同步,当扫描完一行后自动加一。

(3)  设置页地址

功能:执行本指令后,下面的读写操作将在指定页内,直到重新设置。页地址就是DD RAM 的行地址,页地址存储在X地址计数器中,A2-A0可表示8页,读写数据对页地址没有影响,除本指令可改变页地址外,复位信号(RST)可把页地址计数器内容清零。

(4)  设置列地址

功能: DD RAM 的列地址存储在Y地址计数器中,读写数据对列地址有影响,在对DD RAM进行读写操作后,Y地址自动加1。

(5)  状态检测

功能:读忙信号标志位(BF)、复位标志位(RST)以及显示状态位(ON/OFF)。  BF=H:内部正在执行操作;BF=L:空闲状态。RST=H:正处于复位初始化状态;RST=L:正常状态。ON/OFF=H:表示显示关闭;ON/OFF=L:表示显示开。

(6)  写显示数据

功能:写数据到DD RAM,DD RAM是存储图形显示数据的,写指令执行后Y地址计数器自动加1。D7-D0位数据为1表示显示,数据为0表示不显示。写数据到DD RAM前,要先执行“设置页地址”及“设置列地址”命令。

(7)  读显示数据

功能:从DD RAM读数据,读指令执行后Y地址计数器自动加1。从DD RAM读数据前要先执行“设置页地址” 及“设置列地址”命令.

LCD初始化时,根据指令格式,先进行读忙碌检查。当BF为低电平时,设置显示起始行为第一行,A0~A5位为0,然后设置屏幕显示为开,具体做法见参考代码。

3.4.2 键盘扫描程序

键盘扫描通过外部中断来检测,四个方向键经与门与P3.2(中断输入)引脚相连。程序初始化时将IT0置0,表示外部中断使用电平触发方式,低电平可引起中断。任何键的按下均会使P3.2电平为低,进入中断服务程序。中断服务程序先将P2口数据取出,右移6位将键盘接入引脚(P2.7、P2.6)的值取出,得到键盘识别码。键盘识别码有四个:1(左键)、2(右键)、0(上键)、3(下键)。得到按键识别码后根据识别码及当前方向更新前进方向。

3.4.3 显示16*16点阵汉字

液晶显示器的数据线是8位的,显示数据是逐字节输入的。每字节输入数据对应到一列的8个像素点(8行)。自然地,64行像素点分成8页,每8行为一页。向液晶显示器写入显示数据时均要先设置写入的页号和列号(而不是具体某一个像素点的坐标),然后写入一个字节的数据。如表ChpNum-2所示,液晶显示模块中的DDRAM是存储图形显示数据的,LCD显示的数据与DDRAM中的数据一一对应(一位DDRAM对应一个像素,数据为1表显示该点,为0不显示)。汉字在液晶中占用16*16像素点,即两页16列。把汉字的点阵数据写到液晶的DDRAM中时。先载入第一页的16列(字节),再载第2页的16列。详细过程见参考代码。

表ChpNum-2 DDRAM地址映像

Y 地址

3.4.4  食物的随机出现

    食物的出现是一种随机行为,所以必须做一个随机数,而且食物出现的位置不能与蛇的位置相同,也不能超出墙外,否则就要重置食物。这里使用程序中的定时计数器的低八位TL0的数值,由于TL0不断变化,不同的时间点数值不同,TL0%24及TL0%15分别为食物的列地址和行地址。

3.4.5      8X8 点阵LED工作原理说明

8X8点阵LED结构如下图所示

从上图中可以看出,8X8点阵共需要64个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上,当对应的某一列置1电平,某一行置0电平,则相应的二极管就亮;因此要实现一根柱形的亮法,如图49所示,对应的一列为一根竖柱,或者对应的一行为一根横柱,因此实现柱的亮的方法如下所述:

一根竖柱:对应的列置1,而行则采用扫描的方法来实现。

一根横柱:对应的行置0,而列则采用扫描的方法来实现

3.4.6      锁存器(74HC573)

锁存器74HC573的输出端为Q0~Q7 可直接与总线相连。当三态允许控制端 OE 为低电平时,Q0~Q7 为正常逻辑状态,可用来驱动负载或总线。当OE 为高电平时,Q0~Q7 呈高阻态,即不驱动总线,也不为总线的负载,但锁存器内部的逻辑操作不受影响。当锁存允许端 LE 为高电平时,Q 随数据D 而变。当LE 为低电平时,O 被锁存在已建立的数据电平。 输出能直接接到CMOS,NMOS 和TTL 接口上。

操作电压范围:2.0V~6.0V
低输入电流:1.0uA
CMOS 器件的高噪声抵抗特性

引出端符号:

D0~D7     数据输入端

OE         三态允许控制端(低电平有效)

LE         锁存允许端

Q0~Q7     输出端 
外部管脚图、逻辑图如图1.3和如图1.4:

  图1.3  74LS373引脚图                                图1.4   74LS373逻辑图

真值表如表1.1所示:

3.5软件编译

    程序调试使用uVision3,此在软件中要求每个工程都要建立一个文件来存储相关信息,因此不论是汇编的,还是C语言的,不论是只有一个文件的还是有多个文件的程序都需要一个工程文件。这里使用C语言编写,具体步骤如下:

(1)打开uVision3后,打开Project菜单,在弹出的下拉菜单中选择New Project命令,选择保存路径,输入工程名“贪食蛇”,最后单击“保存”按钮。在保存新建工程后要求选择所使用单片机的型号,在此选择Atmel的80C52单片机。

(2)建立起一个新工程后,接下来是新建程序及各种文件,保存程序文件后需要将其添加文件到工程中。

(3)至此就可以进行源程序编写,完成编写后就对程序进行编译及仿真。通过单击工具栏图标Build Target,可以翻译所有源文件并生成应用。当Build应用存在语法错误时,uVision3将会在Output Window-Build页显示错误和警示信息。双击信息行,uVision3在编辑器窗口打开此信息对应的源文件,并定位到相应的位置。一旦成功地生成了应用程序,就可以开始调试了。在调试好应用程序后,要求生成一个Intel HEX文件。这个文件可以下载到EPROM编程器中。

4  调试的步骤及调试过程中出现的问题以及解决方法

4.1 PROTEUS仿真

在用uVision3编写单片机程序时,因uVision3往往只能修改语法上的错误,对于算法上的问题不好检查,而直接下到单片机里又受电路板的限制而不方便调试,因此这里使用Proteus进行电路仿真。该软件具有模拟电路仿真、数字电路仿真、单片机及其外围电路组成的系统仿真、RS232动态仿真、I2C调试器、SPI调试器、键盘和LCD系统仿真的功能,同时有各种虚拟仪器,如示波器、逻辑分析仪、信号发生器等。

    在Proteus软件的ISIS中新建设计图,画出本设计的电路图。电路设计完成后就可以进行仿真。先双击单片机,把用uVision3编译生成的HEX文件指定为下载文件,点击PLAY键即可进行仿真。当出现ANALYSER ERRORS时,表示电路有错误,列表中说明了具体的错误,必须要先排错才可以进行仿真。

4.2  硬件的安装

    软件调试及Proteus仿真完成后就进行硬件的安装。读者可以自己印制PCB进行设计,亦可在万能板上搭建。安装时要考虑受热、稳固等多方面的影响。比如使用电烙铁时要控制好焊接的时间,电烙铁停留的时间太短,焊锡不易完全熔化,形成“虚焊”,而焊接时间太长又容易损坏元器件,每一两秒内要焊好一个焊点,若没完成,宁愿等一会儿再焊一次。其次芯片的摆置要方便连线,焊接时要先把芯片从插座拔出,等线接好了再插上去。在焊接时要考虑电路的抗干扰能力,同时要充分考虑电源对单片机的影响。每焊接完一个模块,要用万能表根据电路图检查有没有接错、短路等现象,确认正确后再继续下一个模块。

4.3  调试注意事项

4.3.1  硬件调试注意事项

硬件全部焊接好以后,先初步检查是否焊错,有没有漏焊、虚焊,元件有没有接错或者接反。在上电之前可以用万用表检测电源正负极有没有短路,保证系统可靠地供电。

对于复杂的接口,例如LCD接口,单纯从硬件角度不易调试,应结合软件从不同的角度进行测试,这样能起到更好的效果。有条件的话可以合理地使用示波器,提高工作效率。同时系统时钟受干扰或晶体振荡不正常可能导致系统工作故障,复位电路也要保证连接正确及工作正常。

检查故障时要细心严谨,要学会正确迅速排除故障原因。如果调试各部分都正常,就可以把整个程序下载到单片机系统中,再进行下一步的软件调试了。

4.3.2  软件调试注意事项

    整个程序是用C语言进行编写的,因为程序不可能一次就能正确无误的完成,所以须要在调试中慢慢修改。程序完成后先是用Proteus软件进行仿真调试,之后再进行硬件调试。这样可以避免多次使用硬件下载调试造成不必要的麻烦与失误。软件仿真成功了,便可以通过串口下载到单片机上进行调试了。但要注意一点,并不是软件上仿真成功了硬件就一定能成功,很多时候软件上的仿真跟便件上仿真还是存在很大的差别的,软件仿真成功只说明电路连接正确,程序编写合理。而硬件的仿真还受到元件性能,周围环境温度和噪音干扰等多方面因素影响,必须要综合的进行考虑。

 5 设计心得体会

   我们这次的课程设计的方案有两个,一是通过OCM-12864-1液晶显示屏显示,二是通过8*8点阵来显示。

   我们的硬件电路比较简单,只要单片机最小系统加一个三输入与非门和按键就可以了,所以我们还加入了程序下载模块。

由于平时做实验时都做过所以很容易就把硬件做完了。主要时间都花在程序的编写与调试,但是在调试过程出现一些问题,

仿真可以但是在OCM-12864-1液晶上显示就是不行,后来我们通过一步一步调试发现,OCM-12864-1液晶屏本身就有问题。8*8点阵显示相对而言比较顺利就完成了。

   通过这次课程设计,使我对C语言的基本使用更加熟练,同时也增加我对C语言模块化程序设计的一些认识。在调试过程中通过和同学的交流,也增加了合作的技巧。通过查阅资料也学到了一些课本上没有的知识,拓宽了自己的知识面,增加了学习C语言的信心。

   平时我也做了很多实验,写了很多小程序。很多子程序都是固定的,直接调用就可以了,只要改一下参数就可以。这大大方便了我们的设计。为我们节省了横多时间。

    这次课程设计让我受益匪浅,无论从知识上还是其他个方面。上课的时候从来没有见过真正的单片机,只是从理论的角度去理解。通过课程设计能够理论联系实际的学习,开阔了我们的眼界,也提高了单片机知识的理解水平。在这次课程设计中又让我体会到了合作与团结的力量,当我遇到问题,我就会在QQ群里讨论或者是同学之间相互帮助。团结就是力量,无论在现在的学习中还是在以后的工作中,团结都是至关重要的,有了团结就会有更多的理念、更多的思维、更多的情感。

       单片机是一门很重要的课程。如果学好一门单片机,就能找到一份好的工作。尽管我们在课堂学到的东西很有限,但在以后的学习中单片机还是要好好的深入研究和学习,学好单片机也就是多了一项生存的本钱。

最后感谢老师对我的精心指导和帮助,同时也感谢同学们对我的帮助。

6 附录

6.1总原理图

方案1原理图

方案2原理图

6.2单片机程序代码

方案1:OCM12864-1显示

       /******************************     贪食蛇.c******************************/

#include <reg52.h>

#include <intrins.h>

#include"12864.h"

#include"display.h"

void Delay_1ms(uint k); //函数声明

//文字点阵数据

uchar code shu0[]=

{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00};/*0*/

uchar code shu1[]=

{0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00};/*1*/

uchar code shu2[]=

{0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00};/*2*/

uchar code shu3[]=

{0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00};/*3*/

uchar code shu4[]=

{0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00};/*4*/

uchar code shu5[]=

{0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00};/*5*/

uchar code shu6[]=

{0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00};/*6*/

uchar code shu7[]=

{0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00};/*7*/

uchar code shu8[]=

{0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00};/*8*/

uchar code shu9[]=

{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00};/*9*/

uchar code GameOverWord[]=

         {

         0x00,0x20,0x44,0x08,0x20,0xE0,0x92,0x94,0x10,0x28,0xAE,0x68,0x24,0x04,0x00,0x00,

         0x00,0x0C,0x03,0x04,0x02,0x19,0x0C,0x03,0x02,0x12,0x22,0x1F,0x01,0x01,0x01,0x00,/*游*/

         0x00,0x20,0xA0,0x90,0x10,0xF0,0x00,0x40,0x7F,0xC0,0x20,0x24,0x88,0x00,0x00,0x00,

         0x10,0x08,0x04,0x02,0x01,0x02,0x14,0x10,0x08,0x05,0x06,0x09,0x10,0x20,0x38,0x00,/*戏*/

         0x00,0x60,0x50,0xCC,0x40,0x30,0x40,0x40,0x40,0xFE,0x20,0x20,0x20,0x20,0x00,0x00,

         0x00,0x12,0x13,0x0A,0x09,0x05,0x00,0x3A,0x2A,0x25,0x25,0x15,0x1D,0x00,0x00,0x00,/*结*/

         0x00,0x00,0x00,0x60,0xA8,0xA8,0xA8,0xFF,0x94,0x54,0x70,0x00,0x00,0x00,0x00,0x00,

         0x10,0x10,0x08,0x08,0x04,0x02,0x01,0x7F,0x02,0x04,0x08,0x08,0x10,0x10,0x10,0x00 /*束*/

         };

uchar code fen[]=

         {

         0x80, 0x40, 0x20, 0x98, 0x87, 0x82, 0x80, 0x80, 0x83, 0x84, 0x98, 0x30, 0x60, 0xc0, 0x40, 0x00,

         0x00, 0x80, 0x40, 0x20, 0x10, 0x0f, 0x00, 0x00, 0x20, 0x40, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00/*分*/

         };

uchar code shu[]=

         {

         0x10, 0x92, 0x54, 0x38, 0xff, 0x38, 0x54, 0x52, 0x80, 0xf0, 0x1f, 0x12, 0x10, 0xf0, 0x10, 0x00,

         0x42, 0x42, 0x2a, 0x2e, 0x13, 0x1a, 0x26, 0x02, 0x40, 0x20, 0x13, 0x0c, 0x33, 0x60, 0x20, 0x00/*数*/

         };

uchar SnakeBody[80]={0,8,1,8};

sbit deep=P2^5;

bit IsMovInVertical=0;  //IsMovInVetical为0表示当前蛇头移动是水平向,1表垂直向       

bit IsNotEatSelf;   //为0表蛇头碰到蛇身,为1表未碰到。

bit IsToStep=0;       //步进标志,主循环发现此位为1,让蛇进一步且将该位置0.

bit IsT0GenNewFood;   //是否需要重新产生食物(重置食物)

uchar SnakeLength=2;   //蛇体当前长度

uchar p=30;            //定时次数

uchar dengji;              //等级

uchar ButtonNum,MovDirection=1; //ButtonNum是按键码,MovDirection是移动方向

bit flag;

        

void main()

{          

    uchar food[2]={12,8}; //食物的位置(位于哪个4*4小格)

    uchar i,x,y;

    choose12864(2);      //选定左右屏幕

    init_lcd();          //初始化

    clear12864();           //清屏 

    vertical(1,61,30);   //画左垂直边框线

    vertical(1,61,127);  //画右垂直边框线

//for(i=0;i<62;i++)   

//    {

//           dotx(30,1+i);

//           dotx(127,1+i);

 //   }

    for(i=0;i<98;i++)    //画上下水平边框线

    {

             dot(30+i,1);

             dot(30+i,62);

    }

    Play16(0,0,1,fen);   //在屏幕左侧显示"分数"字样

    Play16(0,0,2,shu);

    DisplaySnake(SnakeBody,(SnakeBody+1));//显示贪食蛇

    DisplaySnake((SnakeBody+2),(SnakeBody+3));

    DisplaySnake(food,food+1);           //显示食物

    TMOD=0x01;    //定时器工作方式

    IT0=1;     //边延有效

    EX0=1;   //开外部中断

    TL0=0x00;

    TH0=0x00;        //定时器初值

    TR0=1;   //启动定时器

    ET0=1;  //开定时器中断

    IP=0x01;         //设置中断优先级

    EA=1;              //开CPU中断

    do

    {

        while(!IsToStep);//等待到定时中断给步进标记置位后,控制蛇身向前一步

        x=*(SnakeBody); //暂存蛇尾位置                           

        y=*(SnakeBody+1);

        switch(MovDirection)

               {

                   case 1://右

                               {

                                 //蛇步进,原次尾单元变成尾单元。SnakeBody向前挪两个字节。

                                               for(i=0;i<SnakeLength-1;i++)

                                                        {

                                     *(SnakeBody+(i<<1))=*(SnakeBody+(i<<1)+2);

                                           *(SnakeBody+(i<<1)+1)=*(SnakeBody+(i<<1)+3);

                                      }

                               (*(SnakeBody+(SnakeLength<<1)-2))++;  //设定新蛇头的位置  

                                        IsMovInVertical=0;flag=0;

                                        break;//退出switch循环

                               }

                   case 2:                                        //上

                           {    // 蛇步进,原次尾单元变成尾单元。SnakeBody向前挪两个字节。

                                for(i=0;i<SnakeLength-1;i++)             

                                                   {

                            *(SnakeBody+(i<<1))=*(SnakeBody+(i<<1)+2);

                                    *(SnakeBody+(i<<1)+1)=*(SnakeBody+(i<<1)+3);

                             }

                               (*(SnakeBody+(SnakeLength<<1)-1))--;//设定新蛇头的位置 

                               IsMovInVertical=1;flag=0;

                                       break;

                           }

                         case 3:                                              //左

                           {//蛇步进,原次尾单元变成尾单元。SnakeBody向前挪两个字节。

                       for(i=0;i<SnakeLength-1;i++)               

                                               {

                           *(SnakeBody+(i<<1))=*(SnakeBody+(i<<1)+2);

                                   *(SnakeBody+(i<<1)+1)=*(SnakeBody+(i<<1)+3);

                         }

                       (*(SnakeBody+(SnakeLength<<1)-2))--;//设定新蛇头的位置 

                               IsMovInVertical=0;

                                               flag=0;

                               break;

                           }

                              case 4:                                              //下

                           { //蛇步进,原次尾单元变成尾单元。SnakeBody向前挪两个字节。

                       for(i=0;i<SnakeLength-1;i++)               

                                               {

                           *(SnakeBody+(i<<1))=*(SnakeBody+(i<<1)+2);

                                   *(SnakeBody+(i<<1)+1)=*(SnakeBody+(i<<1)+3);

                         }

                             (*(SnakeBody+(SnakeLength<<1)-1))++;

                               IsMovInVertical=1;

                                                        flag=1;

                                       break;

                           }

                       

               }

                 if(((*(SnakeBody+(SnakeLength<<1)-2))==food[0])&&

          ((*(SnakeBody+(SnakeLength<<1)-1))==food[1]))

               { //若碰到食物,调整蛇体

                           for(i=SnakeLength;i>0;i--)

                                    {

                                        *(SnakeBody+(i<<1))=*(SnakeBody+(i<<1)-2);

                                        *(SnakeBody+(i<<1)+1)=*(SnakeBody+(i<<1)-1);

                                    }

                           *SnakeBody=x;

                           *(SnakeBody+1)=y;//加回尾巴

                           SnakeLength++;//蛇体长度加1

                           do  //产生新的有效的食物

                           {

                               IsT0GenNewFood=0;

                               food[0]=TL0%24; //产生食物

                         food[1]=TL0%15;

                    //检查Food位置是否被蛇身覆盖,若是需重置食物。

                               for(i=0;i<SnakeLength-1;i++)

                               {

                       if((*(SnakeBody+(i<<1)))==food[0]&&

                         ((*(SnakeBody+(i<<1)+1))==food[1]))

                                      {

                                          IsT0GenNewFood=1;

                                          break;//退出小循环

                                      }

                                }

                           }

                           while(IsT0GenNewFood);

                           DisplaySnake(food,food+1); //显示食物

               }

              

        IsNotEatSelf=1;

              for(i=0;i<SnakeLength-1;i++)

            { //判断是否吃到自己,蛇头坐标与身体某单元相同

                           if(*(SnakeBody+(i<<1))==*(SnakeBody+(SnakeLength<<1)-2)

                   &&(*(SnakeBody+(i<<1)+1)==*(SnakeBody+(SnakeLength<<1)-1)))

                             {

                               IsNotEatSelf=0;//吃到自己

                              break;//退出大循环

                          }

                       }

              IsNotEatSelf=IsNotEatSelf&&*(SnakeBody+(SnakeLength<<1)-2)>=0

                        &&*(SnakeBody+(SnakeLength<<1)-2)<24; //是否碰到垂直墙壁

                    IsNotEatSelf=IsNotEatSelf&&*(SnakeBody+(SnakeLength<<1)-1)>=0

                        &&*(SnakeBody+(SnakeLength<<1)-1)<15;//是否碰到水平墙壁

               if(IsNotEatSelf) //如果未吃到自己且没碰到墙壁

                {

                   ClearSnake(&x,&y);

                   for(i=0;i<SnakeLength;i++)//显示蛇身

                   {

                      DisplaySnake(SnakeBody+(i<<1),SnakeBody+(i<<1)+1);

                   }

                   IsToStep=0;

                   Play8(0,0,3,shu0+((SnakeLength/10)<<4));//显示得分

                    Play8(0,1,3,shu0+(((SnakeLength)%10)<<4));

                }

    }  

    while(IsNotEatSelf);

    //如果吃到自己,则上面大循环结束,游戏结束。

    TR0=0;

    Delay_1ms(450);

    Delay_1ms(450);

    choose12864(2);

    clear12864();

    Play16(0,4,1,GameOverWord); //显示"游戏结束"字样。

    Play16(0,6,1,GameOverWord+32);

    Play16(1,0,1,GameOverWord+64);

    Play16(1,2,1,GameOverWord+96);

    while(1);

void Delay_1ms(uint k)//延时

{

 uchar t;

   while(k--)

                   {

                            for (t=123;t>0;t--);

                   }

        

}

void Ex0Interrupt() interrupt 0

{       

           ButtonNum=(P2>>6);

           ButtonNum=ButtonNum&0x03;//提取ButtonNum的值(00、01、10、11)

           if(IsMovInVertical)

           {  //当前方向为垂直方向,则不处理上下方向键的按下。

               if(ButtonNum==1)            MovDirection=3;//左

               if(ButtonNum==2)    MovDirection=1;//右

           }

           else

           {  //当前方向为水平方向,则不处理左右方向键的按下。             

               if(ButtonNum==0)    MovDirection=2;//上

               if(ButtonNum==3)    MovDirection=4;//下

           }

}

void timer0() interrupt 1   

{

    if(p--)

    {

         TL0=0;

              TH0=0xa0;

              IsToStep=0;

    }

    else

    {

         deep=0;

//                   Delay_1ms(1);

_nop_();

_nop_();

_nop_();

          IsToStep=1;

               TL0=0;

               TH0=0x00;

                     dengji++;

                     if(dengji>=10)

                            dengji=10;

               p=20-dengji;

               deep=1;

    }

}

/*****************display.c***************************************************/

#include"display.h"

#include"12864.h"

extern void Delay_1ms(uint k);

extern bit flag;

void Play8(uchar Screen,uchar Column,uchar Page,uchar *Disp)       //8X16字符的显示

{                 //       左右屏幕选择,显示位置的起始列号显示位置的起始页号,指向显示的内容uchar i;                                                                     

   choose12864(Screen);

   Page=Page<<1;

   Column=Column<<3;

   cmd_w12864(Column+0x40);

   cmd_w12864(Page+0xb8);

   for(i=0;i<8;i++)  dat_w12864(*(Disp+i));

   cmd_w12864(Column+0x40);

   cmd_w12864(Page+0xb9);

   for(i=8;i<16;i++)  dat_w12864(*(Disp+i));

}

void Play16(uchar Screen,uchar Column,uchar Page,uchar *Disp)      // 16*16的汉字

{                 //左右屏幕选择,显示位置的起始列号, 显示位置的起始页号,指向显示的内容

    uchar i;

    choose12864(Screen);

    Page=Page<<1;                                                 

    Column=Column<<3;

    cmd_w12864(Column+0x40);

    cmd_w12864(Page+0xb8);

    for(i=0;i<16;i++)  dat_w12864(*(Disp+i));   //写起始页的16列

    cmd_w12864(Column+0x40);

    cmd_w12864(Page+0xb9);

    for(i=16;i<32;i++) dat_w12864(*(Disp+i));   //写下一页的16列

}

/***********************************************************************

uchar RowStart, uchar RowStop,

uchar Column, 

************************************************************************/

void vertical(uchar RowStart,uchar RowStop,下端

{                                                                                                                                 //       点所在行、竖线所在列

   uchar i,sum=0;

   if(Column>63)  //若列号大于63选择在右屏画竖线

   {

     choose12864(1);

     Column=Column-64;

   }

   else  choose12864(0);  //若列号小于63选择在左屏画竖线

  if((RowStart/8)!=(RowStop/8))//如果上下端点在不同页

  {

      for(i=0;i<(8-RowStart%8);i++) sum=sum|((0x80>>((RowStart%8)+i)));

      cmd_w12864(Column+0x40);                //2

      cmd_w12864(RowStart/8+0xb8);

      dat_w12864(sum);

      sum=0;

      for(i=0;i<(RowStop/8-RowStart/8-1);i++)

      {

          cmd_w12864(Column+0x40);

               cmd_w12864((RowStart/8)+0xb9+i);

               dat_w12864(0xff);

      }

       for(i=0;i<=(RowStop%8);i++) sum=sum|(0x80>>i);

       cmd_w12864(Column+0x40);

       cmd_w12864(RowStop/8+0xb8);

       dat_w12864(sum);

       sum=0;     

 }

 else                                        //如果上下端点在同一页

  {

     for(i=0;i<=RowStop-RowStart;i++)  sum=sum|(2<<(i+(RowStart%8)));

           cmd_w12864(0x40|Column);

           cmd_w12864(0xb8|(RowStart/8));

           dat_w12864(sum);

   }

}

void dot(uchar x,uchar y)                               //点的显示

{

    uchar dat;

    if(x>63)

    {

                   choose12864(1);

              x=x-64;

    }

    else  choose12864(0);

    dat=dat_r12864(y/8,x);

//       if(flag==0)dat=dat&0x0f;

//       if(flag==1)dat=dat&0xf0;

    cmd_w12864(0x40|x);         //确定列码

    cmd_w12864(0xb8|(y/8));     //确定页码

    dat_w12864((1<<(y%8))|dat); //写数据

}

void dotx(uchar x,uchar y)

{

    uchar dat;

    if(x>63)

    {

                   choose12864(1);

              x=x-64;

    }

    else  choose12864(0);

    dat=dat_r12864(y/8,x);

    cmd_w12864(0x40|x);         //确定列码

    cmd_w12864(0xb8|(y/8));     //确定页码

    dat_w12864((1<<(y%8))|dat); //写数据

}

void cleardot(uchar x,uchar y)                                                      //清点

{

    uchar dat,j;

    if(x>63)

    {

        choose12864(1);

              x=x-64;

    }

    else  choose12864(0);

    dat=dat_r12864(y/8,x);

    cmd_w12864(0x40|x);

    cmd_w12864(0xb8|y/8);

    j=~(1<<y%8);

    dat_w12864(dat&j);

//       Delay_1ms(1);

         dat_w12864(dat&j);

//       dat_w12864(dat&j);

//       dat_w12864(dat&j);

}

void DisplaySnake(uchar *x,uchar *y)     //x<24  y<15 显示一个4*4单元格

{

    uchar i,m,n;

    if(*x<24&&*y<15)

    {

        m=(*x)<<2;

        n=((*y)<<2)+2;                   //n=((*y)<<2)+2;

        for(i=0;i<4;i++)

        {

                            dot(31+m,n+i);

                            dot(32+m,n+i);

                            dot(33+m,n+i);

                 dot(34+m,n+i);

 //           dot(31+m,n+i);

//               dot(34+m,n+i);

        }

//        dot(31+m,n);

//        dot(31+m,n+1);

//dot(1+m,n+2);

//        dot(31+m,n+3);

//        dot(32+m,n);

//dot(2+m,n+1);

//        dot(32+m,n+2);

//        dot(32+m,n+3);

    }

}

void ClearSnake(uchar *x, uchar *y)                          //清除一个4*4单元格

{

    uchar i,m,n;

    m=((*x)<<2)+31;

    n=((*y)<<2)+2;                                     //n=((*y)<<2)+2;

    for(i=0;i<8;i++)

    {

         cleardot(m,n+i);

              cleardot(m+1,n+i);

              cleardot(m+2,n+i);

              cleardot(m+3,n+i);

    }

}

         方案2:8*8点阵显示

#include <reg52.h>

#define uint unsigned int

#define uchar unsigned char

sfr  DB=0x90;

sbit LE=P3^7;

sbit OE=P3^6;

sbit K1=P3^2;

sbit K2=P3^3;

sbit K3=P3^4;

sbit K4=P3^5;

sbit K5=P3^1;

sbit BP=P0^0;

sbit MT=P0^1;

#define Snakelen 30                             //最大长度

uchar disram[8];      //显示缓冲区

uchar x[Snakelen],y[Snakelen];//蛇身坐标

bit on_off;                                          //显示开关,0--关闭;1--开启

bit inverse;                                //反色显示,0--关闭;1--开启

bit food, gameover;                              //食物和结束标志位

char dx, dy;

uchar ID;

uchar Snakespeed;             //移动速度

uchar level;             //关数

uchar IsNotEatSelf;                     //生命值

void key_scan(void);

void T0_init(void)

{

    TMOD|=0x01;

    TH0=0xf8;                //2ms

    TL0=0x36;

    IE|=0x82;

         PT0=1;

    TR0=1;

}

void T1_init(void)                   

{

    TMOD|=0x10;

    TH1=0x00;        //65ms

    TL1=0x00;

    IE|=0x88;

    TR1=1;

}

void clr_ram(void)

{

         uchar i;

         for(i=0;i<8;i++)

                   disram[i]=0x00;

}

//画点函数,擦点或者绘点

//点阵左上角坐标为(0, 0) 右下角坐标为(7, 7)

//横坐标为x:0~7              纵坐标为y:0~7

//k = 1 --绘点           k = 0 --擦点       

void point1(uchar x, uchar y, bit k)

{

         if(k) disram[y] |= 0x01 << x;

         else  disram[y] &= ~(0x01 << x);

}

void T0_intservice(void) interrupt 1

{

         static uchar n;

         TR1=0;

    TH0=0xf8;                                                     //2ms

    TL0=0x36;

         OE=1;                                                                  //高阻

         if(on_off)      DB=0x01 << n;                         //开始列扫描

         else              DB=0x00;                                //关闭显示

         LE=1;                                                                  //传送

         LE=0;                                                                  //锁存

         if(inverse)     DB=disram[n];                          //反色显示

         else              DB=~disram[n];               //正常显示

         OE=0;                                                                  //输出显示

        

         n++; if(n==8) n=0;                              //循环扫描

         TR1=1;

}

void T1_intservice(void) interrupt 3

{

         TH1=0x00;

    TL1=0x00;

         key_scan();

}

void delay(void)

{

    uchar i,j;

    for(i=5;i>0;i--)

    for(j=123;j>0;j--);

}

void beep(void)

{

         BP=0;

         delay();

         BP=1;

}

void key_scan(void)

{

         if(K1==0)                                          //上

         {

                   if(ID==0) {dx=0; dy=-1;}

                   if(ID==1 &&Snakespeed>1)        //加速

                   {

                            Snakespeed--;

                            beep();                                      //声音指示

                            while(K1==0);

                   }

                   if(ID==2 &&level<17)  //选关

                   {

                            level++;

                            beep();                                      //声音指示

                            while(K1==0);

                   }

         }

         if(K2==0)                                          //下

         {

                   if(ID==0) {dx=0;dy=1;}

                   if(ID==1&&Snakespeed<15)        //减速

                   {

                            Snakespeed++;

                            beep();                                      //声音指示

                            while(K2==0);

                   }

                   if(ID==2 && level>0)       //选关

                   {

                            level--;

                            beep();                                      //声音指示

                            while(K2==0);

                   }

                  

         }

         if(K3==0)                                          //左

         {

                   if(ID==0) {dy=0; dx=-1;}

                   if(ID==1) inverse=0;                   //正常显示

                   if(ID==2) on_off=1;                   //开启显示

                  

         }

         if(K4==0)                                          //右

         {

                   if(ID==0) {dy=0; dx=1;}

                   if(ID==1) inverse=1;                   //反色显示

                   if(ID==2) on_off=0;                   //关闭显示

         }

         if(K5==0)                                          //中

         {

                   ID++; if(ID==4) ID=0;

                   beep();                                                        //声音指示

                   while(K5==0);

         }                

}

//定点显示,一次只显示一个点

void gotoxy(unsigned char x, unsigned char y)

{

         OE =1;                                                                          //高阻

         DB =(0x01 << y);                                       

         LE =1;                                                                          //传送

         LE =0;                                                                          //锁存

         DB = ~(0x01 << x);                                     

         OE = 0;                                                                         //输出显示

         beep();                                                                  //声音指示

         delay();

}

//通关演示

void congrate(void)

{

         char i, j;

         TR0 =0;

         TR1 =0;

         for(j=0; j<5; j++)

         {

                   for(i=j ; i<8-j; i++)gotoxy(j, i);

                   for(i=j+1 ; i<8-j; i++)gotoxy(i, 7-j);

                   for(i=j+1 ; i<8-j; i++)gotoxy(7-j, 7-i);

                   for(i=j+1 ; i<7-j; i++)gotoxy(7-i, j);

         }

         for(j=4; j>=0; j--)

         {

                   for(i=6-j ; i>=j+1; i--)gotoxy(7-i, j);

                   for(i=7-j ; i>=j+1; i--)gotoxy(7-j, 7-i);

                   for(i=7-j ; i>=j+1; i--)gotoxy(i, 7-j);

                   for(i=7-j ; i>=j; i--)gotoxy(j, i);   

         }

}

void main (void)

{       

         uchar i;                           //通用循环变量

         uchar num;                      //蛇的长度

         uchar foodx, foody;  //食物坐标

         IsNotEatSelf=3;

         Snakespeed=8;

         num=2;

         T0_init();                                           //定时器初始化

         T1_init();

         on_off=1;                                           //开显示

         inverse=0;                                 //正常显示

         clr_ram();                                           //清显示缓冲区

         x[0]=1;                                              //初始化位置

         y[0]=2;

         dx=1;                                                //向右运动

         while(1)

         {

                   x[0] += dx;   y[0] += dy;

                   x[0] &= 0x07; y[0] &= 0x07;                         //穿墙

                   if(!food)                                                               //放置食物

                   {

again:           foodx = TL0&0x07;                                     //随机

                            foody = TH0&0x07;

                            for(i = 0; i < num; i++)

                            {

                                     if(foodx == x[i] && foody == y[i])

                                     goto again;

                            }

                            point1(foodx, foody, 1);              //显示食物

                            food=1;                                                       //置食物标志位

                   }

                   if(x[0]==foodx && y[0]==foody) //吃到食物  

                   {

                            beep();                                                        //声音指示

                            num++;                                                                //蛇长增加1节

                            food=0;                                                       //清食物标志位

                   }

                   for(i=0; i<num; i++)                            //显示蛇身

                   point1(x[i], y[i], 1);

                   point1(x[i], y[i], 0);                             //清蛇尾

                   for(i=1; i<num; i++)                            //判断是否自撞

                   {

                            if((x[0]==x[i]) && (y[0]==y[i]))

                            gameover=1;                                                //置结束标志位

                   }

                   for(i=0; i<Snakespeed; i++) delay();       //蛇运动速度

                   for(i=0; i<num; i++)                            //蛇移动蛇身

                   {

                            x[num-i]=x[num-i-1];

                            y[num-i]=y[num-i-1];

                   }

         /*       if(x[0]>7||y[0]>7)                                          //判断是否撞墙

                   {

                            over = 1;                                                     //置结束标志位

                   }

         */

                   if(num>10+level)                                //过关

                   {

                            if(num>28)                                         //通关!!!

                            {

                                     while(1){congrate();inverse =~inverse;}

                            }

                            delay(); beep();                                    //声音指示

                            MT=0; delay(); MT=1;               //振动指示

                            if(Snakespeed>1) Snakespeed--;                       //速度加一

                            num=2;                                                       //重定蛇长

                            level+=1;                                                    //总的蛇长增加1

                            clr_ram();                                                    //清除屏幕

                            goto again;                                                  //新的一关开始

                   }

                   if(gameover)                                                          //判断是否结束

                   {

                            MT=0; delay(); MT=1;               //振动指示

                            clr_ram();                                                    //清除屏幕

                            num = 2;                                                     //重新设定蛇长

                            point1(foodx, foody, 1);              //重新放置食物

                            x[0]=1; y[0]=2;                                   //起点位置

                            dx=1;                                                         //向右运动

                            gameover=0;                                                //清结束标志位

                            IsNotEatSelf--;                                                                //生命值减1

                   }       

                   if(!IsNotEatSelf)      congrate();

         }

}

                    

7 参考文献

[1] 万隆,巴奉丽.单片机原理与应用技术.清华大学出版社,2010.3

[2] 谭浩强.C语言设计(第三版). 清华大学出版社,2005.6

[3] 罗永能,刘云.基于51单片机的贪食蛇游戏机开发[D].广东:仲恺农业工程学院信息学院,2008.6.

[4] 王为青,邱文勋.51单片机应用开发案例精选[M].北京: 人民邮电出版社, 2007.8.

[5] 林志琦,郎建军,李会杰等. 基于Proteus的单片机可视化软硬件仿真[M].北京: 北京航空航天大学出版社, 2006.9.

相关推荐