资源描述
单片机原理及其应用实验报告
基于51单片机的简易计算器的设计
班级:12电子1班
姓名: 金 腾 达
学号:1200401123
2015年1月6日
Ⅱ
摘 要
一个学期的51单片机的课程已经随着期末的到来落下了帷幕。“学以致用”不仅仅是一句口号更应该是践行。本设计秉承精简实用的原则,采用AT89C51单片机为控制核心,4X4矩阵键盘作为输入,LCD1602液晶作为输出组成实现了基于51单片机的简易计算器。计算器操作方式尽量模拟现实计算器的操作方式,带有基本的运算功能和连续运算能力。并提供了良好的显示方式,与传统的计算器相比,它能够实时显示当前运算过程和上一次的结果,更加方便用户记忆使用。本系统制作简单,经测试能达到题目要求。
关键词:简易计算器、单片机、AT89C51、LCD1602、矩阵键盘
3
Ⅱ
目 录
一、系统模块设计 1
1.1 单片机最小系统 1
1.2 LCD1602液晶显示模块 1
1.3 矩阵按键模块 1
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
4
Ⅱ
基于51单片机的简易计算器的设计
一、系统模块设计
51单片机
矩阵按键输入
LCD液晶显示
串口输出
本系统主要由51单片机最小系统、串口模块、显示模块、矩阵键盘输入模块组成,下面分别论证这几个模块的选择。
1.1单片机最小系统
51单片机的最小系统包括电源、时钟电路、复位电路,搭建最小系统是实现单片操作的最基本的硬件电路要求。由于程序上需要使用串口工作在11920的波特率,为了更好地匹配该波特率,晶振采用11.0592MHz的晶振而不是常用的12MHz晶振。
1.2 LCD1602液晶显示模块
为了便于计算器的计算过程以及结果的显示,方案采用了LCD1602的液晶来显示。使用液晶比数码管的优势很多,占用较少的IO口、更低的功耗、更简单的控制过程、更强大的显示能力:
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.如何与上位机进行计算结果的通信?
本实例中已经简单实现了基于串口的单片机与计算机上位机之间的通信,不过是单向的,为了实现真正的通信,可定义相关协议,通过串口收发管理这些数据和操作来实现。
正文
第 14 页 共 14 页
附录1:整体电路原理图
附录2:部分源程序
/*头文件引用部分*/
#include "mySys.h"
#include "LM016.h"
#include "stdio.h"
#include "MyUsart.h"
/*端口定义部分*/
#define PORT_KEY P1
/*全局变量*/
float CalNum1=0; //待计算数1
float CalNum2=0; //待计算数2
float 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)
{
unsigned char TempKey;
LM016_Init();
MyUsart_Init();
MyUsart_Print("This is 51 Calculator Program!");
MyUsart_Print("Usart Mode : TX BAD=19200bps");
ShowLogo(); //显示LOGO
MyUsart_Print("System Init Done! Have Fun!");
while(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) //第一次输入0
FirstZero = 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) //第一次输入0
FirstZero = 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) //第一次输入0
FirstZero = 1; //给了一次输入0的机会,后面就不给了
else
break;
}
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;
}
}
}
展开阅读全文