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