中文摘要
本设计介绍了单片机在计算器领域的应用技术,以常用的MCS-51系列(本次选用8051)单片机为基础,简单介绍了单片机与键盘之间以及与显示器之间的应用技术。
本次设计中的键盘的识别是一个非常重要的部分,我们此次所用的是非编码键盘,只需要预先写入识别程序,这种方法使得我们的硬件设计变的简洁许多。在设计过程中由于实际情况的要求,我们利用部分器件对单片机进行了数据扩展,以满足计算器系统的工作要求。
设计中利用键盘进行数据的输入,然后通过单片机对其进行处理,最后就可以通过LCD显示器显示出来。
关键字:单片机;键盘;显示器;LCD
前言
单片机在计算器中的应用是非常普遍的,我们对计算器的功能及工作顺序都非常熟悉。但是却很少知道它的内部结构以及工作原理。计算器由单片机组成核心,通过对输入的数据进行一系列操作后,将其输出,利用显示器显示出来。输入设备键盘我们都很熟悉,输出设备显示器我们通常都是用液晶显示技术。
本次设计所用的单片机是MCS-51系列,要求1、了解计算器的基本原理。2、进一步掌握数字键盘和多位数码显示管的联合使用。3、实现加、减、乘、除的运算(可显示负数),和清零、溢出报错的显示。
系统的开发过程
第一节概述
本次设计总体上可分为三个部分:
第一部分:硬件部分。主要介绍本次设计过程所用到的硬件器件,包括单片机、门电路、键盘以及显示器等。键盘主要负责数据的输入,单片机主要是对输入的数据进行处理,并把处理后的数据输送进输出设备即显示器中去。门电路器件主要起到辅助、扩展以及保护电路作用。
其总体设计框如图一如下:
图一:总体设计框图
第二部分:软件部分。因为本次设计使用的硬件采用非编码技术,只有给硬件注入程序,系统才能正常工作,软件的设计决定了系统的功能,是单片机设计最主要的工序。其主要介绍主要介绍每个重要部分的工作流程图,通过分析流程图得出设计所需要的程序,而且让人看起来更易懂,更直观。
第二节硬件部分
第1小节 AT89C51简介
AT89C51是美国ATMEL公司生产的低功耗,高性能CMOS8位单片机,片内含4kbytes的可编程的Flash只读程序存储器,兼容标准8051指令系统及引脚,并集成了 Flash 程序存储器,既可在线编程(ISP),也可用传统方法进行编程,因此,低价位AT89C51单片机可应用于许多高性价比的场合,可灵活应用于各种控制领域,对于简单的测温系统已经足够。单片机AT89C51具有低电压供电和体积小等特点,四个端口只需要两个口就能满足电路系统的设计需要,很适合便携手持式产品的设计使用系统可用二节电池供电。芯片AT89C51的引脚排列如图二所示:
图二:AT89C51单片机引脚图
第2小节 门电路
本次设计中我们共用到了74LS373、74LS00、7407三种门电路芯片。其中,74LS373为三态输出的八D 透明锁存器, 74LS00 为四组 2 输入端与非门(正逻辑)、7407 为集电极开路输出的六高压输出缓冲器/驱动器(OC,30V)。
第3小节 LCD显示器
本次设计选用的是LCD1602芯片,它是字符型显示器。 液晶显示模块具有体积小、功耗低、显示内容丰富等特点,现在字符型液晶显示模块已经是单片机应用设计中最常用的信息显示器件了。 本实验以常见的1602字符型LCD模块为例,介绍该模块的简单使用。1602可以显示2行16个字符,有8位数据总线D0-D7,和RS、R/W、E三个控制端口,工作电压为5V,并且带有字符对比度调节和背光。其引脚图如图三所示:
图三:LCD显示引脚图
第4小节 键盘
键盘是若干个按键组成的开关矩阵,它是最简单的仪器输入设备,通过键盘输入数据或命令,实现简单的人机对话。键盘上闭合键的识别是由专用硬件实现的键盘,称为编码键盘,靠软件实现识别的键盘称为非编码键盘。单片机系统由于应用的要求常使用非编码键盘,因此本次设计所采用非编码键盘,如图四所示:
第二节软件部分
第1小节 总程序流程图
图五:总程序流程图
第2小节 键盘扫描程序流程图
键盘扫描程序最主要的功能是寻找是否有键被按下,并确定被按键的键值,使其输入到相应的程序内部,从而运行运算程序,并得出相应的结果。之后就可以通过输出设备输出结果。
图六:键盘扫描流程图
由于机械触点的弹性作用,在闭合及断开的瞬间均伴随有一连串的抖动,抖动时间的长短由按键的机械特性决定,一般为5~10ms,键抖动会引起一次按键被误读多次。而按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。所以一般不会出现问题。
第3小节 显示程序流程图
LCD显示是本次设计的主要部分之一,计算器的显示方式主要采用的是动态显示。开始时的初始状态为“0”,我们输入数据时都将显示于显示器上,当计算结果在显示器显示范围内时,可以正常的显示结果,之后可继续进行计算;反之,则显示无效标志,显示器将显示我们设置的“Exception”,如想继续运算,必须回到初始状态。
图七:显示程序流程图
系统测试情况
进入系统,鼠标按键方可进入计算,计算可以实现加、减、乘、除的运算(可显示负数),和清零(ON/C)、溢出报错的显示。当溢出或者报错时候,屏幕显示Exception,如图八为运行界面原理图和显示“Exception”所示:
图八:运行界面原理图
系统的优点与不足
优点:可以进行简单的数值运算并显示,可以显示负数有溢出,报错和清零功能,而且实现简单。
缺点:不能做过于复杂的运算,例如多项式的运算;没有实现小数点的功能。
参考文献
1.《单片机C语言程序设计实训100例—基于8051+Proteus仿真》彭伟编著
2.《基于PROTEUS软件的计算器设计》http://wenku.baidu.com/link?url=cBmorV2mNwS1YYSUI_BNuNfakVKXN8_V8SIwG0Hc95UnOswBAgOwdiv_OVnoqJqBirJo88CpVX1ppiqhekPZM6R_xudla46P4XN7eOWq6vq
附录代码
Calculator.c:
#include<reg51.h>
#include<ctype.h>
#include"calc.h"
static data long lvalue;
static data long rvalue;
static data char currtoken;
static data char lasttoken;
static data char lastpress;
static xdata char outputbuffer[MAX_DISPLAY_CHAR];//输出缓冲
//将浮点数转换成ASCII字符串
char *calc_decascii(long num)
{
long data temp=num;
char xdata *arrayptr=&outputbuffer[MAX_DISPLAY_CHAR];
long data divisor=10;
long data result;
char data remainder,asciival;
int data i;
//如果计算结果为0,则在缓冲中插入0并结束
if(!temp)
{
*arrayptr='0';goto done;
}
if(temp<0)//处理负数
{
outputbuffer[0]='-';temp-=2*temp;
}
for(i=0;i<sizeof(outputbuffer);i++)
{
remainder=temp%divisor;
result=temp/divisor;
if((!remainder)&&(!result))*arrayptr=' ';
else
{
asciival=remainder+'0';
*arrayptr=asciival;
}
temp/=10;
//为“-”号保留位置
if(arrayptr!=&outputbuffer[1])arrayptr--;
}
done:return outputbuffer;
}
//调用output和clearscreen向LCD输出ASCII
void calc_display(char buf[MAX_DISPLAY_CHAR])
{
int data i=0;
clearscreen();
for(i;i<=MAX_DISPLAY_CHAR;i++)
{
if(buf[i]!=' ')output(buf[i]);
}
}
//根据操作状态输出
void calc_output(int status)
{
switch(status)
{
case OK:calc_display(calc_decascii(lvalue));break;
case SLEEP:break;
case ERROR:calc_display("Exception");break;
default :calc_display("Exception");break;
}
}
//调用input搜索按键,并返回按键的ASCII码值
char calc_getkey()
{
char data mykey;
do{
mykey=input();
}
while(mykey==0);
return mykey;
}
//测试按键是数组还是操作符,如果是数字则返回1,是操作符返回0
char calc_testkey(char key)
{
if(isdigit(key))return 1;else return 0;
}
//将ASCII字符串转换成浮点数
long calc_asciidec(char *buffer)
{
long data value;
long data digit;
value=0;
while(*buffer!=' ')
{
digit=*buffer-'0';
value=value*10+digit;
buffer++;
}
return value;
}
//检查待显示数据的上界和下界
int calc_chkerror(long num)
{
if(num>=-9999999&&num<=9999999)return OK;else return ERROR;
}
//根据运算符按键进行运算处理
void calc_opfunctions(char token)
{
char data result;
switch(token)
{
case '+':
if(currtoken=='='||isdigit(lastpress))
{
lvalue+=rvalue;
result=calc_chkerror(lvalue);
}
else result=SLEEP;break;
case '-':
if(currtoken=='='||isdigit(lastpress))
{
lvalue-=rvalue;
result=calc_chkerror(lvalue);
}
else result=SLEEP;break;
case '*':
if(currtoken=='='||isdigit(lastpress))
{
lvalue*=rvalue;
result=calc_chkerror(lvalue);
}
else result=SLEEP;break;
case '/':
if(currtoken=='='||isdigit(lastpress))
{
if(rvalue)
{
lvalue/=rvalue;
result=calc_chkerror(lvalue);
}
else result=ERROR;
}
else result=SLEEP;
break;
case 'C':lvalue=0;rvalue=0;currtoken='0';lasttoken='0';
result=OK;
break;
default:result=SLEEP;
}
calc_output(result);
}
//获取按键值并进行计算
void calc_evaluate()
{
char data key;
int data i;
char xdata number[MAX_DISPLAY_CHAR];
char xdata *bufferptr;
//清楚缓冲
for(i=0;i<=MAX_DISPLAY_CHAR;i++)number[i]=' ';
bufferptr=number;
while(1)
{
key=calc_getkey();
if(calc_testkey(key))
{
if(bufferptr!=&number[MAX_DISPLAY_CHAR-1])
{
*bufferptr=key;
calc_display(number);
bufferptr++;
}
}
else
{
if(lasttoken=='0')lvalue=calc_asciidec(number);
else rvalue=calc_asciidec(number);
//清楚数字缓冲
bufferptr=number;
for(i=0;i<MAX_DISPLAY_CHAR;i++)number[i]=' ';
//处理操作符
currtoken=key;
if(currtoken=='C')calc_opfunctions(currtoken);
else calc_opfunctions(lasttoken);
//清除输出缓冲
for(i=0;i<=MAX_DISPLAY_CHAR;i++)outputbuffer[i]=' ';
bufferptr=number;
if(currtoken!=0x3D)lasttoken=currtoken;
}
lastpress=key;
}
}
void main()
{
//初始化变量
lvalue=0;
rvalue=0;
currtoken='=';
lasttoken='0';
//初始化LCD
initialise();
calc_output(OK);
calc_evaluate();
}
calc.h:
typedef unsigned short WORD;
typedef unsigned char BYTE;
//定义适合屏幕显示的ASCII字符的最大个数
#define MAX_DISPLAY_CHAR 9
//错误状态处理
enum ERROR{OK=0,SLEEP=1,ERROR=2};
void calc_evaluate();
void calc_opfunctions(char token);
char calc_testkey(char ch);
long calc_asciidec(char *buffer);
char *calc_decascii(long num);
int calc_chkerror(long num);
void calc_output(int status);
char calc_getkey();
void calc_display(char buf[MAX_DISPLAY_CHAR]);
void initialise();
char input();
char output(char ch);
void clearscreen();
lcd.c:
#include<reg51.h>
#include<absacc.h>
#define uchar unsigned char
#define LCD_CMD_WR 0x00
#define LCD_DATA_WR 0x01
#define LCD_BUSY_RD 0x02
#define LCD_DATA_RD 0x03
#define LCD_PAGE 0x80
#define LCD_CLS 1
#define LCD_HOME 2
#define LCD_SETMODE 4
#define LCD_SETVISIBLE 8
#define LCD_SHIFT 16
#define LCD_SETFUNCTION 32
#define LCD_SETCGADDR 64
#define LCD_SETDDADDR 128
sbit bflag=ACC^7;//忙标志位
void wrcmd(uchar);
void output(char);
//忙等待
void busywait()
{
uchar volatile pdata *p=LCD_BUSY_RD;
P2=LCD_PAGE;
do
{
ACC=*p;
}while(bflag==1);
}
void busywait();
//向LCD写命令
void wrcmd(uchar cmd)
{
uchar volatile pdata *p=LCD_CMD_WR;
P2=LCD_PAGE;
*p=cmd;
busywait();
}
//初始化LCD
void initialise()
{
wrcmd(0x30);//1行8位
wrcmd(LCD_SETVISIBLE+4);//显示开,关光标
wrcmd(LCD_SETDDADDR+15);//从右边开始显示
wrcmd(LCD_SETMODE+3);//递增左移
}
//清屏并将显示位置起点设置在最右边
void clearscreen()
{
wrcmd(LCD_CLS);
wrcmd(LCD_SETDDADDR+15);
}
//向LCD写一个字符
void output(char ch)
{
char volatile pdata *p=LCD_DATA_WR;
P2=LCD_PAGE;
*p=ch;
busywait();
}
keypad.c:
//返回按键的ASCII码
#include<reg51.h>
#include<intrins.h>
char code keycodes[]=
{
'7','8','9','/',
'4','5','6','*',
'1','2','3','-',
'C','0','=','+'
};
char data keyflags[16];
//获取键盘子程序
char input()
{
char *pKeyflag=keyflags;//获取按键指针
char RowAddr=0xef;//键盘行地址1110 1111
char ColData=0;//列数据
char i,j,Tmp=0;
for(i=0;i<4;i++)
{
P2=RowAddr;//在P2端口设置行地址
_nop_();
Tmp=P1&0x0f;//从P1端口读取列数据
for(j=0;j<4;j++)
{
ColData=Tmp;//保存当前列数据
if((Tmp&0x01)==0x00)//如果有键按下
{
Tmp=*pKeyflag;
*pKeyflag=1;//标识按键
if(Tmp==0)//有新按键按下
{
P2=0xff;
return keycodes[pKeyflag-keyflags];
}
}
else *pKeyflag=0;
pKeyflag++;
Tmp=ColData>>1;
}
RowAddr<<=1;
}
P2=0xff;
return 0;
}