设计一个基于ADuC848的简易计算器,基本功能如下,可以自行添加另外功能。
基本功能:
1)利用小键盘作为数据和命令的输入装置
2)利用LED或LCD作为过程和结果的输出装置
3)可实现基本的数据运算,包括:加、减、乘、除、开方等
4)未工作时显示北京时间,可调整
5)当数据超出范围,可声音提示或报警
6)可通过按键控制,随机模拟滚动显示乘法口诀表
模块分析:
需要用到键盘模块、液晶模块(采用LCD1602)、DS1307模块等
问题设想:
在编写代码之前,我们不妨考虑下要处理哪些问题或者现象,这样便能在第一次编写时优化代码,节省时间。
类型一:闭锁问题
1)由于最开始是显示北京时间,那么我们肯定需要一个按键实现运算模式的切换。那么当我们按下这个按键之后,输入数但还没有结束运算过程,再次按下该按键时,应该会怎样(无效?重新运算?)。如果是无效,那么就涉及到闭锁,在按下按键之前没有按下实现等于功能的按键,就操作无效,没有反应或者报错。
2)在成功切换到运算模式后,能直接先按下加减乘除的按键吗?显然,这是不符合数学运算逻辑的,所以也需要涉及到闭锁。处理方式可以是必须输入至少一位数后才允许按下运算符号的按键有效!!
3)同样,按下运算符号的按键后立马按下等于的按键也是不合理的,处理方式相同,必须先输入第二个数(至少一位)。
4)有时候,按键处理的代码并不是很完善,当我们按下按键后,可能会发生在液晶屏幕上光标连跳两位的现象。当然我们可以通过按键处理的优化解决这个问题,这里我们也可以采用闭锁的方法(具体在之后的代码会详细体现)。
类型二:运算函数功能问题(由于简易计算器的功能还是比较多,不少于16个按键,处理还是一键一用,所以就暂时省略了小数点的功能~~)
这个类型主要是针对除法和开方!在C语言中,比如6/4=1,6%4=2,而没有专门的函数实现6除于4等于1.5;另外,开方函数得到是一个单精度浮点型数据,所以要单独处理!!
代码分析:
1.运算部分
首先可以建一个C文件用于写数学处理,加减乘除等。
#include<yunsuan.h>
#include<math.h> //记得要包含math.h库文件
//加法
uint add(uint a,uint b)
{
uint temp;
temp=a+b;
return temp;
}
//减法
uint sub(uint a,uint b)
{
uint temp;
temp=a-b;
return temp;
}
//乘法
uint cheng(uint a,uint b)
{
uint temp;
temp=a*b;
return temp;
}
//除法
uint chu(uint a,uint b)
{
uint temp;
temp=a/b;
return temp;
}
/*
//开方
uint kaifang(uint a)
{
uint temp;
temp=sqrt(a);
return temp;
} */
//运算
int calculate(uint a,uint b,uchar c)
{
//加法
if(c==1)
{
return add(a,b);
}
//减法
if(c==2)
{
return sub(a,b);
}
//乘法
if(c==3)
{
return cheng(a,b);
}
//除法
if(c==4)
{
return chu(a,b);
}
/* //开方
if(c==5)
{
return kaifang(a);
} */
}
2.液晶模块
这块代码和智能电压监视仪的完全一样,就不弄上来了。
3.键盘模块
#include<lcd1602.h>
#include<jianpan.h>
#include<DS1307.h>
#include<main.h>
#include<yunsuan.h>
#include<math.h>
idata uchar id=1; //跳出显示时间界面的标志量,id=0,跳出;否则,继续显示。
idata uchar kcount=0; //按键次数量,亦即光标显示位置的标志量
idata uchar kcount1; //当与kcount相等时,说明没按下光标移动键
bit tflag=0; //改动时间标志,tflag=1时能改动时间,tflag=0时锁存时间,禁止改动。
uchar count=0; //table数组标号,0对应数字0······
uint pos=0;
bit pos_lock; //锁闭确定键,防止按一次跳多位;为0开锁,为1闭锁。
bit yunsuan_lock; //符号锁,输入一个运算符号,就会锁定,即一个算式内不能再输入运算符号。为0开锁,为1闭锁。
bit startcal_lock; //开启运算锁,当输入数字时闭锁;为0开锁,为1闭锁。
bit gundong_lock=0; //滚动锁,为0开锁,为1闭锁。
uchar code table[]={0,1,2,3,4,5,6,7,8,9};
uchar code table1[]={"we are"};
uchar value1[4];
uchar value2[4];
uchar value_count=0;
uint cal1,cal2; //计算对象
uint result;//结果
uchar len1=0;//第一位数的数值长度
uchar len2=0;//第二位数的数组长度
//uchar len3=0;//结果的数值长度
uchar cal_flag;//运算标志号,加法对应1,减法对应2,乘法对应3,除法对应4,开方对应5
void delay() //延时函数
{
uchar i;
for(i = 0;i < 100;i++);
}
void longdelay()
{
uint i;
for(i = 0;i < 65000;i++);
}
void delay_beep(unsigned int z)
{
unsigned int x,y;
for(x=z;x>0;x--)
{
for(y=100;y>0;y--);
}
}
//报警
void alarm()
{
uint i;
uchar code error[]={"Error!Try again!"};
lcd_wc(0x01); //清屏
lcd_wc(0x80);
for(i=0;i<16;i++)
{
lcd_wd(error[i]);
}
for(i=0;i<3;i++)
{
beep=0;delay_beep(10);
beep=1;delay_beep(10);
}
}
//开方显示
void sqrt_display()
{
uchar Fir_bit,Sec_bit,Thi_bit,Fou_bit;
float d;
uint d1;
if(len1==1||len1==2)
{ //数只有一位或两位,开方后结果是1位
d=sqrt(cal1)*1000;
d1=d;
Fir_bit=d1/1000;
Sec_bit=d1/100%10;
Thi_bit=d1/10%10;
Fou_bit=d1%10;
lcd_wc(0x80+pos);
lcd_wd(0x30+Fir_bit);
lcd_wc(0x80+pos+1);
lcd_wd('.');
lcd_wc(0x80+pos+2);
lcd_wd(0x30+Sec_bit);
lcd_wc(0x80+pos+3);
lcd_wd(0x30+Thi_bit);
lcd_wc(0x80+pos+4);
lcd_wd(0x30+Fou_bit);
}
if(len1==3||len1==4)
{ //数有三位或四位,结果两位
d=sqrt(cal1)*100;
d1=d;
Fir_bit=d1/1000;
Sec_bit=d1/100%10;
Thi_bit=d1/10%10;
Fou_bit=d1%10;
lcd_wc(0x80+pos);
lcd_wd(0x30+Fir_bit);
lcd_wc(0x80+pos+1);
lcd_wd(0x30+Sec_bit);
lcd_wc(0x80+pos+2);
lcd_wd('.');
lcd_wc(0x80+pos+3);
lcd_wd(0x30+Thi_bit);
lcd_wc(0x80+pos+4);
lcd_wd(0x30+Fou_bit);
}
if(len1==5||len1==6)
{ //数有五位或六位,结果三位
d=sqrt(cal1)*10;
d1=d;
Fir_bit=d1/1000;
Sec_bit=d1/100%10;
Thi_bit=d1/10%10;
Fou_bit=d1%10;
lcd_wc(0x80+pos);
lcd_wd(0x30+Fir_bit);
lcd_wc(0x80+pos+1);
lcd_wd(0x30+Sec_bit);
lcd_wc(0x80+pos+2);
lcd_wd(0x30+Thi_bit);
lcd_wc(0x80+pos+3);
lcd_wd('.');
lcd_wc(0x80+pos+4);
lcd_wd(0x30+Fou_bit);
}
}
//结果显示
void result_display()
{
uchar Fir_bit,Sec_bit,Thi_bit;
/* if(calculate(cal1,cal2,cal_flag)<-65536||calculate(cal1,cal2,cal_flag)>65536)
{
alarm();
}*/
if(calculate(cal1,cal2,cal_flag)>999)
{ //超出范围,报警
alarm();
}
else
{
if(calculate(cal1,cal2,cal_flag)<0)
{
Fir_bit= (calculate(cal2,cal1,cal_flag))/100;
Sec_bit= (calculate(cal2,cal1,cal_flag))/10%10;
Thi_bit= (calculate(cal2,cal1,cal_flag))%10;
lcd_wc(0x80+pos);
lcd_wd('-');
pos++;
}
else
{
Fir_bit= (calculate(cal1,cal2,cal_flag))/100;
Sec_bit= (calculate(cal1,cal2,cal_flag))/10%10;
Thi_bit= (calculate(cal1,cal2,cal_flag))%10;
}
if(Fir_bit!=0)
{
lcd_wc(0x80+pos);
lcd_wd(0x30+Fir_bit);
lcd_wc(0x80+pos+1);
lcd_wd(0x30+Sec_bit);
lcd_wc(0x80+pos+2);
lcd_wd(0x30+Thi_bit);
}
if(Fir_bit==0&&Sec_bit!=0)
{
lcd_wc(0x80+pos);
lcd_wd(0x30+Sec_bit);
lcd_wc(0x80+pos+1);
lcd_wd(0x30+Thi_bit);
}
if(Fir_bit==0&&Sec_bit==0)
{
lcd_wc(0x80+pos);
lcd_wd(0x30+Thi_bit);
}
}
}
//清空存储数组
void clear()
{
uchar i;
for(i=0;i<len1;i++)
{
value1[i]=0;
}
for(i=0;i<len2;i++)
{
value2[i]=0;
}
}
//获得第一个数
uint getcal1(uint len)
{
uint temp;
uchar temp1,temp2,temp3,temp4,temp5;
if(len==1)
{
temp=value1[0];
}
if(len==2)
{
temp1=value1[0];
temp2=value1[1];
temp=temp1*10+temp2;
}
if(len==3)
{
temp1=value1[0];
temp2=value1[1];
temp3=value1[2];
temp=temp1*100+temp2*10+temp3;
}
if(len==4)
{
temp1=value1[0];
temp2=value1[1];
temp3=value1[2];
temp4=value1[3];
temp=temp1*1000+temp2*100+temp3*10+temp4;
}
if(len==5)
{
temp1=value1[0];
temp2=value1[1];
temp3=value1[2];
temp4=value1[3];
temp5=value1[4];
temp=temp1*10000+temp2*1000+temp3*100+temp4*10+temp5;
}
return temp;
}
//获得第二个数
uint getcal2(uint len)
{
uint temp;
uchar temp1,temp2;
if(len==1)
{
temp=value2[0];
}
if(len==2)
{
temp1=value2[0];
temp2=value2[1];
temp=temp1*10+temp2;
}
return temp;
}
//光标移动
void move()
{
if(tflag)
{
//调节时间时光标移动情况
//超过范围则光标复位
if(kcount>16)
{
kcount=1;
}
//遇到非数字符号调到下一位有效位
if(kcount==3||kcount==6||kcount==11||kcount==14)
{
kcount++;
}
lcd_wc(0x0f);//光标显示并闪烁
//光标闪烁位置
if(kcount>8)
{
lcd_wc(0xC0+kcount-9); //当kcount大于8时,光标从第二行开始闪烁
}
else
{
lcd_wc(0x80+kcount-1); //当kcount小于8时,光标从第一行开始闪烁
}
//当不再按键时,光标停留在原位上
kcount1=kcount;
delay();
while(kcount==kcount1)
{
Keyscan();
}
}
tflag=0;
}
//滚动显示
void gundong() //存在问题,还没有完善!!!!!!!
{
uchar cheng1[]={"1*1=1"};
uchar cheng2[]={"1*2=2"};
uchar cheng3[]={"1*3=3"};
uchar cheng4[]={"1*4=4"};
uint i=0;
lcd_wc(0x80);
for(i=0;i<5;i++)
{
lcd_wd(cheng1[i]);
}
longdelay();
lcd_wc(0xc0);
for(i=0;i<5;i++)
{
lcd_wd(cheng2[i]);
}
longdelay();
lcd_wc(0x80);
for(i=0;i<5;i++)
{
lcd_wd(cheng3[i]);
}
longdelay();
lcd_wc(0xc0);
for(i=0;i<5;i++)
{
lcd_wd(cheng4[i]);
}
}
//lcd屏幕显示
void lcd_display(uchar key)
{
switch(key)
{
case 1: //开启运算
if(startcal_lock==0)
{
count=0;
gundong_lock=1;
lcd_wc(0x01);//清屏
lcd_wc(0x0f);//光标闪烁
lcd_wc(0x80);
lcd_wd(0x30+table[count]);
lcd_wc(0x80);
}
break;
case 2: //加值
count++;
if(count>9)
{
count=0;
}
lcd_wc(0x80+pos);
lcd_wd(0x30+table[count]);
lcd_wc(0x80+pos);
break;
case 3: //减值
if(count==0)
{
count+=10;
}
count--;
lcd_wc(0x80+pos);
lcd_wd(0x30+table[count]);
lcd_wc(0x80+pos);
break;
case 4: //确认值
if(pos_lock==0)
{
pos++;
lcd_wc(0x80+pos);
if(yunsuan_lock==0)
{
value1[value_count]=table[count]; //储存第一个数位数值
len1++;
}
if(yunsuan_lock==1)
{
value2[value_count]=table[count]; //储存第二个数位数值
len2++;
}
value_count++;
}
pos_lock=1;
count=0;//count清零,下一位从0开始显示
break;
case 5: //加法
if(yunsuan_lock==0&&pos!=0)
{
lcd_wc(0x80+pos);
lcd_wd('+');
cal_flag=1; //加法标号为1
yunsuan_lock=1; //不能再输入其他运算符号
cal1=getcal1(len1);//获得第一位数
value_count=0; //存储数组重新存储
if(pos_lock==0)
{
pos++;
lcd_wc(0x80+pos);
}
pos_lock=1;
}
break;
case 6: //减法
if(yunsuan_lock==0&&pos!=0)
{
lcd_wc(0x80+pos);
lcd_wd('-');
cal_flag=2; //减法标号为2
yunsuan_lock=1; //不能再输入其他运算符号
cal1=getcal1(len1);//获得第一位数
value_count=0; //存储数组重新存储
if(pos_lock==0)
{
pos++;
lcd_wc(0x80+pos);
}
pos_lock=1;
}
break;
case 7: //乘法
if(yunsuan_lock==0&&pos!=0)
{
lcd_wc(0x80+pos);
lcd_wd('*');
cal_flag=3; //乘法标号为3
yunsuan_lock=1; //不能再输入其他运算符号
cal1=getcal1(len1);//获得第一位数
value_count=0; //存储数组重新存储
if(pos_lock==0)
{
pos++;
lcd_wc(0x80+pos);
}
pos_lock=1;
}
break;
case 8: //除法
if(yunsuan_lock==0&&pos!=0)
{
lcd_wc(0x80+pos);
lcd_wd('/');
cal_flag=4; //除法标号为4
yunsuan_lock=1; //不能再输入其他运算符号
cal1=getcal1(len1);//获得第一位数
value_count=0; //存储数组重新存储
if(pos_lock==0)
{
pos++;
lcd_wc(0x80+pos);
}
pos_lock=1;
}
break;
case 9: //开方
if(yunsuan_lock==0&&pos!=0)
{
lcd_wc(0x80+pos);
lcd_wd('$');
cal_flag=5; //除法标号为5
yunsuan_lock=1; //不能再输入其他运算符号
cal1=getcal1(len1);//获得第一位数
value_count=0; //存储数组重新存储
if(pos_lock==0)
{
pos++;
lcd_wc(0x80+pos);
}
pos_lock=1;
}
break;
case 10: //等于
if(len2!=0||cal_flag==5)
{
lcd_wc(0x80+pos);
lcd_wd('=');
yunsuan_lock=1; //不能再输入其他运算符号
cal2=getcal2(len2);
//显示结果
pos++;
if(cal_flag==5)
{
sqrt_display();
}
// lcd_wc(0x80+pos);
// lcd_wd(0x30+calculate(cal1,cal2,cal_flag));
else
{
result_display();
}
if(pos_lock==0)
{
pos++;
lcd_wc(0x80+pos);
}
count=0;
pos=0;//下一次光标复位
clear();//清空存储两个数数值的数组
len1=0;
len2=0;
value_count=0;
startcal_lock=0;//可以再次开启运算
yunsuan_lock=0;//下次可以重新输入运算符
pos_lock=1;
lcd_wc(0x0c);//关光标
}
break;
case 12: //时间显示
Check_Busy();
lcd_wc(lcd_clear);
lcd_wc(0x0c);
id=1;
tflag=1;
display();
break;
}
}
//键盘扫描
void Keyscan()
{
unsigned char temp;
unsigned int j=0;
P1&=0xf0; //P1口低四位作为数字输入时应往相应引脚写0
P2&=0xf0; //置列扫描信号为0
temp=P1&0x0f;
if(temp!=0x0f) //P1口低四位不全为1,则有键被按下
{
delay(); //延时消抖
if(temp==(P1&0x0f)) //消抖后再判断键值
{
P2|=0x07; //P2.3为0
switch(P1&0x0f)
{
case 0x0e://开启运算
id=0;//跳出时间显示
lcd_display(1);
break;
case 0x0d: //加法
pos_lock=0;
lcd_display(5);
break;
case 0x0b: //开方
pos_lock=0;
lcd_display(9);
break;
case 0x07://光标闪烁
kcount++;
move();
break;
case 0x0f:break;
default:break;
}
P2&=0xf0;
P2|=0x0b; //P2.2为0
switch(P1&0x0f)
{
case 0x0e: //加值
pos_lock=0;
startcal_lock=1;
lcd_display(2);
break;
case 0x0d://减法
pos_lock=0;
lcd_display(6);
break;
case 0x0b: //等于
pos_lock=0;
lcd_display(10);
break;
case 0x07: //加光标所在位置的数值
addValue(kcount);
break;
case 0x0f:break;
default:break;
}
P2&=0xf0;
P2|=0x0d; //P2.1为0
switch(P1&0x0f)
{
case 0x0e: //减值
pos_lock=0;
startcal_lock=1;
lcd_display(3);
break;
case 0x0d: //乘法
pos_lock=0;
lcd_display(7);
break;
case 0x0b: //滚动显示乘法口诀表
id=0;
lcd_wc(0x01);
lcd_wc(0x07);
lcd_wc(0x1c);
while(!gundong_lock)
{
gundong();
Keyscan();
}
lcd_wc(0x0c);
break;
case 0x07: //减光标所在位置的数值
subValue(kcount);
break;
case 0x0f:break;
default:break;
}
P2&=0xf0;
P2|=0x0e; //P2.0为0
switch(P1&0x0f)
{
case 0x0e: //确认值
lcd_display(4);
break;
case 0x0d: //除法
pos_lock=0;
lcd_display(8);
break;
case 0x0b: //时间显示
lcd_display(12);
break;
case 0x07: //确认调整时间完毕
id=1;
makeSure();
break;
case 0x0f:break;
default:break;
}
}
}
}
/*
uchar getk()
{
return kcount;
} */
//设置按键次数量,主要负责清零重置
void setk(uchar count)
{
kcount=count;
}
//获得跳出显示时间界面的标志量
uchar getid()
{
return id;
}
//获得运算符号
uchar getcal_flag()
{
return cal_flag;
}
运算模式的过程是:通过按加减键得到0~9的任一位,然后再按确定键确定输入。得到第一个数和第二个数的方法也很简单,就是建两个数组,分别储存每一位,然后再组合起来;或者建一个数组,在得到第一个数后清空,再放第二位数的各位。此外,我还编写了获得结果的函数,主要是控制结果的输出精度。
4.主函数模块
同样和上篇博客的一模一样,故也不放上来了。
这样,就能实现一个简易计算器的功能。