单片机原理及其应用实验报告基于51单片机的简易计算器的设计班级:12电子1班姓名:***学号:**********2015年1月6日摘要一个学期的51单片机的课程已经随着期末的到来落下了帷幕。
“学以致用”不仅仅是一句口号更应该是践行。
本设计秉承精简实用的原则,采用AT89C51单片机为控制核心,4X4矩阵键盘作为输入,LCD1602液晶作为输出组成实现了基于51单片机的简易计算器。
计算器操作方式尽量模拟现实计算器的操作方式,带有基本的运算功能和连续运算能力。
并提供了良好的显示方式,与传统的计算器相比,它能够实时显示当前运算过程和上一次的结果,更加方便用户记忆使用。
本系统制作简单,经测试能达到题目要求。
关键词:简易计算器、单片机、AT89C51、LCD1602、矩阵键盘目录一、系统模块设计........................................................................................... 错误!未定义书签。
1.1 单片机最小系统 (1)1.2 LCD1602液晶显示模块 (1)1.3 矩阵按键模块 (2)1.4 串口连接模块 (1)二、C51程序设计 (2)2.1 程序功能描述及设计思路 (2)2.1.1按键服务函数 (2)2.1.2 LCD驱动函数 (2)2.1.3 结果显示函数 (2)2.1.4状态机控制函数 (2)2.1.5串口服务函数 (2)2.2 程序流程图 (3)2.2.1系统总框图 (3)2.2.2计算器状态机流程转换图 (3)三、测试方案与测试结果 (4)3.1测试方案 (4)3.3 测试结果及分析 (7)4.3.1测试结果(仿真截图) (7)4.3.2测试分析与结论 (7)四、总结心得 (7)五、思考题 (8)附录1:整体电路原理图 (9)附录2:部分程序源代码 (10)基于51单片机的简易计算器的设计一、系统模块设计本系统主要由51单片机最小系统、串口模块、显示模块、矩阵键盘输入模块组成,下面分别论证这几个模块的选择。
1.1单片机最小系统 51单片机的最小系统包括电源、时钟电路、复位电路,搭建最小系统是实现单片操作的最基本的硬件电路要求。
由于程序上需要使用串口工作在11920的波特率,为了更好地匹配该波特率,晶振采用11.0592MHz 的晶振而不是常用的12MHz 晶振。
1.2 LCD1602液晶显示模块为了便于计算器的计算过程以及结果的显示,方案采用了LCD1602的液晶来显示。
使用液晶比数码管的优势很多,占用较少的IO 口、更低的功耗、更简单的控制过程、更强大的显示能力: 51单片机 矩阵按键LCD 液晶显示串口输出1.3 矩阵按键模块计算器的输入通过4X4的矩阵按键来实现,由于软件上做了相应的映射处理,因此该4X4按键可以实现在极少代码更改下随意安排每个按键的实际意义。
矩阵按键通过行列扫描的方式快速求出当前的按下按键并等待起弹起以防止重复触发:1.4 串口连接模块由于使用Proteus仿真,这里的串口电路进行了简化,没有使用实际中将会用于进行电平转换的232芯片,而直接使用串口观察控件进行串口接收以及显示:二、C51程序设计由于本系统对系统的响应速度要求并不高,不需要进行高速的大量数据运算操作,因此不采用汇编方式编写程序。
使用C语言编写程序能够清晰地分析系统的整体思路。
本程序的主要思想是状态机,利用状态机的不同状态对程序的流程进行分段控制,在本系统中,较大限度的提高了系统的运行效率,同时具有了便于分析、改进和查错的天然优势2.1程序功能描述与设计思路2.1.1、按键服务函数:将4X4矩阵按键封装至按键服务函数中,利用映射表(数组)对4X4的对应按键进行键值映射,这样不仅仅完成了按键判断的函数封装更便于实际操作时对按键的定义的灵活改动,另外,按键的返回值采用ASCII码形式,这样更加利于程序上的可读性;2.1.2、LCD驱动函数:按照LM016模块的操作时序编写的LM016(LCD1602液晶)的驱动函数,使用C和H文件组合的形式既完成了底层的液晶驱动又开放了操作液晶的接口函数,使整体程序更加清晰明了;2.1.3、结果显示函数:由于计算结果涉及到小数点、负数以及长度的不确定性,这里直接通过调用stdio.h中的sprintf字符串格式化函数进行格式化,得到15个字符长度的ASCII形式数据显示,并在程序中进行范围限定以避免数据过大而产生显示不完整造成结果“不正确”的现象。
同时该函数还通过调用串口输出函数对单片机串口进行输出,可在计算机上位机端得到每次的计算结果信息;2.1.4、状态机控制函数:该函数直接在main函数的内部实现,并融合状态机的程序思想,利用状态机判断计算器输入时的各种可能状态并在不同程序状态中跳转实现灵活的程序流程控制,实践表明这种方式是非常适合计算器的程序设计的,能较大限度地提高系统的运行效率;2.1.5、串口服务函数:串口服务主要负责实现单片机向计算机上位机端的数据结果输出以及灵活的字符串显示。
2.2 程序流程图2.2.1、系统总框图2.2.2、计算器状态机流程转换图三、仿真方案与仿真结果3.1仿真方案1、硬件仿真使用Proteus7.8进行硬件仿真。
2、软件仿真使用Keil4For51 Debug工具进行软件编写和仿真3、硬件软件联调利用Proteus7.8和Keil4进行联合软硬件调试,方便查错和仿真展现3.2 测试结果及分析3.2.1测试结果(仿真截图)1、加、减、乘、除基本运算展示:2、连续运算展示:3、溢出和除0判断展示:4、串口通信展示:5、开机效果显示:3.3.2测试分析与结论根据上述测试数据,综上所述,本设计总体来说可以达到大部分设计要求。
四、总结与心得经过本次的实验设计学习,又一次深刻感受到了51单片机虽然已经过去几十年,现在也不断地收到16位、32位低价单片机的冲击,但仍然是一款性能优越的单片机,在处理生活中常用的简单任务时,51单片机依然能够焕发出青春般的光彩。
同时,51单片机也是学习和理解其他高级单片机的最好的入门平台,本次的实验也将增强了我对学习好其他高级单片机的决心和信心。
五、思考题1.描述完整所设计的计算器能完成的各项功能及实现方法。
(如几位数以内的运算;连加;复合运算等等)本实例实现了加减乘除基本运算、连续运算、最大长度14位的数据输入、超过14位数据后程序为避免不良显示自动显示溢出2.计算器设计过程中碰到的问题及解决的方法?使用时原本打算使用double型变量,但在实际测试中并没有发现精度很高,通过联合调试发现KEILC51编译器将double型自动转换为float型;3.如何实现掉电保护?使用E2PROM或者外部的SD卡等存储设备,通过一定的时序操作控制这些外部设备实现存储数据的接口,在每一步计算操作后都将过程和结果存储到存储设备中,在下次上电后直接读取实现掉电保护;4.日常生活中计算器光敏单元的功能及实现原理?光敏单元可看作为一个电流源,通过电阻进行简单的I/V转换,然后用ADC转换为数字量,通过单片机处理后调节液晶偏压或占空比来调节显示对比度以实现不同光强下的正常显示;5.如何与上位机进行计算结果的通信?本实例中已经简单实现了基于串口的单片机与计算机上位机之间的通信,不过是单向的,为了实现真正的通信,可定义相关协议,通过串口收发管理这些数据和操作来实现。
附录1:整体电路原理图附录2:部分源程序/*头文件引用部分*/#include "mySys.h"#include "LM016.h"#include "stdio.h"#include "MyUsart.h"/*端口定义部分*/#define PORT_KEY P1/*全局变量*/float CalNum1=0; //待计算数1float CalNum2=0; //待计算数2float ResNum =0; //计算结果unsigned char AppendNum=0; //判断是否为连续模式unsigned char NumsBegin=0; //判断是否真的开始有数字输入这里是为了避免重复输入前面的0 unsigned char FirstZero=0; //第一个是否为0 unsigned char NumPen=0; //书写坐标,会随着数字的输入而向后移动unsigned char AllNumsLen; //当前待计算数和运算符总长度,定义该变量是为了避免式子过长code unsigned char CalSymbolTable[]={0,'+','-','*','/'}; //运算符表unsigned char CalSymbol=0; //运算符unsigned char SysStatus=0; //程序运行状态//0程序初始化状态1正在输入第一个数2正在输入第二个数3得到运算结果/*函数声明部分*/unsigned char KeyScan(void);void ShowLogo(void);void ShowCalResult(float Value);/*函数实现部分*/void main(void){u nsigned char TempKey;L M016_Init();M yUsart_Init();M yUsart_Print("This is 51 Calculator Program!");M yUsart_Print("Usart Mode : TX BAD=19200bps");S howLogo(); //显示LOGOM yUsart_Print("System Init Done! Have Fun!");w hile(1){switch(SysStatus){case 0: //【程序初始化状态】LM016_Clear(); //清屏CalNum1=0;CalNum2=0;ResNum =0;NumPen =0;AppendNum=0;NumsBegin=0;FirstZero=0;AllNumsLen =0;CalSymbol=0;SysStatus=1; //进入正在输入第一个数状态break;case 1: //【正在输入第一个数字】TempKey = KeyScan();if(!TempKey) break; //没有按键按下则快速跳出if(TempKey>= '0' && TempKey <='9') //*****数字键{if(!NumsBegin) //现在还没有开始正式输入数字{if(TempKey == '0'){if(!FirstZero) //第一次输入0FirstZero = 1; //给了一次输入0的机会,后面就不给了else break;}else{NumsBegin = 1; //开头是非零,已经开始正式输入数字了NumPen=0; //书写坐标归位,此时将会覆盖原有的0}}if(AllNumsLen >= 10) break; //长度过长则快速跳出不在接受数字输入CalNum1 *= 10;CalNum1 += (TempKey - '0'); //求得当前数值LM016_PutChar(0,NumPen, TempKey);NumPen++; //书写坐标向右边移动AllNumsLen++;}else //*****符号键{switch(TempKey){case '+':case '-':case '*':case '/':if((!NumsBegin)&&(!FirstZero)) //若没有任何数字按键按下则补充这个0{LM016_PutChar(0,0, '0');NumPen++;}CalSymbol=TempKey;LM016_PutChar(0,NumPen, TempKey);NumPen++;AllNumsLen++;NumsBegin=0;FirstZero=0;AppendNum=0;CalNum2 = 0; //把第二个数值清空SysStatus=2;break;case '=':ResNum = CalNum1;ShowCalResult(ResNum);FirstZero=0;AppendNum=0;NumsBegin=0;NumPen=0;AllNumsLen=0;SysStatus=3; //进入过度阶段break;case 'C':SysStatus=0;break;}break;}case 2: //【正在输入第二个数字】TempKey = KeyScan();if(!TempKey) //没有按键按下则快速跳出break;if(TempKey>= '0' && TempKey <='9') //*****数字键{if(!NumsBegin) //现在还没有开始正式输入数字{if(TempKey == '0'){if(!FirstZero) //第一次输入0FirstZero = 1; //给了一次输入0的机会,后面就不给了else break;}else{NumsBegin = 1; //开头是非零,已经开始正式输入数字了if(FirstZero) NumPen--; //书写坐标归位,此时将会覆盖原有的0 }}if(AllNumsLen >= 16) break; //长度过长则快速跳出不在接受数字输入CalNum2 *= 10;CalNum2 += (TempKey - '0'); //求得当前数值LM016_PutChar(0,NumPen, TempKey);NumPen++; //书写坐标向右边移动AllNumsLen++;}else //*****符号键{switch(TempKey){case '=':switch(CalSymbol){case '+':ResNum = CalNum1 + CalNum2;reak;case '-':ResNum = CalNum1 - CalNum2;break;case '*':ResNum = CalNum1 * CalNum2;break;case '/':ResNum = CalNum1 / CalNum2;break;}if((!NumsBegin)&&(!FirstZero)) //若没有任何数字按键按下则补充这个0{LM016_PutChar(0,NumPen, '0');CalNum2 = 0;NumPen++;}ShowCalResult(ResNum);FirstZero=0;AppendNum=0;NumsBegin=0;NumPen=0;AllNumsLen=0;SysStatus=3; //进入过度阶段break;case 'C':SysStatus=0;break;}}break;case 3: //【当前是过度阶段】TempKey = KeyScan();if(!TempKey) break; //没有按键按下则快速跳出if(TempKey>= '0' && TempKey <='9') //*****数字键{LM016_Clear(); //清屏if(!NumsBegin) //现在还没有开始正式输入数字{if(TempKey == '0'){if(!FirstZero) //第一次输入0FirstZero = 1; //给了一次输入0的机会,后面就不给了elsebreak;}else{NumsBegin = 1; //开头是非零,已经开始正式输入数字了NumPen=0; //书写坐标归位,此时将会覆盖原有的0 }}CalNum1 = (TempKey - '0'); //求得当前数值这里直接赋值就OK了CalNum2 = 0; //把第二个数值清空LM016_PutChar(0,NumPen, TempKey);NumPen=1; //书写坐标向右边移动AllNumsLen=1;SysStatus=1; //进入输入第一个数值状态}else //*****符号键{switch(TempKey){case '+':case '-':case '*':case '/':LM016_Print(0,0,"Ans ");AppendNum=1;CalNum1 = ResNum;CalNum2 = 0;NumPen=2;if((!NumsBegin)&&(!FirstZero)) //若没有任何数字按键按下则补充这个0{LM016_PutChar(0,3, '0');NumPen++;}CalSymbol=TempKey;LM016_PutChar(0,NumPen, TempKey);NumPen++;AllNumsLen=4;NumsBegin=0;FirstZero=0;AppendNum=0;SysStatus=2;break;case '=':switch(CalSymbol){case '+':ResNum = ResNum + CalNum2;break;case '-':ResNum = ResNum - CalNum2;break;case '*':ResNum = ResNum * CalNum2;break;case '/':ResNum = ResNum / CalNum2;break;}ShowCalResult(ResNum);FirstZero=0;AppendNum=0;NumsBegin=0;NumPen=0;AllNumsLen=0;SysStatus=3; //进入过度阶段break;case 'C':SysStatus=0;break;}break;}break;}}}。