通过本次课题设计,应用《单片机应用基础》、《数据结构》等所学相关知识及查阅资料,完成实用计算器的设计,以达到理论与实践更好的结合、进一步提高综合运用所学知识和设计的能力的目的。
通过本次设计的训练,可以使我在基本思路和基本方法上对基于MCS-51单片机的嵌入式系统设计有一个比较感性的认识,并具备一定程度的设计能力。
1.1.2 设计要求
在本次课程设计中,主要完成如下方面的设计要求:
1、掌握MCS-51系列某种产品(例如8051)的最小电路及外围扩展电路的设计方法;
2、计算器能实现加、减、乘、除、平方、开方、N次方、开N次方、正弦函数、 余弦函数、正切函数、反正弦、反余弦、反正切、对数运算、阶乘(N<=34)、排列、 组合、累加等十九种功能。
3、支持浮点数运算;
4、较为友好的界面显示,对输入实时显示,对计算结果输出显示;
5、能够具备比较完善的报错系统
1.2 方案论证及选择
基于设计要求,笨设计考虑了两种设计方案,他们均可以实现计算器的功能,但基于设计目的及微控制器的广泛运用,比较两种方案的优劣,最终选择基于51单片机的计算器设计。
1.2.1 方案一 采用FPGA控制
FPGA是一种高密度的可编程逻辑器件,自从Xilinx公司1985年推出第一片FPGA以来,FPGA的集成密度和性能提高很快,其集成密度最高达500万门/片以上,系统性能可达200MHz。由于FPGA器件集成度高,方便易用,开发和上市周期短,在数字设计和电子生产中得到迅速普及和应用,并一度在高密度的可编程逻辑器件领域中独占鳌头。
但是而基于 SRAM编程的FPGA,其编程信息需存放在外部存储器上 ,需外部存储器芯片 ,且使用方法复杂 ,保密性差,而其对于一个简单的计算器而言,实用FPGA有点大材小用,成本太高。
1.2.2 方案二 采用STC89C52
单片机是单片微型机的简称,故又称为微控制器MCU(Micro Control Unit)。通常由单块集成电路芯片组成,内部包含有计算机的基本功能部件:中央处理器CPU,存储器和I/O接口电路等。因此,单片机只要和适当的软件及外部设备相结合,便可成为一个单片机控制系统。单片机广泛用于智能产品,智能仪表,测控技术,智能接口等,具有操作简单,实用方便,价格便宜等优点,而其中AT89S52以MCS-51为内核,是单片机中最典型的代表,应用于各种控制领域。
1.2.3 方案比较及选择
通过以上两种方案论证和比较,从设计的实用性,方便性和成本出发,选择了以STC89C52单片机作为中央处理单元进行计算器的设计,这样设计能够实现对六位浮点数的加减和三位浮点数的乘除运算。
2 单元电路设计
2.1 工作原理
利用矩阵键盘进行按键的输入,通过对矩阵键盘的扫描,获取用户的输入,并实时的显示在1602液晶上,每次获取到输入时,根据软件设计的相应方法对输入进行处理、运算,输入结束后(以“=“为标志),将最终的运算结果输出的液晶上。
系统组成及整体框图如图2.1所示。
图2.1 系统组成及总体框图(见附件)
2.2 硬件电路设计
2.2.1 单片机电路设计
为使单片机正常工作,除电源供电部分外,还需提供晶振电路和复位电路。具体电路如下:
图2.2 单片机工作电路
由图2.2可知,9脚外接的是按键复位电路,18,19脚外接的是晶振电路,这样,就构成了单片机正常工作的必备电路。同时,为使P0口正常工作,并增加其带负载能力,P0口需接了上拉电阻。图中EA为外部访问允许,欲使CPU仅访问外部程序存储器(地址为0000H-FFFFH),EA端必须保持低电平(接地)。在这里,STC89C52单片机8k的程序存储器已经够本设计使用,无需外部程序储存器,故EA直接接高电平。
2.2.2 键盘模块电路
图2.3是键盘电路,共20个按键,用来实现人机交互和运算表达式的输入,S0~S3、S5~S8、S10~S13、S15~S18共16个按键组成一组4*4的矩阵键盘,行线第一行到第四行分别接在P3.0~P3.3口,列线第一列到第四列分别接在P3.4~P3.7口,这样P3口就完成了对4*4的矩阵键盘的接线。同时,注意到按键数量还达不到要求,故增加了四个独立按键S4,S9,S14,S19。他们依次接在P20~P23口。可见,矩阵键盘相对来讲更节省I/O口,但本着学习的目的,加之本设计并不需要太多的I/O口,故为充分学习和利用资源,在这里也设计了4个这样的独立按键。
图2.3 键盘模块电路
2.2.3 蜂鸣器提示电路
蜂鸣器主要用于按键时发出声音,提示当前的按键操作,电路如图2.5所示,三极管主要用于驱动蜂鸣器,因为单片机I/O的驱动能力有限。同时单片机I/O口还在这里还起到开关作用,为‘0’时打开蜂鸣器通道,使蜂鸣器发声。
图2.4 蜂鸣器提示电路
2.2.4 液晶显示电路
LCD也是本次设计的重要组成部分之一,主要用于显示输入和输出。电路如图2.5所示,LCD数据端与单片机P0口相连,控制端与P24~P26连接,电位器用于调节对比度。
图2.5 LCD显示电路
至此,整个电容测量仪的硬件设计部分就基本设计完成,接下来,需要的就是与之相匹配的软件支持了。
2.3 软件设计
软件编程平台选择最常用的keil软件。由于该程序并未涉及到底层的驱动问题,因此选择方便快捷的C语言编程。在编程中,将该程序分为四个模块:延时模块、1602显示模块、用于处理计算表达式的对战模块及主函数模块。采用模块化设计,方便调试与理解。具体程序见附录二。在这里重点介绍软件核心的表达式处理程序算法。
2.3.1 键盘扫描
独立键盘很好实现,只需不停的检测即可,出现低电平即出现按键,在经过一定的延时消抖,再确认判断即可。
矩阵键盘扫描程序,首先读出P3的低四位,然后读出P3口的高四位。然后确定键值并显示缓存,最终将按键的值通过一个预先定义好的数组转换为相应的ASCII码值送给LCD显示和与表达式相应的堆栈进行处理,读键程序使用的是反转法读键,不管键盘矩阵的规模大小,均进行两次读键。第一次所有行线均输出高电平,从P3口的值读入键盘信息(行信息);第二次所有列线均输出高电平,从P3口的值读入键盘信息(列信息)。
2.3.2 表达式的处理
表达式包含加、减、乘、除、括号等,必须按照相应的优先级运算,才可能得出正确的结果。在这儿采用栈结构,可以有效的进行表达式的处理。
栈结构具有“后进先出”的固有属性,借助这个属性我们可以随时对刚输入的元素进行操作,从而实现边输入边计算。
为了实现算符优先算法。可以使用两个工作栈。一个称为OPTR,用以寄存运算符,另一个称做OPND,用以寄存操作数或运算结果。
1.首先置操作数栈为空栈,表达式起始符”#”为运算符栈的栈底元素;
2.依次读入表达式,若是操作符即进OPND栈,若是运算符则和OPTR栈的栈顶运算符比较优先权后作相应的操作,直至整个表达式求值完毕(即OPTR栈的栈顶元素和当前读入的字符均为”#”)。
在这里,相应的处理指的是,如果当前符号的优先级比栈顶优先级低,则将该符号继续压入堆栈,不做其它操作;如果当前符号的优先级比栈顶优先级高,则依次取出操作数栈的栈顶两个数据和符号栈的栈顶符号进行这两个数的运算,运算结果数据再压入操作数栈中。若优先级相等,则弹出符号栈栈顶符号。算符间的优先关系如下(‘#‘表示开始和结束):
+
-
*
/
(
)
#
+
>
<
<
<
<
>
>
-
>
>
<
<
<
>
>
*
>
>
>
>
<
>
>
/
>
>
>
>
<
>
>
(
<
<
<
<
<
=
)
>
>
>
>
>
>
#
<
<
<
<
<
=
表2.1 运算符优先级表
2.4 altium designer 原理图设计及PCB制作
2.4.1 原理图设计
图2.6 原理图设计(1)——单片机部分
图2.7 原理图设计(2)——键盘、蜂鸣器部分
2.4.2 PCB制作
图2.8 PCB设计(1)——单片机部分
图2.9 原理图设计(2)——键盘、蜂鸣器部分
注:在此并没有布双层板,红色的线仅仅只是为了标志出跳线或者本质上实物已经连接上了。
2.4.3 设计结果
图2.10 设计结果实物图
如图所示,各个按键功能如图中文字说明,电源为5V直流电源。
3系统测试
测试主要测试其运算是否正确,及检错能力。
3.1 整数运算
在此以整数运算为例,介绍计算器的使用方法即流程。首先打开电源,看到LCD打开显示,说明运行正常,接着,输入表达式:12*(56+23)*2,其结果本身应为1896。
输入结束后,点“=”按键,即可在第二行显示出运算结果,由图可见运算完全正确。在使用时,可以通过声音开关按钮控制按键音的打开和关闭,在LCD上也有显示,如果再第二行第一个位置没有显示,则没有打开声音,可以通过按钮打开。若有显示,则声音已经打开,可以通过按钮关闭声音。如图3.1所示,此时已经打开声音。
图3.1 整数运算
3.2 浮点数运算
由图可以看出,可以进行浮点数运算,还可以从第二行第一个字符,蜂鸣器处于关闭状态。
3.3 输入出错的情况
输入一个错误的表达式,如图3.3所示。
图3.3 输入错误的情况下
再按“=”号,将会出现出错画面。如图3.4所示。
图3.4 输入错误的显示
由以上测试可知,整个设计运行正常,能够正确的进行运算和出错提示。由此可得,整个设计是成功的。
4 结论与心得体会
总之,通过一系列仿真和设计,基于单片机的计算器设计还是比较成功的做出来了。一路下来还是比较坎坷,从原理到实物,从调试到调试成功,遇到了很多问题,特别是在软件编程时,开始以为既然单片机具有数据处理与运算的能力,那么用它来做一个计算器应该很简单了,可是,后面实际操作才知道,当计算表达式时,优先级问题非常重要,一开始用了很多if语句来实现,程序繁琐复杂,且效果不是很好,很容易出错,最后通过查阅相关资料,了解到利用数据结构中栈的思想来解决这一问题就很方便。但在实际写程序时也遇到了很多问题,但最终还是克服难关,将整个软件比较完善的实现了。
在硬件的原理图及PCB设计中,也遇到了很多问题,先做模块后做主板,导致我后面的布线就很麻烦,这也教会了我一些经验,在PCB分模块设计中,模块与模块之间的连接也是必须考虑到的,从左端连接还是从右端连接,都直接影响到整个PCB板的设计。
总之,通过这次设计也收获了很多,知识层面上,学得了很多新知识,解决问题的新方法,思考问题的新方向。实践方面,提高了动手能力,提高了解决实际问题的能力等等。在思想上,更加明白的坚持不懈的重要性,学习探索的重要性,实践动手的重要性。
5 参考文献
【1】 《单片机基础》第三版 李广弟 朱月秀 冷祖祁 编著 北京航天大学出版社,2007
【2】 《数据结构》严蔚敏 编著 清华大学出版社
附录1 元器件清单
(1)晶振12M一个
(2)stc89c52芯片一片
(3)30pf 2个;10uf 1个;
(4)40脚活动底座一个
(5)LCD液晶一个
(6)按键21个
(7)发光二极管1个
(8)9引脚排阻 1个
(9)680Ω 1个;10k 2个;1k 1个;10k滑动变阻器 1个; 10Ω 1个;
(10) 开关1个
(11) 5V有源蜂鸣器1个
(12) 三极管S8550一个
(13)排针若干,杜邦线若干
单片机源程序如下:
#include <REGX51.H>
#include<intrins.h>
#include<stdio.h>
#include<string.h>
#include<math.h>
sbit RS = P2^5;
sbit RW = P2^6;
sbit E = P2^7;
#define Data P0//数据端口
//全局变量
float num1;//第一操作数num1 (初始为零)
float num2;//第二操作数num2 (初始为零)
char yun_flag='+';//运算符号 yun_flag (默认为+ )
char key_last;//上次按键状态标志 key_last
char sqrt_flag;//开方键按下标志 sqrt_flag
unsigned long pos_flag=1;//小数位权pos_flag (默认为1)
char neg_flag;//正负标志 neg_flag
char data c_num1[15]=" ";//c_num1 字符型第一操作数 (数组不定义大小与其他变量冲突)
char data c_num2[15]=" ";//c_num2 字符型第二操作数
char error;//运算法则错误;
char len;//小数点后位数
/* 微秒延时函数 */
/******************************************************************/
void DelayUs(unsigned char us)//delay us
{
unsigned char uscnt;
uscnt=us>>1; /*12MHz频率*/
while(--uscnt);
}
/******************************************************************/
/* 毫秒函数声明 */
/******************************************************************/
void DelayMs(unsigned char ms)
{
while(--ms)
{
DelayUs(250);
DelayUs(250);
DelayUs(250);
DelayUs(250);
}
}
/******************************************************************/
/* 写入命令函数 */
/******************************************************************/
void WriteCommand(unsigned char c)
{
DelayMs(5);//操作前短暂延时,保证信号稳定
E=0;
RS=0;
RW=0;
_nop_();
E=1;
Data=c;
E=0;
}
/******************************************************************/
/* 写入数据函数 */
/******************************************************************/
void WriteData(unsigned char c)
{
DelayMs(5); //操作前短暂延时,保证信号稳定
E=0;
RS=1;
RW=0;
_nop_();
E=1;
Data=c;
E=0;
RS=0;
}
/******************************************************************/
/* 写入字节函数 */
/******************************************************************/
void ShowChar(unsigned char pos,unsigned char c)
{
unsigned char p;
if (pos>=0x10)
p=pos+0xb0; //是第二行则命令代码高4位为0xc
else
p=pos+0x80; //是第二行则命令代码高4位为0x8
WriteCommand (p);//写命令
WriteData (c); //写数据
}
/******************************************************************/
/* 写入字符串函数 */
/******************************************************************/
void ShowString (unsigned char line,char *ptr)
{
unsigned char l,i;
l=line<<4;
for (i=0;*(ptr+i)!='\0';i++)
ShowChar (l++,*(ptr+i));//循环显示16个字符
}
/******************************************************************/
/* 初始化函数 */
/******************************************************************/
void InitLcd()
{
DelayMs(15);
WriteCommand(0x38); //display mode
WriteCommand(0x38); //display mode
WriteCommand(0x38); //display mode
WriteCommand(0x06); //显示光标移动位置
WriteCommand(0x0c); //显示开及光标设置
WriteCommand(0x01); //显示清屏
}
/*************按键扫描****************************/
char scan()
{
char h_data,l_data,i,key_num;
P2=P2&0XE0;//P2低5位送0
P1=P1|0X0F;//P1低4位送1
while((P1|0xf0)==0xff)//判断P1低4位是否全为1
{
while((P1|0xf0)==0xff);
DelayMs(10);//延时10ms
}
l_data=~(P1|0xf0);//记入列标志
P2=P2|0X1F;//P2低五位送1
P1=P1&0XF0;//P1低四位送0
h_data=~(P2|0xe0);//记入行标志
for(i=0;i<=4;i++)//计算行号(0~4)
{
if(h_data==1)break;
h_data=h_data>>1;
}
h_data=i;//行号
for(i=1;i<=4;i++)//计算列号(1~4)
{
if(l_data==1)break;
l_data=l_data>>1;
}
l_data=i;//列号
key_num=h_data*4+l_data;
P2=P2&0XE0;//P2低5位送0
P1=P1|0X0F;//P1低4位送1
while((P1|0xf0)!=0xff)//判断P1低4位是否全为1
{
while((P1|0xf0)!=0xff);
DelayMs(10);//延时10ms
}
return key_num;
}
void float_to_char(float a,char* p)
{
char i,flag,length;
flag=0;
sprintf(p,"%f",a);
length=strlen (p);
for(i=0;i<length;i++)
{
if(*(p+i)=='.')flag=1;
}
if(flag==1)
for(i=length-1;i>=0;i--)
{
if(*(p+i)=='.'){*(p+i)='\0';break;}
if(*(p+i)!='0'){*(p+i+1)='\0';break;}
}
}
/**********************更新液晶*************************/
void refresh()//更新液晶
{
char length,i,j;
char dot;
dot=0;
float_to_char(num2,c_num2);//num2转为字符型
length=strlen(c_num2);
for(i=0,j=0;i<length;i++)
{
if(c_num2[i]=='.')
dot=1;
if(dot==1)
j++;
}
if(j<len)
{
if(dot==1)
{
for(i=length;i<(length+len-j);i++)
{
c_num2[i]='0';
}
c_num2[i]='\0';
}
else
{
c_num2[length]='.';
if(len-j>1)
{
for(i=length+1;i<(length+len-j);i++)
c_num2[i]='0';
}
c_num2[length+len-j]='\0';
}
}
if(neg_flag==1)//'+/-'按下首位加‘-’
{
length=strlen(c_num2);//计算c_num2长度
for(i=length-1;i>=0;i--)
{
c_num2[i+1]=c_num2[i];
}
c_num2[length+1]='\0';
c_num2[0]='-';
}
if(sqrt_flag==1)
{
length=strlen(c_num2);//计算c_num2长度
for(i=length-1;i>=0;i--)
{
c_num2[i+1]=c_num2[i];
}
c_num2[length+1]='\0';
c_num2[0]=0xe8;//字符根号
}
if(error==0)
{
float_to_char(num1,c_num1);//num1转为字符型
WriteCommand(0x01); //显示清屏
ShowString(0,c_num1);
ShowString(1,c_num2);
ShowChar(15,yun_flag);
}else
{
WriteCommand(0x01); //显示清屏
ShowString(0,"error!!!");
}
}
//键值处理
void operation1(char keynum)//按下 ’+、-、*、/处理
{
if(key_last==1)//上次按键为 数字、小数点、+/-、sqrt
{
if(neg_flag==1)num2=-num2;//'+/-'按下
if(sqrt_flag==1)//sqrt按下
{
if(num2>=0)
num2=sqrt(num2);
else
error=1;
}
if(yun_flag=='+')num1=num1+num2; //按下的是‘+’
if(yun_flag=='-')num1=num1-num2;//按下的是‘-’
if(yun_flag=='*')num1=num1*num2;//按下的是‘*’
if(yun_flag=='/')//按下的是‘/’
{
if(num2!=0)
num1=num1/num2;
else
error=1;
}
num2=0;//num2清零
sqrt_flag=0;//sqrt_flag清零
neg_flag=0;//neg_flag清零
pos_flag=1;//pos_flag回1
}
if(keynum==4)yun_flag='+';//yun_flag更新
if(keynum==8)yun_flag='-';
if(keynum==12)yun_flag='*';
if(keynum==16)yun_flag='/';
len=0;
key_last=0;//key_last更新
refresh();
}
//
void operation2(char keynum)//输入数字
{
float Data1;
if(keynum==1)Data1=7;//分析输入数字
if(keynum==2)Data1=8;
if(keynum==3)Data1=9;
if(keynum==5)Data1=4;
if(keynum==6)Data1=5;
if(keynum==7)Data1=6;
if(keynum==9)Data1=1;
if(keynum==10)Data1=2;
if(keynum==11)Data1=3;
if(keynum==13)Data1=0;
if(pos_flag==1)//更新num2
num2=num2*10+Data1;
else
{
num2=num2+(Data1/pos_flag);
pos_flag=pos_flag*10;
len++;
}
key_last=1;//更新key_last
refresh();
}
void operation3()//输入小数点
{
if(pos_flag==1)//首次出现小数点
{
pos_flag=pos_flag*10;//小数位权*10
len++;
}
key_last=1;//更新key_last
refresh();//更新液晶
}
/
void operation4()//输入'='
{
if(neg_flag==1)num2=-num2;//'+/-'按下
if(sqrt_flag==1)//sqrt按下
{
if(num2>=0)
num2=sqrt(num2);
else
error=1;
}
if(yun_flag=='+')num1=num1+num2; //按下的是‘+’
if(yun_flag=='-')num1=num1-num2;//按下的是‘-’
if(yun_flag=='*')num1=num1*num2;//按下的是‘*’
if(yun_flag=='/')//按下的是‘/’
{
if(num2!=0)
num1=num1/num2;
else
error=1;
}
num2=0;//num2清零
sqrt_flag=0;//sqrt_flag清零
neg_flag=0;//neg_flag清零
pos_flag=1;//pos_flag回1
yun_flag='+';//yun_flag更新
len=0;
key_last=0;//key_last更新
refresh();
}
void operation5()//输入clear all
{
num1=0;// num1清零
num2=0;//num2清零
sqrt_flag=0;//清sqrt_flag
neg_flag=0;// 清neg_flag
pos_flag=1;// Pos_flag=1
yun_flag='+';// yun_flag(为'+')
error=0;//清error
len=0;
key_last=0;//更新key_flag
refresh();//更新液晶
}
///
void operation6()//输入'C'
{
num2=0;//num2清零
sqrt_flag=0;//清sqrt_flag
neg_flag=0;//清neg_flag
pos_flag=1;//pos_flag=1
len=0;
key_last=0;//key_last
refresh();//更新液晶
}
void operation7()//输入'+/-'
{
if(neg_flag==0)//neg_flag反转
neg_flag=1;
else neg_flag=0;
key_last=1;//key_last
refresh();// 更新液晶
}
/
void operation8()//输入'sqrt'
{
if(sqrt_flag==0)//sqrt_flag反转
sqrt_flag=1;
else sqrt_flag=0;
key_last=1;//更新key_last
refresh();//更新液晶
}
//键值分析
void key(char keynum)
{
switch(keynum)
{
case 4 :
case 8 :
case 12:
case 16://+、-、*、/
{
if(error==0)
operation1(keynum);
break;}
case 1 : //数字7
case 2 : //数字8
case 3 : //数字9
case 5 : //数字4
case 6 : //数字5
case 7 : //数字6
case 9 : //数字1
case 10 : //数字2
case 11 : //数字3
case 13 : //数字0
{
if(error==0)
operation2(keynum);
…………
…………
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/runweipa/article/details/139536072