简易计算器
实验说明
利用51单片机和keil uVision4共同完成通过按键来控制8位共阴数码管显示数据,并且通过按键来完成连续的加减乘除运算,文章中还有实物连接图。
按键图如下:
上面代表的数字及运算符相应位置为:
0 1 2 3
4 5 6 7
8 9 + -
- / 清零 =
硬件设计
动态数码管介绍
多位数码管,即是两个或两个以上单个数码管并列集中在一起形成一体的数码管。当多位一体时,它们内部的公共端是独立的,而负责显示什么数字的段线全部是连接在一起的,独立的公共端可以控制多位一体中的哪一位数码管点亮,而连接在一起的段线可以控制这个能点亮数码管亮什么数字,通常我们把公共端叫做“位选线”,连接在一起的段线叫做“段选线”,有了这两个线后,通过单片机及外部驱动电路就可以控制任意的数码管显示任意的数字了。
动态显示,就是利用减少段选线,分开位选线,利用位选线不同时选择通
断,改变段选数据来实现的。比如在第一次选中第一位数码管时,给段选数据 0,下一次位选中第二位数码管时显示 1。为了在显示 1 的时候,0 不会消失( 当然实际上是消失了),必须在人肉眼观察不到的时间里再次点亮第一次点亮的 0。而这时就需要记住,人的肉眼正常情况下只能分辨变化超过 24ms 间隔的运动。也就是说,在下一次点亮 0 这个数字的时间差不得大于 24ms。这时就会发现,数码管点亮是在向右或者向左一位一位点亮, 形成了动态效果。 如果把间隔时间改长就能直接展现这现象。
考虑到 51 单片机 IO 口资源的限制,通常我们会使用一种 IO 扩展芯片,比如 74HC138、74HC164、74HC595 芯片等,只需要很少的单片机 IO 口就可以扩展出 8 个控制口,通过级联方式甚至可扩展出更多的控制口。我的开
发板上使用的是 74HC138 译码器芯片,只需单片机 3 个 IO 口就可以实现 8 个位选管脚的控制,大大节省了芯片的 IO 资源。
具体的动态数码管以及IO 扩展芯片之间的关系原理可百度搜索,这里不多讲。总之你要知道IO 扩展芯片只需单片机 3 个 IO 口就可以实现对8个位选管脚的控制。
矩阵按键介绍
开发板上将 16 个按键排成 4 行 4 列,第一行将每个按键的一端连接在一起构成行线,第一列将每个按键的另一端连接在一起构成列线,这样便一共有 4 行 4 列共 8 根线,我们将这 8 根线连接到单片机的 8 个 I/O 口上,通过程序扫描键盘就可检测 16 个键。
单片机检测其是否被按下的依据都是一样的,也就是检测与该键对应的 I/O 口是否为低电平。矩阵键盘两端都与单片机 I/O 口相连,因此在检测时需编程通过单片机 I/O 口送出低电平。检测方法有多种,最常用的是行列扫描和线翻转法。这次实验使用的是行列扫描法
行列扫描
行列扫描法检测时,先送一列为低电平,其余几列全为高电平(此时我们确
定了列数),然后立即轮流检测一次各行是否有低电平,若检测到某一行为低电平(这时我们又确定了行数),则我们便可确认当前被按下的键是哪一行哪一列的,用同样方*流送各列一次低电平,再轮流检测一次各行是否变为低电平,这样即可检测完所有的按键,当有键被按下时便可判断出按下的键是哪一个键。当然我们也可以将行线置低电平,扫描列是否有低电平。从而达到整个键盘的检测
线翻转法
线翻转法,就是使所有行线为低电平时,检测所有列线是否有低电平,如果
有,就记录列线值;然后再翻转,使所有列线都为低电平,检测所有行线的值,由于有按键按下,行线的值也会有变化,记录行线的值。从而就可以检测到全部按键。
硬件实物连接
其中矩阵按键的8个排线和stc89c516的P1排线连接,74HC138 译码器芯片的3个排线A、B、C分别和stc89c516的P22、 P23、P24连接,控制动态数码管模块的8个排线和stc89c516的P0排线连接(注意:连线时不要连错了,当然连接取决于你的软件编码,这只是本篇文章的连接,你也可以根据你的软件编码设计来连接排线)
软件设计
通过以上的原理介绍,下面就直接编代码吧。
AC代码:
#include"reg51.h"
typedef unsigned char u8; //对数据类型进行声明定义
typedef unsigned int u16;
sbit LSA=P2^2; //74HC138译码器数码管位选
sbit LSB=P2^3;
sbit LSC=P2^4;
#define GPIO_KEY P1
#define GPIO_DIG P0
u16 KeyValue; //用来存放读取到的键值
u16 keyflag,i; //用来判断按下的数字还是运算符或清空键
u8 code smgduan[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x40}; //显示0~F、负号‘-’
u16 wei[8]={0}; //用来存放每一位数码管数字的数组
void delay(u16 i)//延时函数
{
while(i--);
}
void display() //扫描显示动态数码管
{
LSA=0; LSB=0; LSC=0; GPIO_DIG=smgduan[wei[7]];delay(50); GPIO_DIG=0x00; //消隐
LSA=1; LSB=0; LSC=0; GPIO_DIG=smgduan[wei[6]];delay(50); GPIO_DIG=0x00;
LSA=0; LSB=1; LSC=0; GPIO_DIG=smgduan[wei[5]];delay(50); GPIO_DIG=0x00;
LSA=1; LSB=1; LSC=0; GPIO_DIG=smgduan[wei[4]];delay(50); GPIO_DIG=0x00;
LSA=0; LSB=0; LSC=1; GPIO_DIG=smgduan[wei[3]];delay(50); GPIO_DIG=0x00;
LSA=1; LSB=0; LSC=1; GPIO_DIG=smgduan[wei[2]];delay(50); GPIO_DIG=0x00;
LSA=0; LSB=1; LSC=1; GPIO_DIG=smgduan[wei[1]];delay(50); GPIO_DIG=0x00;
LSA=1; LSB=1; LSC=1; GPIO_DIG=smgduan[wei[0]];delay(50); GPIO_DIG=0x00;
}
void KeyDown(void)//检测有按键按下并读取键值
{
u16 a=0;
GPIO_KEY=0x0f;
if(GPIO_KEY!=0x0f)//读取按键是否按下
{
delay(1000);//延时10ms进行消抖
if(GPIO_KEY!=0x0f)//再次检测键盘是否按下
{
//测试列
GPIO_KEY=0x0f;
switch(GPIO_KEY)//行列扫描法
{
case(0X07): KeyValue=0;break;
case(0X0b): KeyValue=1;break;
case(0X0d): KeyValue=2;break;
case(0X0e): KeyValue=3;break;
}
//测试行
GPIO_KEY=0xf0;
switch(GPIO_KEY)
{
case(0X70): KeyValue=KeyValue;break;
case(0Xb0): KeyValue=KeyValue+4;break;
case(0Xd0): KeyValue=KeyValue+8;break;
case(0Xe0): KeyValue=KeyValue+12;break;
}
if(KeyValue==0 || KeyValue==1 || KeyValue==2 || KeyValue==3 || KeyValue==4 || KeyValue==5 || KeyValue==6 || KeyValue==7 || KeyValue==8 || KeyValue==9)
{
keyflag=1;
}
while((a<50)&&(GPIO_KEY!=0xf0)) //检测按键松手检测
{
delay(1000);
a++;
}
}
}
}
main函数代码:
void main()
{
u16 a=0,b=0,c=0;
while(1)
{
display(); /* 第一个数字输入*/
KeyDown();
if(keyflag==1)
{
for(i=7;i>0;i--) //输入一位,数字向左移动一位
{wei[i]=wei[i-1];}
wei[0]=KeyValue;
keyflag=0;
}
else if(KeyValue==14)
{
for(i=0;i<8;i++)
wei[i]=0; //对数码管清零
display();
}
else if(KeyValue==10)//加法运算
{
a=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000; //计算a的值
for(i=0;i<8;i++)
wei[i]=0;
while(1) //输入第二个数
{
display();
KeyDown();
if(KeyValue==15) break;//当读到等于号,既,KeyValue=15时,停止输入
if(keyflag==1)
{
for(i=7;i>0;i--)
{wei[i]=wei[i-1];}
wei[0]=KeyValue;
keyflag=0;
}
}
b=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000; //计算b的值
c=a+b;
wei[0]=c%10; //计算C的各个位的数字
wei[1]=c/10%10;
wei[2]=c/100%10;
wei[3]=c/1000%10;
wei[4]=c/10000%10;
wei[5]=c/100000%10;
wei[6]=c/1000000%10;
wei[7]=c/10000000%10;
display();
}
else if(KeyValue==11)//减法运算
{
a=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000;
for(i=0;i<8;i++)
wei[i]=0;
while(1)
{ //输入第二个数
display();
KeyDown();
if(KeyValue==15) break;
if(keyflag==1)
{
for(i=7;i>0;i--)
{wei[i]=wei[i-1];}
wei[0]=KeyValue;
keyflag=0;
}
}
b=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000; //计算b的值
if(a>=b)
{
c=a-b;
wei[0]=c%10; //计算C的各个位的数字
wei[1]=c/10%10;
wei[2]=c/100%10;
wei[3]=c/1000%10;
wei[4]=c/10000%10;
wei[5]=c/100000%10;
wei[6]=c/1000000%10;
wei[7]=c/10000000%10;
}
if(a<b)//相减得负数
{
u16 e=0;//用于判断负号的位置
c=b-a;
wei[0]=c%10; //计算C的各个位的数字
wei[1]=c/10%10;
if(wei[1]==0)
{
wei[1]=16;
e=1;
}
wei[2]=c/100%10;
if(wei[2]==0 && e==0)
{
wei[2]=16;
e=1;
}
wei[3]=c/1000%10;
if(wei[3]==0 && e==0)
{
wei[3]=16;
e=1;
}
wei[4]=c/10000%10;
if(wei[4]==0 && e==0)
{
wei[4]=16;
e=1;
}
wei[5]=c/100000%10;
if(wei[5]==0 && e==0)
{
wei[5]=16;
e=1;
}
wei[6]=c/1000000%10;
if(wei[6]==0 && e==0)
{
wei[6]=16;
e=1;
}
wei[7]=c/10000000%10;
if(wei[7]==0 && e==0)
{
wei[7]=16;
e=1;
}
}
display();
}
else if(KeyValue==12)//乘法运算
{
a=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000;
for(i=0;i<8;i++)
wei[i]=0;
while(1)
{ //输入第二个数
display();
KeyDown();
if(KeyValue==15) break;
if(keyflag==1)
{
for(i=7;i>0;i--)
{wei[i]=wei[i-1];}
wei[0]=KeyValue;
keyflag=0;
}
}
b=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000; //计算b的值
c=a*b;
wei[0]=c%10; //计算C的各个位的数字
wei[1]=c/10%10;
wei[2]=c/100%10;
wei[3]=c/1000%10;
wei[4]=c/10000%10;
wei[5]=c/100000%10;
wei[6]=c/1000000%10;
wei[7]=c/10000000%10;
display();
}
else if(KeyValue==13)//除法运算
{
a=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000;
for(i=0;i<8;i++)
wei[i]=0;
while(1) //输入第二个数
{
display();
KeyDown();
if(KeyValue==15) break;
if(keyflag==1)
{
for(i=7;i>0;i--)
{wei[i]=wei[i-1];}
wei[0]=KeyValue;
keyflag=0;
}
}
b=wei[0]+wei[1]*10+wei[2]*100+wei[3]*1000+wei[4]*10000+wei[5]*100000+wei[6]*1000000+wei[7]*10000000; //计算b的值
c=a/b;
wei[0]=c%10; //计算C的各个位的数字
wei[1]=c/10%10;
wei[2]=c/100%10;
wei[3]=c/1000%10;
wei[4]=c/10000%10;
wei[5]=c/100000%10;
wei[6]=c/1000000%10;
wei[7]=c/10000000%10;
display(); //显示最终结果
}
}
}
实验现象
通过以上的硬件和软件设计(把两块代码放在main.c里面运行即可)后,这个简易计算器就大功告成了。
举例:
666/3*9-1314+520=?
你可以拿出计算器来算下看是否为这个数字
实验总结
这个简易计算器还有不足的地方,比如小数怎么处理,这里我就没有再深究了,有兴趣的博客可以再去深究,其实通过液晶显示屏和矩阵按键也可以来完成本次实验,效果可能更好;
最后代码中有不足或需要改善的地方求各位大佬博主指出,如果觉得本篇文章写得好,欢迎各位点赞、评论,谢谢大家!