由于本文较早,这里推荐一下近期刚优化的:【优化】51单片机计算器(LCD1602液晶显示)_Gxtoeilee的博客-CSDN博客
1 设计目标
设计可选模式的计算器
2 主要功能
功能一:可选两种计算模式,模式一为四则运算,模式二为扩展内容;
功能二:除法的计算结果保留到小数点后四位;
功能三:除数为0时报错。
3 硬件部分讲解
51单片机,导线,矩阵键盘,独立按键,LCD1602液晶显示屏
图1 图2
图3 图4
图5 图6
图7
图8
图9
图10
图11
图1为矩阵键盘模块。
图2为独立按键模块。K1连接P2_0,K2连接P2_1。
图3为LCD1602接口。控制效果如下:
RW:P2_5控制;
RS:P2_6控制;
EN:P2_7控制;
D0—D7:P0_0—P0_7。
图4为矩阵键盘由P1组控制的效果。
图5为LCD1602显示代码。
图6为LCD1602显示位置及代码。
图7为LCD1602的引脚接口说明。
图8为LCD1602的写操作时序图。
图9为LCD1602的时序参数。
图10为计算器计算按键,对应矩阵键盘。
图11为计算器模式选择按键,对应独立按键。
4 软件流程及代码实现
4.1主要代码说明
本程序由一个主函数和六个子函数组成。子函数分别为:延时函数、LCD写命令函数、LCD写数据函数、LCD初始化函数、模式判定函数、计算器函数。
4.1.1 主函数
主函数首先调用LCD初始化函数对LCD1602显示屏进行初始化,然后显示欢迎语句welcome,延时1s后清屏。进入循环后首先调用功能判定函数选择计算器模式。在设计之初计划设定两种模式,模式一中为四则运算,模式二中为扩展其他功能,随后根据所选模式的返回值进入不同的计算器程序。此处只对模式一进行展示,即四则运算。
void main()
{
uint x;
lcdinit();//LCD1602初始化
for(x=0;x<12;x++)
lcdwrd(wel[x]);//显示欢迎语句:Welcome
delay(1000);
lcdwrc(0x01);//LCd1602清屏
while(1)
{
MODE();//功能判定函数
KeyDown();//按键判断函数
}
}
4.1.2 子函数1
延时函数为延时x毫秒,主要用于LCD1602的初始化、显示语句、按键消抖。
void delay(unsigned int xms)//延时x毫秒
{
unsigned int i,j;
for(i=xms;i>0;i--)
for(j=112;j>0;j--);
return;
}
4.1.3 子函数2
LCD写命令函数。RS为寄存器选择,高电平时选择数据寄存器,低电平时选择指令寄存器。此处为RS赋低电平选择指令寄存器。由写操作时许图可知,RW可全过程定义为低电平;EN为使能端,由EN和DB0—DB7时序可知,EN首先为低电平,高电平时写指令,低电平时写指令完成,液晶模块执行命令。
void lcdwrc(uint c)
{
delay(100);
rs=0;
rw=0;
e=0;
P0=c;
e=1;
delay(100);
e=0;
}
4.1.4 子函数3
LCD写数据函数。RS为寄存器选择,高电平时选择数据寄存器,低电平时选择指令寄存器。此处为RS赋高电平选择数据寄存器。由写操作时许图可知,RW可全过程定义为低电平;EN为使能端,由EN和DB0—DB7时序可知,EN首先为低电平,高电平时写数据,低电平时写数据完成,液晶模块执行命令。
void lcdwrd(uint dat) //设置LCD写数据
{
delay(100);
rs=1;
rw=0;
e=0;
P0=dat;
e=1;
delay(100);
e=0;
rs=0;
}
4.1.5 子函数4
初始化LCD函数。固定程序以及定义计算器需要用的变量。
void lcdinit() //初始化LCD
{
delay(150);
lcdwrc(0x38);//写指令38H
delay(50);
lcdwrc(0x38);//显示模式设置
delay(50);
lcdwrc(0x38);
delay(50);
lcdwrc(0x38);//功能设定指令
lcdwrc(0x08);
lcdwrc(0x01);//清屏
lcdwrc(0x06);//显示光标移动设置
lcdwrc(0x0c); //显示开关控制指令
num=0;//用于判定按键是否为“=”或者“归零”时的变量
fuhao=0;//用于判定是否为运算符号
flag=0;//用于判定运算方式时的变量
a=0;//计算的第一个数
b=0;//计算的后一个数
c=0;//计算结果
x=0;//判定小数点的变量,暂存c值
k=0;//判定小数点的变量,小数点后非零数个数
j=0;//显示ERROR时的循环用变量
}
4.1.6子函数5
模式判定函数。用于显示选择的是哪个模式并且返回主函数值,使主函数运行相应值。此处因我只定义了一个模式,故只有模式1。
void MODE()
{
if(P2_0==0)
{
for(j=0;j<10;j++)
lcdwrd(MODE1[j]);//显示语句:MODE1
delay(1000);//延时
lcdinit();//重新初始化LCD1602
}
}
4.1.7子函数6
计算器函数。该函数较为复杂,大致分为按键判定、按键扫描、实现功能。
按键判定使判断是否有按键按下。
按键扫描部分是判定哪个键被按下。首先对第一列扫描,若扫描结果为归零键,便会将LCD执行清零操作。若扫描到其他按键,则显示对应数字。后面二、三、四列的定义均如此。
计算部分为扫描结果为“=”键时。加法和乘法运算都只需要直接计算即可。计算的结果是从后往前输入,这样便于确定结果显示的位置。
减法运算中,程序会先判断减数和被减数的大小关系,从而确定结果是否为负数。若为负数,则会显示负号。
除法运算时,因为除法中可能会出现小数,所以先将计算结果乘以10000,然后将结果循环除以10,直到为0,每次循环都会使变量k加1,最后跟据k的大小(1-4)来确定小数点的位置,即保留四位小数。
void KeyDown()//计算器函数
{
k=0;
P1=0x0f; //0000 11111,行都为低电平0,列都为高电平
if(P1!=0x0f)//P1不为0x0f,则判定为有按键被按下
{
delay(100);//消抖
if(P1!=0x0f)//延时后,再次判断
{
P1=0x0f;//0000 11111,行都为低电平0,列都为高电平
switch(P1)//
{
/********************第一列有按键按下*********************/
case 0x07://0000 0111 第一列有按键被按下
{
P1=0xf0;//列都为低电平0,行都为高电平1
switch(P1)//判断哪一行的按键被按下
{
case 0x70: num = 1/*S1*/;k=7;break; //0111 0000第一列第一行 数字7
case 0xb0: num = 5/*S5*/;k=4;break; //1011 0000第一列第二行 数字4
case 0xd0: num = 9/*S9*/;k=1;break; //1101 0000第一列第三行 数字1
case 0xe0: num = 13/*S13*/;break; //1110 0000第一列第四行 归零
}
if(num!=13)//被按下的按键不是归零键
{
if(fuhao==0)//fuhao==0,表示第一个数a,fuhao==1,表示第二个数b
a=a*10+k;
else
b=b*10+k;
}
lcdwrd(0x30+k);//显示按下的数,0x30为ASCII码中数字组的开头,0x3k:显示k,即按下数字k
if(num==13)//按下的按键是清零键
{
lcdwrc(0x01); //清屏指令
lcdinit();//重新初始化LCD1602
}
}while(P1!=0xf0);break;//当有按键被按下时,结束此次判断,进行下一次判断
/********************第二列有按键按下*********************/
case 0x0b: //0000 1011 第二列有按键被按下
{
P1=0xf0;//行都为高电平1,列都为低电平0
switch(P1)//判断哪一行有按键被按下
{
case 0x70: k=8;break;//第二列第一行 数字8
case 0xb0: k=5;break;//第二列第二行 数字5
case 0xd0: k=2;break;//第二列第三行 数字2
case 0xe0: k=0;break;//第二列第四行 数字0
}
if(fuhao==0)//fuhao==0,表示第一个数a,fuhao==1,表示第二个数b
a=a*10+k;
else
b=b*10+k;
lcdwrd(0x30+k);//显示按下的数,0x30为ASCII码中数字组的开头,0x3k:显示k,即按下数字k
} while(P1!=0xf0);break;//当有按键被按下时,结束此次判断,进行下一次判断
/********************第三列有按键按下*********************/
case 0x0d: //0000 1101 第三列有按键被按下
{
P1=0xf0;//行都为高电平1,列都为低电平0
switch(P1)//判断哪一行有按键被按下
{
case 0x70: num=3;k=9;break;//第三列第一行 数字9
case 0xb0: num=7;k=6;break;//第三列第二行 数字6
case 0xd0: num=11;k=3;break;//第三列第三行 数字3
case 0xe0: num=15;break;//第三列第四行 “=”键
}
if(num!=15)//按下的键不为“=”键
{
if(fuhao==0)//fuhao==0,表示第一个数a,fuhao==1,表示第二个数b
a=a*10+k;
else
b=b*10+k;
lcdwrd(0x30+k);//显示按下的数,0x30为ASCII码中数字组的开头,0x3k:显示k,即按下数字k
}
/************"="键被按下************/
if(num==15)//按下的键为=键
{
switch(flag)//判断进行哪种运算
{
/*******加法运算*********/
case 1://加法运算
{
c=a+b;//计算结果
lcdwrc(0x4f/*第二行末尾位置*/+0x80);//光标置于第二行末尾
lcdwrc(0x04);//设置显示方式:显示后指针减一,即前移一位
if(c==0)//若结果为0
lcdwrd(0x30);//显示0
while(c!=0)
{
lcdwrd(0x30+c%10);//从后向前显示最后一位
c=c/10;//去掉最后一位后再循环显示
}
lcdwrd(0x3d);//显示完计算结果后,显示“=”
}break;
/*******减法运算*********/
case 2://减法运算
{
if(a>b)//大数减小数
c=a-b;
else//小数减大数
c=b-a;//计算两数相减的绝对值
lcdwrc(0x4f+0x80);//将光标置于第二行末尾
lcdwrc(0x04);//设置显示方式:显示后指针减一
if(c==0)//若结果为零
lcdwrd(0x30);//显示0
while(c!=0)
{
lcdwrd(0x30+c%10);//从后向前显示最后一位
c=c/10;//去掉最后一位后再循环显示
}
if(a0&&x<=9)//0.0001到0.0009
{
if(k==1)//只有小数点后最后一位 x.000x
{
lcdwrd(0x30);//0
lcdwrd(0x30);//0
lcdwrd(0x30);//0
lcdwrd(0x2e);//.
k=0;
}
}
if(x>9&&x<=99)//0.0010到0.0099
{
if(k==2)//只有小数点后最后两位 x.00xx
{
lcdwrd(0x30);//0
lcdwrd(0x30);//0
lcdwrd(0x2e);//.
k=0;
}
}
if(x>99&&x<=999)//0.0100到0.0999
{
if(k==3)//只有小数点后最后三位 x.0xxx
{
lcdwrd(0x30);//0
lcdwrd(0x2e);//.
k=0;
}
}
else if(k==4)//小数点后四位都非零 x.xxxx
{
lcdwrd(0x2e);//.
}
}
if(x<10000)//若结果小于1,在个位补0
lcdwrd(0x30);//个位补0
lcdwrd(0x3d);//显示=
k=0;//将k定回0用于下次计算
}
} break;
}
}
}
while(P1!=0xf0);break;//当有按键被按下时,结束此次判断,进行下一次判断
/********************第四列有按键按下*********************/
case 0x0e://0000 1110 第四列有按键被按下
{
fuhao=1;//有符号键被按下
P1=0xf0;//1111 0000
switch(P1)
{
case 0x70:/*0111 0000*/flag=4;lcdwrd(0xfd);break;//“/”第四列第一行
case 0xb0:/*1011 0000*/flag=3;lcdwrd(0x2a);break;//“*”第四列第二行
case 0xd0:/*1101 0000*/flag=2;lcdwrd(0x2d);break;//“-”第四列第三行
case 0xe0:/*1110 0000*/flag=1;lcdwrd(0x2b);break;//“+”第四列第四行
}
}
while(P1!=0xf0);break;
}
}
}
}
5 演示
6 结论
制作过程中建议先学会使用单个模块的功能,再将这些部分拼接起来,从而组成完整的计算器。
7 源代码
#include
typedef unsigned int uint; //定于无符号int
typedef unsigned char uchar; //定义无符号char
/*****************************定义引脚**********************************/
sbit rw=P2^5;
sbit rs=P2^6;
sbit e=P2^7;
/*****************************定义变量**********************************/
uint fuhao,flag,k,i,j,num;
uchar ERROR[]=" ERROR!";//除数为0时,显示
uchar wel[]=" Welcome!";//初始化时显示
uchar MODE1[]=" MODE1";
long a,b,c,x;
/*****************************延时函数**********************************/
void delay(unsigned int xms)//延时x毫秒
{
unsigned int i,j;
for(i=xms;i>0;i--)
for(j=112;j>0;j--);
return;
}
/*****************************LCD写命令————位置**********************************/
void lcdwrc(uint c)
{
delay(100);
rs=0;
rw=0;
e=0;
P0=c;
e=1;
delay(100);
e=0;
}
/*****************************LCD写数据————内容**********************************/
void lcdwrd(uint dat) //设置LCD写数据
{
delay(100);
rs=1;
rw=0;
e=0;
P0=dat;
e=1;
delay(100);
e=0;
rs=0;
}
/*****************************初始化LCD**********************************/
void lcdinit() //初始化LCD
{
delay(150);
lcdwrc(0x38);//写指令38H
delay(50);
lcdwrc(0x38);//显示模式设置
delay(50);
lcdwrc(0x38);
delay(50);
lcdwrc(0x38);//功能设定指令
lcdwrc(0x08);
lcdwrc(0x01);//清屏
lcdwrc(0x06);//显示光标移动设置
lcdwrc(0x0c); //显示开关控制指令
num=0;//用于判定按键是否为“=”或者“归零”时的变量
fuhao=0;//用于判定是否为运算符号
flag=0;//用于判定运算方式时的变量
a=0;//计算的第一个数
b=0;//计算的后一个数
c=0;//计算结果
x=0;//判定小数点的变量,暂存c值
k=0;//判定小数点的变量,小数点后非零数个数
j=0;//显示ERROR时的循环用变量
}
/*****************************模式判定函数**********************************/
void MODE()
{
if(P2_0==0)
{
for(j=0;j<10;j++)
lcdwrd(MODE1[j]);//显示语句:MODE1
delay(1000);//延时
lcdinit();//重新初始化LCD1602
}
}
/*****************************计算器函数**********************************/
void KeyDown()//计算器函数
{
k=0;
P1=0x0f; //0000 11111,行都为低电平0,列都为高电平
if(P1!=0x0f)//P1不为0x0f,则判定为有按键被按下
{
delay(100);//消抖
if(P1!=0x0f)//延时后,再次判断
{
P1=0x0f;//0000 11111,行都为低电平0,列都为高电平
switch(P1)//
{
/********************第一列有按键按下*********************/
case 0x07://0000 0111 第一列有按键被按下
{
P1=0xf0;//列都为低电平0,行都为高电平1
switch(P1)//判断哪一行的按键被按下
{
case 0x70: num = 1/*S1*/;k=7;break; //0111 0000第一列第一行 数字7
case 0xb0: num = 5/*S5*/;k=4;break; //1011 0000第一列第二行 数字4
case 0xd0: num = 9/*S9*/;k=1;break; //1101 0000第一列第三行 数字1
case 0xe0: num = 13/*S13*/;break; //1110 0000第一列第四行 归零
}
if(num!=13)//被按下的按键不是归零键
{
if(fuhao==0)//fuhao==0,表示第一个数a,fuhao==1,表示第二个数b
a=a*10+k;
else
b=b*10+k;
}
lcdwrd(0x30+k);//显示按下的数,0x30为ASCII码中数字组的开头,0x3k:显示k,即按下数字k
if(num==13)//按下的按键是清零键
{
lcdwrc(0x01); //清屏指令
lcdinit();//重新初始化LCD1602
}
}while(P1!=0xf0);break;//当有按键被按下时,结束此次判断,进行下一次判断
/********************第二列有按键按下*********************/
case 0x0b: //0000 1011 第二列有按键被按下
{
P1=0xf0;//行都为高电平1,列都为低电平0
switch(P1)//判断哪一行有按键被按下
{
case 0x70: k=8;break;//第二列第一行 数字8
case 0xb0: k=5;break;//第二列第二行 数字5
case 0xd0: k=2;break;//第二列第三行 数字2
case 0xe0: k=0;break;//第二列第四行 数字0
}
if(fuhao==0)//fuhao==0,表示第一个数a,fuhao==1,表示第二个数b
a=a*10+k;
else
b=b*10+k;
lcdwrd(0x30+k);//显示按下的数,0x30为ASCII码中数字组的开头,0x3k:显示k,即按下数字k
} while(P1!=0xf0);break;//当有按键被按下时,结束此次判断,进行下一次判断
/********************第三列有按键按下*********************/
case 0x0d: //0000 1101 第三列有按键被按下
{
P1=0xf0;//行都为高电平1,列都为低电平0
switch(P1)//判断哪一行有按键被按下
{
case 0x70: num=3;k=9;break;//第三列第一行 数字9
case 0xb0: num=7;k=6;break;//第三列第二行 数字6
case 0xd0: num=11;k=3;break;//第三列第三行 数字3
case 0xe0: num=15;break;//第三列第四行 “=”键
}
if(num!=15)//按下的键不为“=”键
{
if(fuhao==0)//fuhao==0,表示第一个数a,fuhao==1,表示第二个数b
a=a*10+k;
else
b=b*10+k;
lcdwrd(0x30+k);//显示按下的数,0x30为ASCII码中数字组的开头,0x3k:显示k,即按下数字k
}
/************"="键被按下************/
if(num==15)//按下的键为=键
{
switch(flag)//判断进行哪种运算
{
/*******加法运算*********/
case 1://加法运算
{
c=a+b;//计算结果
lcdwrc(0x4f/*第二行末尾位置*/+0x80);//光标置于第二行末尾
lcdwrc(0x04);//设置显示方式:显示后指针减一,即前移一位
if(c==0)//若结果为0
lcdwrd(0x30);//显示0
while(c!=0)
{
lcdwrd(0x30+c%10);//从后向前显示最后一位
c=c/10;//去掉最后一位后再循环显示
}
lcdwrd(0x3d);//显示完计算结果后,显示“=”
}break;
/*******减法运算*********/
case 2://减法运算
{
if(a>b)//大数减小数
c=a-b;
else//小数减大数
c=b-a;//计算两数相减的绝对值
lcdwrc(0x4f+0x80);//将光标置于第二行末尾
lcdwrc(0x04);//设置显示方式:显示后指针减一
if(c==0)//若结果为零
lcdwrd(0x30);//显示0
while(c!=0)
{
lcdwrd(0x30+c%10);//从后向前显示最后一位
c=c/10;//去掉最后一位后再循环显示
}
if(a0&&x<=9)//0.0001到0.0009
{
if(k==1)//只有小数点后最后一位 x.000x
{
lcdwrd(0x30);//0
lcdwrd(0x30);//0
lcdwrd(0x30);//0
lcdwrd(0x2e);//.
k=0;
}
}
if(x>9&&x<=99)//0.0010到0.0099
{
if(k==2)//只有小数点后最后两位 x.00xx
{
lcdwrd(0x30);//0
lcdwrd(0x30);//0
lcdwrd(0x2e);//.
k=0;
}
}
if(x>99&&x<=999)//0.0100到0.0999
{
if(k==3)//只有小数点后最后三位 x.0xxx
{
lcdwrd(0x30);//0
lcdwrd(0x2e);//.
k=0;
}
}
else if(k==4)//小数点后四位都非零 x.xxxx
{
lcdwrd(0x2e);//.
}
}
if(x<10000)//若结果小于1,在个位补0
lcdwrd(0x30);//个位补0
lcdwrd(0x3d);//显示=
k=0;//将k定回0用于下次计算
}
} break;
}
}
}
while(P1!=0xf0);break;//当有按键被按下时,结束此次判断,进行下一次判断
/********************第四列有按键按下*********************/
case 0x0e://0000 1110 第四列有按键被按下
{
fuhao=1;//有符号键被按下
P1=0xf0;//1111 0000
switch(P1)
{
case 0x70:/*0111 0000*/flag=4;lcdwrd(0xfd);break;//“/”第四列第一行
case 0xb0:/*1011 0000*/flag=3;lcdwrd(0x2a);break;//“*”第四列第二行
case 0xd0:/*1101 0000*/flag=2;lcdwrd(0x2d);break;//“-”第四列第三行
case 0xe0:/*1110 0000*/flag=1;lcdwrd(0x2b);break;//“+”第四列第四行
}
}
while(P1!=0xf0);break;
}
}
}
}
/*****************************主函数**********************************/
void main()
{
uint x;
lcdinit();//LCD1602初始化
for(x=0;x<12;x++)
lcdwrd(wel[x]);//显示欢迎语句:Welcome
delay(1000);
lcdwrc(0x01);//LCd1602清屏
while(1)
{
MODE();//功能判定函数
KeyDown();//按键判断函数
}
}