单片机主题设计报告
目录
目录 (1)
1设计任务及要求 (1)
2总体设计思路及功能描述 (1)
3 各部分软硬件设计原理及方案详细说明 (6)
3.1 人机接口电路 (6)
3.2单片机与PC机通信电路 (8)
3.3 其他部分电路说明 (8)
3.4 软件模块设计 (9)
3.4.1 LCD初始化 (9)
3.4.2 键盘扫描程序 (11)
3.4.3 显示液晶汉字 (11)
3.4.4 地鼠的随机出现 (12)
5 设计心得体会 (12)
6 附录 (13)
6.1总原理图 (13)
6.2单片机程序代码 (14)
7 参考文献 (27)
正文:
1设计任务及要求
本设计以51系列单片机AT89s52为控制核心,以点阵液晶显示模块、遥控为人机接口,实现了一个打地鼠游戏机。通过本设计,令读者掌握利用单片机开发简单电子产品的基本技能,熟悉原理图绘制、仿真、软件设计、优化以及系统调试的基本方法,为进一步设计开发更为复杂的嵌入式模拟/数字混合系统打下一定的基础。
"打地鼠”又称为“打地鼠”,是一种益智小游戏。其游戏规则比较简单,就是随机产生地鼠,即在12864显示屏相应相应的地鼠图案,规定时间内按相应的键,若按错或者未按键则结束游戏。本作品用红外遥控8个键来模拟锤子,另有一个复位按键控制程序的重启,
游戏界面:采用分辨率为128×64的液晶显示屏
2总体设计思路及功能描述
如图1--1打地鼠主要有一个主程序及相应的一些子程序。主程序的作用是一些初始化工作及相应模块的跳转。该主程序流程图:
有三个主要子程序:打地鼠子程序主要是实现打地鼠动作执行、地鼠的随机产生、得分累计、图像显示、相应的按键解释。而该游戏也融入了音乐的效果,使游戏更具有趣味性,由于课程设计有涉及该方面知识,等。该部分流程图为:
Work0
Work1
Work2
Work3
Work4
Work5
Work6
3 各部分软硬件设计原理及方案详细说明
3.1 人机接口电路
本游戏机游戏界面由液晶显示模块呈现。液晶显示模块中,最主要的就是LCD液晶屏。根据LCD 液晶屏显示内容的不同,液晶显示模块可以分为数显液晶模块、点阵字符液晶模块和点阵图形液晶模块3种。本设计使用点阵图形液晶模块OCM12864。OCM12864液晶显示模块是128×64点阵型液晶显示模块,可显示各种字符及图形,可与CPU直接连接,具有8位标准数据总线、6条控制线及电源线,各引脚的信号说明参见表ChpNum-1。
表ChpNum-1 OCM12864引脚说明
管脚名称
方
向
引脚说明
VSS - 逻辑电源地。
VDD - 逻辑电源+5V。
V0 I LCD调整电压,应用时接10K电位器可调端
RS I 数据\指令选择:高电平:数据D0-D7将送入显示RAM;
低电平:数据D0-D7将送入指令寄存器执行。
R/W I 读\写选择:高电平:读数据;低电平:写数据。
E I 读写使能,高电平有效,下降沿锁定数据。
DB0 I/
O
数据输入输出引脚。
DB1 I/
O
数据输入输出引脚。
DB2 I/
O
数据输入输出引脚。
DB3 I/
O
数据输入输出引脚。
DB4 I/
O
数据输入输出引脚。
DB5 I/
O
数据输入输出引脚。
DB6 I/
O
数据输入输出引脚。
DB7 I/
O
数据输入输出引脚。
CS1 I 片选择信号,高电平时选择左半屏。
CS2 I 片选择信号,高电平时选择右半屏。
/RET I 复位信号,低电平有效。
VEE O LCD驱动,负电压输出,对地接10K电位器
LEDA - 背光电源,LED+(5V)。
LEDK - 背光电源,LED-(0V)。
单片机与液晶显示器的连接电路如图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.5脚输出信号控制PNP三极管S8550的通断,当P2.5输出高电平时,三极管截止,当P2.5输出低电平时,三极管导通。因此,通过在P2.5脚输出不同频率的方波信号,可使得蜂鸣器发出不同频率的声音。
蜂鸣器电路图:
B1
BE LL VCC
R15
100
BE EP(P1.5)
蜂鸣器
本次设计按键部分采用的是红外遥控器,其原理是: 一体化红外线接收器是一种集红外线接收和放大于一体,不需要任何外接元件,就能完成从红外线接收到输出与TTL电平信号兼容的所有工作,而体积和普通的塑封三极管大小一样,它适合于各种红外线遥控和红外线数据传输。红外接收头将38K载波信号过虑,得到与发射代码反向接收代码解码的关键是如何识别“0”和“1”,从位的定义我们可以发现“0”,“1”均以0.56ms的低电平开始,不同的是高电平的宽度不同,“0”为
0.56ms,“1”为1.68ms,所以必须根据高电平的宽度区别“0”和“1”。如果从0.56ms 低电平过后,开始延时,0.56ms 以后,若读到的电平为低,说明该位“0”,反之则为“1”,为了可靠起见,延时必须比0.56ms 长些,但又不能超过1.12ms,否则如果该位为“0”,读到的已是下一位的高电平,因此取(1.12ms+0.56ms )/2=0.84ms 最为可靠,一般取0.84ms 左右均可。 红外接收模块电路:
P 3.2/C S 1
红外接收
Te
3.2单片机与PC 机通信电路
中央控制器CPU
3.3 其他部分电路说明
显示电路、复位和时钟电路比较简单,具体连接请参见电路原理总图。
SK
上拉电阻
复位电路
502
供电部分采用7805将输入9~12V电压转换为5V输出,输入输出端接220uF电解电容,使整流后的脉动直流电压变成相对比较稳定的直流电压。由于大容量的电解电容一般具有一定的电感,对高频及脉冲干扰信号不能有效地滤除,所以两端并联一只容量为0.1uF的电容,以滤除高频及脉冲干扰。
复位电路含有上电复位和按键复位功能,按键复位用于游戏结束后游戏的重新启动。系统插电瞬间电源通过10K电阻给10uF电容充电电平,充电电流在10K电阻上产生压降,于是电阻上端产生一个维持几百毫秒的高电平输入到RESET对单片机进行复位,电容充电满后经过电阻电流为0,复位失效。类似地,当开关按下时,强制产生一个高电平对单片机进行复位。
时钟电路使用12MHz的晶振,晶振的两引脚处接入两个22pF的瓷片电容来削减谐波对电路稳定性的影响。
3.4 软件模块设计
3.4.1 LCD初始化
在对LCD进行初始化时,先要检查读忙碌标志位,当BF为1表示内部操作正在进行,0表示允许指令操作。具体指令说明如下:
显示开/关设置
引脚
R
/W
R
S
D
B7
D
B6
D
B5
D
B4
D
B3
D
B2
D
B1
D
B0
电平L L L L H H H H H H
/L
功能:设置屏幕显示开/关。DB0=H,开显示;DB0=L,关显示。不影响显示RAM(DD RAM)中的内容。
设置显示起始行
引R R D D D D D D D D
脚/W S B7 B6 B5 B4 B3 B2 B1 B0
电
平
L L H H 行地址(0~63)
功能:执行该命令后,所设置的行将显示在屏幕的第一行。显示起始行是由Z地址计数器控制的,该命令自动将A0-A5位地址送入Z地址计数器,起始地址可以是0-63范围内任意一行。Z地址计数器具有循环计数功能,用于显示行扫描同步,当扫描完一行后自动加一。
设置页地址
引脚
R
/W
R
S
D
B7
D
B6
D
B5
D
B4
D
B3
D
B2
D
B1
D
B0
电平L L H L H H H 页地址(0~
7)
功能:执行本指令后,下面的读写操作将在指定页内,直到重新设置。页地址就是DD RAM 的行地址,页地址存储在X地址计数器中,A2-A0可表示8页,读写数据对页地址没有影响,除本指令可改变页地址外,复位信号(RST)可把页地址计数器内容清零。
设置列地址
引脚
R
/W
R
S
D
B7
D
B6
D
B5
D
B4
D
B3
D
B2
D
B1
D
B0
电
平
L L L H 列地址(0~63)
功能: DD RAM 的列地址存储在Y地址计数器中,读写数据对列地址有影响,在对DD RAM进行读写操作后,Y地址自动加1。
状态检测
引脚
R
/W
R
S
D
B7
D
B6
DB
5
D
B4
D
B3
D
B2
D
B1
D
B0
电平H L B
F
L ON
/OFF
R
ST
L L L L
功能:读忙信号标志位(BF)、复位标志位(RST)以及显示状态位(ON/OFF)。 BF=H:内部正在执行操作;BF=L:空闲状态。RST=H:正处于复位初始化状态;RST=L:正常状态。ON/OFF=H:表示显示关闭;ON/OFF=L:表示显示开。
写显示数据
引脚
R
/W
R
S
D
B7
D
B6
D
B5
D
B4
D
B3
D
B2
D
B1
D
B0
电平L H D
7
D
6
D
5
D
4
D
3
D
2
D
1
D
功能:写数据到DD RAM,DD RAM是存储图形显示数据的,写指令执行后Y地址计数器自动加1。D7-D0位数据为1表示显示,数据为0表示不显示。写数据到DD RAM前,要先执行“设置页地址”及“设置列地址”命令。
读显示数据
引脚
R
/W
R
S
D
B7
D
B6
D
B5
D
B4
D
B3
D
B2
D
B1
D
B0
电平H H D
7
D
6
D
5
D
4
D
3
D
2
D
1
D
功能:从DD RAM读数据,读指令执行后Y地址计数器自动加1。从DD RAM读数据前要先执行“设置页地址” 及“设置列地址”命令.
LCD初始化时,根据指令格式,先进行读忙碌检查。当BF为低电平时,设置显示起始行为第一行,A0~A5位为0,然后设置屏幕显示为开,具体做法见参考代码。
3.4.2 键盘扫描程序
键盘扫描通过红外接收头来检测,与P3.2(中断输入)引脚相连。程序初始化时将IT0置0,表示外部中断使用电平触发方式,低电平可引起中断。任何键的按下均会使P3.2电平为低,进入中断服务程序。中断服务程序先将P2口数据取出,根据按键解释子程序来模拟使用哪个锤子来打小地鼠。
3.4.3 显示液晶汉字
液晶显示器的数据线是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 地址
0 1 2 ……………………
…
6
1
6
2
6
3
X地
址
DB0
∫ PAGE0
DB7
X=0
DB0
∫ PAGE1
DB7
X=1
∷
∷
∷
∷
DB0
∫ PAGE6
DB7
X=7
DB0
∫ PAGE7
DB7
3.4.4 地鼠的随机出现
地鼠的出现是一种随机行为,所以必须做一个随机数,而且地鼠出现的位置不能超出规定的游戏界面。这里使用程序中的定时计数器的低八位TL0的数值,由于TL0不断变化,不同的时间点数值不同,TL0%33来表示我们之前定义的显示区数组。
5 设计心得体会
我们这次的课程设计的方案是通过OCM-12864-1液晶显示屏显示。我们的硬件电路比较简单,只要单片机最小系统加一红外遥控,所以我们还加入了程序下载模块。通过这次主题设计,使我对C语言的基本使用更加熟练,同时也增加我对C语言模块化程序设计的一些认识。在调试过程中通过和同学的交流,也增加了合作的技巧。通过查阅资料也学到了一些课本上没有的知识,拓宽了自己的知识面,增加了学习C语言的信心。平时我也做了很多实验,写了很多小程序。很多子程序都是固定的,直接调用就可以了,只要改一下参数就可以。这大大方便了我们的设计。为我们节省了横多时间。这次主题报告的设计让我受益匪浅,无论从知识上还是其他个方面。通过课程设计能够理论联系实际
的学习,开阔了我们的眼界,也提高了单片机知识的理解水平。在这次主题报告设计中又让我体会到了合作与团结的力量,当我遇到问题,我就会和同组同学讨论或者是同学之间相互帮助。团结就是力量,无论在现在的学习中还是在以后的工作中,团结都是至关重要的,有了团结就会有更多的理念、更多的思维、更多的情感。
单片机是一门很重要的课程。最后感谢白老师对我们上课时对我们的精心引导和帮助,同时
也感谢同学对我们的帮助。
6 附录
6.1总原理图
复位电路
P 3.2/C S 1
红外接收
Tex
502
6.2单片机程序代码
/*******************************************************************************
●描述:
●12864标准字库液晶演示数据p0,控制p2 *
********************************************************************************/
#include
#include
#include"vadef.h"
#include"music1.h"
#include "hwyk.h"
/*****************************************************************
定时器初始化
******************************************************************/
void inti0()
{ TMOD=0X11;
TH1=0XEC;
TL1=0X78;
TR1=1;
EA=1;
ET0=1;
PT0=1;
//ET2=1; //定时器2中断用于红外控制
TR0=0;
TH2=0X00; //装载初值
TL2=0X06;
TR2=1;
}
/********************************************************* * * 主函数* * *********************************************************/
void main()
{
inti0();
delay(100); //上电,等待稳定
lcd_init(); //初始化LCD
while(1)
{ while(!TF1);
TF1=0;
TH1=0XEC;
TL1=0X78;
t1s++;
if(irok) //如果接收好了进行红外处理
{
Ircordpro();
{key=IRcord[2];
irpro_ok=0;
}
irok=0;
}
switch(sta)
{ case 0:work0();break;
case 1:work1();break;
case 2:work2();break;
case 3:work3();break; //游戏设置模块
case 4:work4();break; //倒计时播放音乐进入游戏模块
case 5:work5();break; //显示游戏结束界面
default:break;
}
}
}
void work0()
{ lcd_pos(1,0); //设置显示位置为第一行
for(i=0;i<16;i++)
{lcd_wdat(DIS1[i]);}
lcd_pos(2,0); //设置显示位置为第二行
for(i=0;i<16;i++)
{lcd_wdat(DIS2[i]);}
lcd_pos(3,0); //设置显示位置为第三行
for(i=0;i<16;i++)
{lcd_wdat(DIS3[i]);}
lcd_pos(4,0); //设置显示位置为第四行
for(i=0;i<16;i++)
{lcd_wdat(DIS4[i]);}
if(t1s==400)
{ t1s=0;
lcdflag();
clr_screen(); //清屏
sta=1;
}
}
/************************************************************/ void work1() //进入游戏界面
{ lcd_pos(1,0); //设置显示位置为第一行
for(i=0;i<16;i++)
{lcd_wdat(DIS11[i]);}
lcd_pos(2,0); //设置显示位置为第二行
for(i=0;i<16;i++)
{lcd_wdat(DIS22[i]);}
for(i=0;i<16;i++)
{lcd_wdat(DIS33[i]);}
song(TwoMouse);
}
/************************************************************/
void work2() //游戏模块
{ if(!dispflag)
{photodisplay(Photo1);
dispflag=1;
}
if(ts++==400)
{sta=3;
ts=0;
dispflag=0;
clr_screen();
}
}
/****************************************************************/ void work3() //游戏初始化模块
{
levsel(void);
}
/******************************************************************/ void work4()
{ uchar i;
lcd_pos(2,0); //设置显示位置为第2行
for(i=0;i<16;i++)
{lcd_wdat(lcdtime[i]);}
lcd_wcmd(0x88); //设置显示位置为第三行
for(i=0;i<16;i++)
{lcd_wdat(0x04);}
if(ts++==200)
{
ts=0;
if(lcdtime[10]!=0)
{ lcdtime[10]-=1;
if(lcdtime[9]==0&&lcdtime[10]==0)
sta=6;
}
else
{lcdtime[9]=0;
lcdtime[10]=9;
}
}
/************************************************************/
void work5()
{ if(!dispflag)
{photodisplay(Photo2);
dispflag=1;
}
if(ts++==400)
{sta=3;
ts=0;
dispflag=0;
clr_screen();
}
}
/************************************************************/
void work6() //游戏主体
{ swap(void);
}
/*******************************************************************/ void swap(void) //打地鼠子程序
{
if(time==count)
{
photodisplay(disp[s][]);
dispflag=1;
if(s<12)
s++;
else
s=0;
}
checkkey();
if((keynum1==seq[s])&(keynum1!=0)) //按对键出现下一个地鼠
{ score[9]+=h;
time=count;
gamenum++;
keynum1=0;
if(gamenum>=gamenum1)
{ level+=1;
gamenum=0;
}
}
else //按错键马上结束
{ sta=5;
}
}
void levsel(void) //根据关卡来选择得分和转换速度
{
lcd_pos(1,0); //设置显示位置为第一行
for(i=0;i<16;i++)
{lcd_wdat(game1[i]);}
lcd_pos(2,0); //设置显示位置为第二行
for(i=0;i<16;i++)
{lcd_wdat(game2[i]);}
lcd_pos(1,0); //设置显示位置为第一行
for(i=0;i<16;i++)
{lcd_wdat(levels[i]);} //游戏设置if(key==0x01){key=0;level++;levels[10]+=1;if(level==6){level=0;levels[10]=0;}}
switch(level)
{case 1: { gamenum1=10;count=100;h=1;}
case 2: { gamenum1=20;count=200;h=2;}
case 3: { gamenum1=30;count=300;h=3;}
case 4: { gamenum1=40;count=400;h=4;}
case 5: { gamenum1=50;count=500;h=5;}
default:break;
}
}
/******************按键解释函数***********************************/
void checkkey(void) //按键解释
{
switch(key)
{case 0x01: key=0;keynum1=1;break;
case 0x02: key=0;keynum1=2;break;
case 0x03: key=0;keynum1=3;break;
case 0x04: key=0;keynum1=4;break;
case 0x05: key=0;keynum1=5;break;
case 0x06: key=0;keynum1=6;break;
case 0x07: key=0;keynum1=7;break;
case 0x08: key=0;keynum1=8;break;
default:break;
}
}
/*******************************************************************/
/* */
/* 延时函数*/
/* */
/*******************************************************************/
void delay(int ms)
{
{
uchar i;
for(i=0;i<150;i++)
{
_nop_();
_nop_();
_nop_();
_nop_();
}
}
}
/*******************************************************************/ /* */ /* 延时函数*/ /* */ /*******************************************************************/ void delay1(int ms)
{
while(ms--)
{
uchar y;
for(y=0;y<100;y++) ;
}
}
/*******************************************************************/ /* */ /*检查LCD忙状态*/ /*lcd_busy为1时,忙,等待。lcd-busy为0时,闲,可写指令与数据。*/ /* */ /*******************************************************************/ bit lcd_busy()
{
bit result;
LCD_RS = 0;
LCD_RW = 1;
LCD_EN = 1;
delayNOP();
result = (bit)(P0&0x80);
LCD_EN = 0;
return(result);
}
/*******************************************************************/ /* */ /*写指令数据到LCD */