矩阵键盘简易计算器.docx
《矩阵键盘简易计算器.docx》由会员分享,可在线阅读,更多相关《矩阵键盘简易计算器.docx(23页珍藏版)》请在冰豆网上搜索。
矩阵键盘简易计算器
《微处理器系统与接口技术》课程实践报告
计算器
班级:
学号:
学生姓名:
指导老师:
日期:
2014.7.5
******电子与信息工程学院
1、设计题目:
计算器
2、设计目的
此次课程实践题目是基于单片机简单计数器的设计,本此设计使用的是Intel公司MCS-51系列的8051AH单片机。
设计的计算器可以实现2位小数的加、减、乘、除运算以及整数的乘方运算,其中用4*4矩阵键盘来输入待参与运算的数据和运算符;八位数码管动态显示输入待参与运算的数据以及运算后产生的结果,每个硬件模块的调用过程中涉及到了函数入口及出口参数说明,函数调用关系描述等。
3、计算器总体设计框图
计算器以MCS-51系列的8051AH单片机作为整个系统的控制核心,应用其强大的I/O功能和计算速度,构成整个计算器。
通过矩阵键盘输入运算数据和符号,送入单片机进行数据处理。
经单片机运算后控制LED数码管的输出。
整体框图如图1所示:
图3整体框图
本系统硬件主要由矩阵键盘、独立键盘I/O输入输出、数码管显示等主要部分组成。
各模块的主要功能如下:
(1)矩阵键盘将十六进制编码的数字送到单片机。
(2)单片机扫描键盘信号并接收,对输入的键盘信号进行处理
(3)LED以动态扫描的方式移位显示每次输入的数据和最后的运算结果。
实践设计的具体流程图如下图2所示:
图3整体流程图
4、计算器详细设计过程
(处理器型号型号:
IntelMCS-51系列8051AH)
计算器设计原理图
4.1输入模块
计算器输入数字和其他功能按键要用到很多按键,如果采用独立按键的方式,在这种情况下,编程会很简单,但是会占用大量的I/O口资源,因此在很多情况下都不采用这种方式。
为此,我们引入了矩阵键盘的应用,采用四条I/O线作为行线,四条I/O线作为列线组成键盘。
在行线和列线的每个交叉点上设置一个按键。
这样键盘上按键的个数就为4×4个。
这种行列式键盘结构能有效地提高单片机系统中I/O口的利用率,如图3所示:
图3键盘按键
4.2键盘输入电路
每个按键都有它的行值和列值,行值和列值的组合就是识别这个按键的编码。
矩阵的行线和列线分别通过两并行接口和CPU通信。
键盘的一端(列线)通过电阻接VCC,而接地是通过程序输出数字“0”实现的。
键盘处理程序的任务是:
确定有无键按下,判断哪一个键按下,键的功能是什么?
还要消除按键在闭合或断开时的抖动。
两个并行口中,一个输出扫描码,使按键逐行动态接地;另一个并行口输入按键状态,由行扫描值和回馈信号共同形成键编码而识别按键,通过软件查表,查出该键的功能。
当无按键闭合时,P1.0-P1.3与P1.4-P1.7之间开路;当有键闭合时,与闭合键相连的两条I/O口线之间短路。
判断有无按键按下的方法是:
第一步,置行线P1.4-P1.7为输入状态,从列线P1.0-P1.3输出高电平,读入列线数据,若某一列线为高电平,则该列线上有键闭合。
第二步,读入列线值,将该值输出到列线值,之后再读出列线值。
综合一二两步的结果,最后键盘最后组合码值可确定按键编号。
但是键闭合一次只能进行一次键功能操作,因此须等到按键释放后,再进行键功能操作,否则按一次键,有可能会连续多次进行同样的键操作。
4.3主程序模块
这次设计共涉及到了处理器、矩阵键盘、独立键盘、数码管显示等主要的模块,在软件主程序中,通过函数的调用实现相应的功能模块处理。
通过num=KeyPro()函数的调用实现了将通过4*4矩阵行列反转扫描法得到的数据送入单片机,用于处理器进一步处理数据。
因为该课程实践要求计数器具有2位小数的加减乘除运算,所以我们在读取矩阵键盘之后,使用语句sscanf(temp,"%f",&a),该语句的意思是将字符串数组temp中的字符以浮点数的形式输出到变量a,因此键盘输的的数据都被转换成了浮点数,所以可以很方便的实现带小数的加减乘除运算,运算所得结果也是浮点数的形式。
在对输出结果输出到数码管的过程中,使用语句sprintf(temp,"%f",a);,该语句的意思是把浮点数变量a输出到字符串数组temp中,此时就可以调用显示函数对字符形式的计算结果进行输出。
5、分析与调试
在程序设计方法上,模块化程序设计是单片机应用中最常用的程序设计方法。
设计的中心思想是把一个复杂应用程序按整体功能划分成若干相对独立的程序模块,各模块可以单独设计、编程和调试,然后组合起来。
这种方法便于设计和调试,容易实现多个程序共存,但各个模块之间的连接有一定的难度。
根据需要我们可以采用自上而下的程序设计方法,此方法先从主程序开始设计,然后再编制各从属程序和子程序,层层细化逐步求精,最终完成一个复杂程序的设计。
这种方法比较符合人们的日常思维,缺点是一级的程序错误会对整个程序产生影响。
本次课程设计的计算器主要完成了矩阵键盘的数据或符号输入、数码管动态显示输入值、单片机处理输入的数据、数码管动态显示计算结果。
在整个的设计电路中,输入输出的数据均采用字符的形式(temp[i]=num+'0'(键盘输入)sprintf(temp,"%f",a)(数码管输出));使用在单片机进行数值运算的过程中,数据使用函数sscanf(temp,"%f",&a),即将数据缓冲区的字符型数据转换成浮点型并存到a变量所对应的地址,这样之后在对变量进行加减乘除运算。
因为是浮点数运算,所以结果也是浮点数,在输出的时候,小数点也能够输出,从而免去在运算结果中对小数的定位工作。
按照上面的思想,在进行小数运算时,输入输出小数并不能正常显示。
后来通过和组员一起仔细研究,发现数码管段码中并没有小数点,所以我们在输入数据时将每一个输入的与小数点的ASCII码进行比较(if(num=='.')),一旦条件成立,将小数点存入数据缓冲temp[8]数组中,同时也将小数点的ASCII码存入显示缓冲区TempData[8]中(定义小数点的段码0x98)。
这样之后关于小数点显示问解决题。
本次实践所设计的计算器的操作数为浮点型,当两个数参与运算之后,运算结果如果出现小数部分全为0的情况,根据实际应该只显示整数部分,但是实验结果是小数0全显示。
但是由于时间关系我们没有调试成功。
现提供一个思路:
在显示小数点的时候,设置一个for循环,将小数点后的各位与'0'进行比较,如果该位等于0,设置一个新变量自加一次,之后依次比较,新变量记录小数点后0的个数。
循环结束之后,将变量值与小数点后的总位数进行比较,如果相等说明小数点后全为0.之后设置一个for循环将小数点后的数据的ASCII全部赋值0。
部分程序如下:
sprintf(temp,"%f",a);//以字符的形式打印浮点数到临时缓冲区字符数组temp中
for(s=0;s<8;s++)//由于打印的是ASCII码值
{
if(temp[s]==0x2d)//表示负号,数码管显示负号0x40
TempData[s]=0x40;
elseif(temp[s]==0x2e)//表示小数点,数码管显示小数点0x98
{
for(j=s+1;j<8;j++)
{
if(temp[j]==48)
k++;
}
if(k==7-s)
{
for(;s<8;s++)
TempData[s]=0;
break;
}
else
TempData[s]=0x98;
}
else
TempData[s]=dofly_DuanMa[temp[s]-'0'];//其他0-9则进行ASCII到数字处理,如当前是'3',用'3'-'0'=3
//'3'的16进制是0x33,'0'的16进制是0x30
}
7、运行结果
8、结束语
经过近一周的努力,终于顺利完成了单片机课程实践-计数器的设计。
刚开始,我们头绪不是很清楚,不知道从哪里入手,但通过老师的耐心指导并和同学认真研究设计课题,跑图书馆查资料、确定基本设计方案、对所用芯片功能进行查找、调试、上机仿真等,经历了一次次的困难,却积累了很多宝贵的经验。
在整个设计的过程中遇到的问题主要有以下二点,第一:
基础知识掌握的不牢固,主要表现在一些常用的电路的形式和功能不清楚,对书本上的内容理解不够透彻。
第二:
相关知识掌握的不够全面,缺少系统设计的经验。
这次设计进一步端了我的学习态度,学会了实事求是,严谨的作风,对自己要严格要求,不能够一知半解,要力求明明白白。
急于求成是不好的,我有所感受。
如果省略了那些必要的步骤,急于求成,不仅会浪费时间,还会适得其反。
我觉得动手之前,头脑里必须清楚该怎么做,这一点是很重要的。
就目前来说,我的动手能力虽然差一点,但我想,通过我的不懈努力,在这方面,我总会得到提高。
8、参考文献
[1]C语言程序设计教程/张宗杰主编.-北京:
电子工业出版社,2013.8
[2]增强型51单片机与仿真技术/肖金球,冯骥编著.-北京:
清华大学出版社,2011.10
[3]
[4]
9、源程序附录
9.1主程序
/*-----------------------------------------------
内容:
整数之间运算,含小数运算,有负号运算,
------------------------------------------------*/
#include//包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include
#include
#include"display.h"
#include"delay.h"
#include"keyboard.h"
sbitCF=P2^5;
//unsignedcharTempData[8];存储显示值的全局变量
/*------------------------------------------------
主程序
------------------------------------------------*/
main()
{
unsignedcharnum,i=0,sign;
unsignedchartemp[8];//最大输入8个
bitfirstflag;
floata=0,b=0;
unsignedchars,j,k=0;
Init_Timer0();//初始化定时器0
while
(1)//主循环
{
if(CF==0)
num=0x5e;
else
num=KeyPro();//扫描键盘
if(num!
=0xff)//如果扫描是按键有效值则进行处理
{
if(i==0)//输入是第一个字符的时候需要把该行清空,方便观看
{
for(s=0;s<8;s++)//赋值完成后把缓冲区清零,防止下次输入影响结果
TempData[s]=0;
}
if((num=='+')||(i==8)||(num=='-')||(num=='x')||(num=='/')||(num=='=')||(num=='^'))//输入数字最大位数为8或输入符号表示输入结束
{
i=0;//计数器复位
if(firstflag==0)//如果是输入的第一个数据,赋值给a,并把标志位置1,到下一个数据输入时可以跳转赋值给b
{
sscanf(temp,"%f",&a);//将字符串数组temp中的字符以浮点数的形式输出到变量a
firstflag=1;
}
else
sscanf(temp,"%f",&b);//b中存放temp字符数组的各自对应的ASCII码
for(s=0;s<8;s++)//赋值完成后把缓冲区清零,防止下次输入影响结果
temp[s]=0;
if(num!
='=')//判断当前符号位并做相应处理
sign=num;//如果不是等号记下标志位
else
{
firstflag=0;//检测到输入=号,判断上次读入的符合
switch(sign)
{
case'+':
a=a+b;
break;
case'-':
a=a-b;
break;
case'x':
a=a*b;
break;
case'/':
a=a/b;
break;
case'^':
a=pow(a,b);
break;
default:
break;
}
sprintf(temp,"%f",a);//以字符的形式打印浮点数到临时缓冲区字符数组temp中
for(s=0;s<8;s++)//由于打印的是ASCII码值
{
if(temp[s]==0x2d)//表示负号,数码管显示负号0x40
TempData[s]=0x40;
elseif(temp[s]==0x2e)//表示小数点,数码管显示小数点0x98
{
for(j=s+1;j<8;j++)
{
if(temp[j]==48)
k++;
}
if(k==7-s)
{
for(;s<8;s++)
TempData[s]=0;
break;
}
else
TempData[s]=0x98;
}
else
TempData[s]=dofly_DuanMa[temp[s]-'0'];//其他0-9则进行ASCII到数字处理,如当前是'3',用'3'-'0'=3
//'3'的16进制是0x33,'0'的16进制是0x30
}
sign=0;a=b=0;//用完后所有数据清零
for(s=0;s<8;s++)
temp[s]=0;
}
}
elseif(i<8)
{
if(num=='.')
{
temp[i]=num;
TempData[i]=0x98;
i++;
}
else
{
temp[i]=num+'0';//
TempData[i]=dofly_DuanMa[num];//输出数据
i++;
}//输入数值累加
}
}
}
}
9.2延时函数delay
#include"delay.h"
/*------------------------------------------------
uS延时函数,含有输入参数unsignedchart,无返回值
unsignedchar是定义无符号字符变量,其值的范围是
0~255这里使用晶振12M,精确延时请使用汇编,大致延时
长度如下T=tx2+5uS
------------------------------------------------*/
voidDelayUs2x(unsignedchart)
{
while(--t);
}
/*------------------------------------------------
mS延时函数,含有输入参数unsignedchart,无返回值
unsignedchar是定义无符号字符变量,其值的范围是
0~255这里使用晶振12M,精确延时请使用汇编
------------------------------------------------*/
voidDelayMs(unsignedchart)
{
while(t--)
{
//大致延时1mS
DelayUs2x(245);
DelayUs2x(245);
}
}
9.3显示函数display
#include"display.h"
#include"delay.h"
#defineDataPortP0//定义数据端口程序中遇到DataPort则用P0替换
unsignedcharcodedofly_DuanMa[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//显示段码值0~9
unsignedcharcodedofly_WeiMa[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};//分别对应相应的数码管点亮,即位码
unsignedcharTempData[8];//存储显示值的全局变量
/*------------------------------------------------
显示函数,用于动态扫描数码管
输入参数FirstBit表示需要显示的第一位,如赋值2表示从第三个数码管开始显示
如输入0表示从第一个显示。
Num表示需要显示的位数,如需要显示99两位数值则该值输入2
------------------------------------------------*/
voidDisplay(unsignedcharFirstBit,unsignedcharNum)
{
staticunsignedchari=0;
DataPort=0;//清空数据,防止有交替重影
LATCH1=1;//段锁存
LATCH1=0;
DataPort=dofly_WeiMa[i+FirstBit];//取位码
LATCH2=1;//位锁存
LATCH2=0;
DataPort=TempData[i];//取显示数据,段码
LATCH1=1;//段锁存
LATCH1=0;
i++;
if(i==Num)
i=0;
}
/*------------------------------------------------
定时器初始化子程序
------------------------------------------------*/
voidInit_Timer0(void)
{
TMOD|=0x01;//使用模式1,16位定时器,使用"|"符号可以在使用多个定时器时不受影响
//TH0=0x00;//给定初值
//TL0=0x00;
EA=1;//总中断打开
ET0=1;//定时器中断打开
TR0=1;//定时器开关打开
}
/*------------------------------------------------
定时器中断子程序
------------------------------------------------*/
voidTimer0_isr(void)interrupt1
{
TH0=(65536-2000)/256;//重新赋值2ms
TL0=(65536-2000)%256;
Display(0,8);
}
9.4键盘扫描函数
/*-----------------------------------------------
内容:
矩阵键盘读入
------------------------------------------------*/
#include//包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include"keyboard.h"
#include"delay.h"
#defineKeyPortP1
/*------------------------------------------------
按键扫描函数,返回扫描键值
------------------------------------------------*/
unsignedcharKeyScan(void)//键盘扫描函数,使用行列反转扫描法
{
unsignedcharcord_h,cord_l;//行列值中间变量
KeyPort=0x0f;//行线输出全为0
cord_h=KeyPort&0x0f;//读入列线值
if(cord_h!
=0x0f)//先检测有无按键按下
{
DelayMs(10);//去抖
if((KeyPort&0x0f)!
=0x0f)
{
cord_h=KeyPort&0x0f;//读入列线值
KeyPort=cord_h|0xf0;//输出当前列线值
cord_l=KeyPort&0xf0;//读入行线值
while((KeyPort&0xf0)!
=0xf0);//等待松开并输出
return(cord_h+cord_l);//键盘最后组合码值
}
}return(0xff);//返回该值
}
/*------------------------------------------------
按键值处理函数,返回扫键值
可以根据需要改变返回值
|1|2|3|+|
|4|5|6|-|
|7|8|9|*|
|0|.|=|/|
------------------------------------------------*/
unsignedcharKeyPro(void)
{
switch(KeyScan())
{
case0x7e:
return1;break;//0按下相应的键显示相对应的码值
case0x7d:
return2;break;//1
case0x7b:
return3;break;//2
case0x77:
return'+';break;//3
case0xbe:
return4;break;//4
case0xbd:
return5;break;//5
case0xbb:
return6;b