硬件综合实习——51单片机四则运算带括号计算器
中国石油大学(北京)
课程设计报告
课程名: 硬件综合实践
姓 名 许恺
学 号 2014011329
班 级 计算机14-1班
设计时间 2018年1月5日
一、 设计内容
在51单片机上编写烧录十进制四则运算计算机程序,使之具有计算、纠错、显示、括号运算(甚至浮点数运算)的功能。
二、 设计思路
根据实习设计要求,经过分析可得整个实验可以分为两个部分,软件和硬件,要明白硬件结合软件的方式以及LCD液晶屏的工作方式和51芯片的编码方式以及按钮的电路连接方式;软件要明白多位数四则运算的代码算法和C的语法。
设计思路可以分为三部分。第一,键盘按钮的扫描;第二,芯片内部的运算;第三,LCD液晶的显示;其中第二部分最为复杂重要。
三、 设计解释
3.1、设计环境介绍
51单片机,keil uv3开发软件,烧写软件PZISP自动下载软件.exe,SMC1602A LCM液晶显示屏。
3.2、程序解释(可含硬件部分)
连线:JP8连JP4 JP9连JP5 J8断开 J2断开 JP165断开 把1602液晶插入
代码:
/*
计算器硬件综合实验
*/
#include <reg51.h> //此文件中定义了51的一些特殊功能寄存器
#include <string.h>
#define uchar unsigned char
sbit EN=P2^7; //LCD的使能引脚
sbit RS=P2^6; //LCD数据命令选择端
sbit RW=P2^5; //LCD的读写选择端
sbit K1=P3^0; //*号
sbit K2=P3^1; // /号
sbit K3=P3^2; //(号
sbit K4=P3^3; // )号
sbit K5=P3^4; // =号
//这里连接了线并且将每位都对应上
code uchar KEY_CODE[]={ 0xed,0xdd,0xbd,0x7d,0xeb,0xdb,0xbb,0x7b,0xe7,0xd7,0xb7,0x77, //3X4矩阵键盘键值表
0x7f,0xbf,0xdf,0xef,0xf7}; //2*4键盘键值表
//定义字符键值表,均为字符对应的ASCII码的十六进制
code uchar CHAR_TABLE[]={0x30,0x31,0x32,0x33,//这四个会在液晶显示器中显示0 1 2 3
0x34,0x35,0x36,0x37,//这四个会显示4 5 6 7
0x38,0x39,0x2b,0x2d,//这个四个会显示8 9 + -
0x2a,0x2f,0x28,0x29,//这四个会显示 * / ( )
0x3d}; //这个会显示 =
//定义优先级矩阵,分为四种情况,当前的比上一个
0优先级高、2优先级低、1左括号遇到右括号、3发生错误
code uchar yxj[6][7]={
// + - * / ( ) =
2,2,0,0,0,2,2, // +
2,2,0,0,0,2,2, // -
2,2,2,2,0,2,2, // *
2,2,2,2,0,2,2, // /
0,0,0,0,0,1,3, // (
2,2,2,2,3,2,2, // )
};
uchar str[40];//定义一个队列,用来放整个运算式
void scan(uchar *var);//从矩阵键盘中获取值
void print(int i,int j,uchar *outStr);//打印字符串
void delay5MS();//短延时函数
void delay100MS();//较长延时函数
void writeCMD(uchar com);//写命令子程序
void showOneChar(uchar dat);//写数据子程序
void init();//初始化子程序,初始化液晶显示屏
void clear();//清除显示屏上的显示
int calculate(uchar *arr);//计算函数,输入运算式,返回最终得数
int Precede(char a,char b);//优先级比较函数,输入两个符号,返回0123哪种情况
int Operate(int num1,uchar theta,int num2);//计算函数,输入两个数一个符号,返回运算结果
void main()
{
uchar new=0xff; //new是用来放当前扫描到的字符
int i=0,j=0,flag=0;//flag用来确认括号是否匹配,遇到左括号++,右括号--
int finvalue;//存放最后的得数
uchar text[16];
uchar text1[16]; //在最后用作字符串逆序
delay100MS();
//这是必要的,根据1602显示屏的说明书,使用前必须进行初始化
init();
//使用前清屏
clear();
while(1)
{
flag=0;
while(1)
{
scan(&new);
clear();
//加入出错判断机制,整个出错判断机制主要是选出所有正确的,不正确的根本不会被加到运算式的字符串中,在显示屏上就是按了不显示。如果有浮点数的话就加上小数点的情况
if(new=='='&&flag==0) //如果new输入=且括号没有问题
{
str[i++]=new;
str[i]='\0';
}
else if(new<'0'||new>'9') //若现在输入是符号
{
if(i==0&&new=='(') //如果第一个要输入(
{
flag++;
str[i++]=new;
}
else if(i!=0&&(str[i-1]=='+'||str[i-1]=='-'||str[i-1]=='*'||str[i-1]=='/'||str[i-1]=='(')&&new=='(') //前一个是符号时对现在的进行限制
{
flag++;
str[i++]=new;
}
else if(i!=0&&str[i-1]==')') //前一个符号是)的时候
{
if(new!='('&&new!=')') //现在的不是()的时候可以加进去
str[i++]=new;
else
if(new==')'&&flag>0) //需要)的时候)后面可以是)
{
flag--;
str[i++]=new;
}
}
if(i!=0&&(str[i-1]>='0'&&str[i-1]<='9')&&new!='(')//前一个是数字时对现在的输入限制
{
if(new==')'&&flag>0) //如果需要)数字后面可以加)
{
flag--;
str[i++]=new;
}
if(new!=')') //否则数字后面加什么都行
str[i++]=new;
}
}
else //若现在输入是数字
{
if(i==0) //第一位可以是数字
str[i++]=new;
else
{
if(str[i-1]!=')') //只要前一位不是),就可以接数字
{
if(str[i-1]!='/'||new!='0') //排除/后面接0的可能
str[i++]=new;
}
}
}
//出错判断机制结束,下面把内容显示到显示屏上
print(0,0,&str);
if(new=='='&&flag==0) //如果现在的符号是=且括号对应正确就直接跳出去进行下一次计算,不过前面出错判断机制规定如果括号不对根本就输不出来=,所以不会括号不对应
{
break;
}
}
finvalue=calculate(&str); //根据运算式计算得出最终得数
if(finvalue<0) //判断得数正负,负就在最后输出的式子第一位置-
{
finvalue=-finvalue;
text1[0]='-';
}
else text1[0]=' '; //否则就第一位置空格
for(i=0;finvalue%10!=0;i++) //把整型的结果变成字符串型,不过出来后是逆序的
{
text[i]=finvalue%10+0x30;
finvalue/=10;
}
for(j=1;j<i+1;j++) //把逆序的结果反转一下
{
text1[j]=text[i-j];
}
print(1,0,&text1); //把结果显示在屏幕的第二行
i=0;
}
}
/*********************短延时函数*************************/
void delay5MS()
{
int n=3000;
while(n--);
}
/*****************定义长点的延时程序**********************/
void delay100MS()
{
int n=10000;
while(n--);
}
/*下图是LCD显示屏的16位引脚的意思,主要使用的就是RS,RW,E三个来控制LCD的操作,D0-D7来进行数据传输,根据下下图说明51单片机默认LCD显示屏的数据传输口与P0口对应
/*******************写命令子程序**************************/
void writeCMD(uchar com)
{
P0=com; //com为输入的命令码。通过P0送给LCD
RS=0; //RS=0 写命令
RW=0;
delay5MS();
EN=1; //LCD的使能端E置高电平
delay5MS();
EN=0; //LCD的使能端E置低电平
}
/*******************写数据子程序**************************/
void showOneChar(uchar dat)
{
P0=dat; //写入数据
RS=1; //RS=1写命令
RW=0;
EN=1;
delay5MS();
EN=0;
}
/*******************初始化函数**************************/
/*其实1602说明书给的很明确了,显示屏的初始化必须这样做*/
void init()
{
EN=0;
writeCMD(0x38);//设置显示模式
writeCMD(0x0e);//光标打开,不闪烁
writeCMD(0x06);//写入一个字符后指针地址+1,写一个字符时整屏不移动
writeCMD(0x01);//清屏显示,数据指针清0,所以显示清0
writeCMD(0x80);//设置字符显示的首地址
}
/*********************清屏子程序**********************/
void clear()
{
EN=0;
writeCMD(0x01);
}
/**********从键盘获取值得函数类似于C语言的scanf()函数**************/
/*这是我设计思路的第一部分,键盘扫描,分为两部分,3*4键盘部分,和2*4部分,两部分用的方式不一样,然后通过已有的字符对照表把参数var置成扫描到的字符。如果加上浮点数的话就增加扫描到小数点时的情况*/
void scan(uchar *var)
{
uchar temp,num;
int i=1;
temp=i;
while(1){
//3*4键盘扫描,因为有12个键只有8个接口,所以通过3行4列来定为按键,故分为行检测和列检测,为什么要分开呢?是因为下图中,如果直接行列同时检检测的话是不可能检测的,两个口必须一高一低才会有电流通过,同时置0或f是不会产生电流的
P1=0x0f;//置行为高电平,列为低电平。这样用于检测行值。低电平有效
if(P1!=0x0f)
{
delay100MS(); //延时,软件消除抖动。
temp=P1; //保存行值
P1=0xf0; //置行为低电平,列为高电平,获取列
if(P1!=0xf0)
{
num=temp|P1; //获取了按键位置
//P2=1;
for(i=0;i<12;i++)
if(num==KEY_CODE[i])
{
if(i==10)
*var='+';//获取等号的值
else if(i==11)
*var='-';//获取加号的值
else *var=i+0x30;//获取数值的字符
}
break; //跳出循环,为了只获取一个值
}
}
//检测2*4键盘的值,接口足够,所以直接对应,按下为低电平则扫描到
if(K1==0)
{
delay100MS();
*var='*';
break;
}
if(K2==0)
{
delay100MS();
*var='/';
break;
}
if(K3==0)
{
delay100MS();
*var='(';
break;
}
if(K4==0)
{
delay100MS();
*var=')';
break;
}
if(K5==0)
{
delay100MS();
*var='=';
break;
}
}
}
/******************显示函数***************************/
/*i为要输出的行,0为第一行,1为第二行,j为输出的列,arr为输出的字符串通过调用传输数据函数一个一个输出到显示屏上*/
void print(int i,int j,uchar *arr)
{
int t;
int len=strlen(arr);
int location;
if(i==0)
{
writeCMD(0x80+j);
}
else
{
writeCMD(0xC0+j);
}
if(len==0) //如果队列为空就清屏
{
clear();
return;
}
else
{
for(t=0;t<len;t++)
{
switch(arr[t])
{
case '0':location=0;break;
case '1':location=1;break;
case '2':location=2;break;
case '3':location=3;break;
case '4':location=4;break;
case '5':location=5;break;
case '6':location=6;break;
case '7':location=7;break;
case '8':location=8;break;
case '9':location=9;break;
case '+':location=10;break;
case '-':location=11;break;
case '*':location=12;break;
case '/':location=13;break;
case '(':location=14;break;
case ')':location=15;break;
case '=':location=16;break;
}
showOneChar(CHAR_TABLE[location]);
}
}
}
/*********************计算字符串输入的运算式的结果**********************/
int calculate(uchar *arr)
{
int num[5]={0}; //num为数据栈,初值为0
uchar sign[5]; //sign为符号栈
int i=0,j=0,k=0; //j是数据栈的栈顶指针,指向栈顶的下一个,k是符号栈的栈顶的下一个
uchar c; //用来存放当前的字符
int flagnum=0; //指当前遇到数字的位数
uchar theta; //运算时的运算符号
int num1,num2,num0; //运算时的运算数和得数
c=arr[i];
i++;
while(c!='\0')
{
//如果要加上浮点数就增加一种情况就是遇到‘.’的时候改变一下迭代方式
if(c>='0'&&c<='9') //如果当前遇到的是数字,先把字符变成数字,
{
c=c-0x30;
if(flagnum==0)
{
num[j]=c;j++; //如果这是第一位就直接入栈
flagnum++;
}
else
{
num[j-1]=num[j-1]*10+c; //不是第一位就拿出来上一个乘十加上再压进去
flagnum++;
}
c=arr[i];i++;
}
else //如果来的不是数字
{
if(k==0) //如果符号栈里没东西直接入栈
{
sign[0]=c; k++;
c=arr[i];i++;
flagnum=0;
}
else //符号栈里有东西就得比较一下了,分三种情况
switch(Precede(sign[k-1],c))
{
case 0: sign[k]=c;k++;c=arr[i];i++; //当前符号优先级更高则入符号栈
flagnum=0; //数字位数清零
break;
case 1: k--;sign[k]='\0';c=arr[i];i++;break; //当两个括号相遇时直接出栈
case 2: theta=sign[k-1];k--; //当当前的符号优先级小于等于上一个符号时,将栈内的数字和符号取出进行运算
num2= num[j-1];j--;
num1=num[j-1];j--;
num0=Operate(num1,theta,num2);
num[j]=num0;j++; //运算结果入栈
break;
default: break;
}
}
}
i=0;
while(arr[i]!='\0') //把运算式字符串置为空
{
arr[i]='\0';
i++;
}
return num[j-1]; //返回整型的结果
}
//符号优先级比较,输入a,b两个符号,根据优先级表返回他的优先级对比情况,分为0123
int Precede(char a,char b)
{
char i,j;
switch(a)
{
case '+':i=0;break;
case '-':i=1;break;
case '*':i=2;break;
case '/':i=3;break;
case '(':i=4;break;
case ')':i=5;break;
case '=':i=6;break;
}
switch(b)
{
case '+':j=0;break;
case '-':j=1;break;
case '*':j=2;break;
case '/':j=3;break;
case '(':j=4;break;
case ')':j=5;break;
case '=':j=6;break;
}
return yxj[i][j];
}
//栈中数的运算,输入两个数一个符号返回一个得数
int Operate(int num1,uchar theta,int num2)
{
int num3;
switch(theta)
{
case '+':num3=num1+num2;break;
case '-':num3=num1-num2;break;
case '*':num3=num1*num2;break;
case '/':num3=num1/num2;break;
}
return num3;
}
四、 设计体会与建议
这次的硬件综合实习让我在两周的时间内快速学习了单片机并且进行了实验,虽然之前参加机器人大赛已经对51单片机的烧录和写代码已经有了一定的了解,但是这次的学习让我对它的了解更加深入,前面的几个小实验也是层层递进为最后的实验做准备,四则运算算法一直是我不太会的算法,通过这次实验我学会了,相信在以后也能自如的写出来。本次设计学习了很多东西,在网上查了好多资料,在短时间内对我自身有一个明显的提高,同时也增加了一些未来的可能性,很感谢我的队友徐志远同学,我们两个缺一不可,在这次实验中帮了我很多,也感谢老师对我的鼓励。
徐朝农老师的教学精神值得所有老师学习,既没有轻松水过,也没有过于严格苛刻,高标准+人性化的评判,真正让同学们学到东西。要说建议也没有什么,就是下一届和后面的学弟学妹人数会越来越多,可以申请多一点教具和教室,难度还可以加大。
参考:https://wenku.baidu.com/view/92c3647d650e52ea551898e2.html?qq-pf-to=pcqq.c2c