【51单片机】七段数码管和矩阵键盘的综合实验——计算器(思路+仿真电路+源代码)

系列文章目录

【51单片机】矩阵键盘逐行扫描法仿真实验+超详细Proteus仿真和Keil操作步骤
【51单片机】点阵LED的显示实验
【51单片机】七段数码管显示实验+详细讲解
【51单片机】矩阵键盘线反转法实验仿真



前言

系列文章中的四篇是我学习单片机以来写下的4篇学习记录。在有了以上知识的了解后,我也掌握了部分80C51单片机的编程思想,当然80C51可以挂载很多不同的芯片和设备,还有很多内容需要学习的。就目前而言,对I/O设备的使用有了基础,平时也在学习中编写程序,这让我的小目标——做一个计算器,有了一定的基础。所以趁今天有时间,把这个计算器实现的过程记录下来。

一、程序思路

  • 首先,要做一个计算器,并且实现连续运算,键盘的功能就应该有数字键和四则运算符号键,并且,连续按下多个数字键可以得到多位数,即有十位、百位、千位;
  • 第二,进行连续运算的第二次符号输入时,即可输出上两个数字的运算结果
  • 第三,按下等号键,输出前两个数字的运算结果,并且可以继续输入符号和数字进行计算,而不代表结束。
  • 第三,按下清零键会有跑马灯提示已经清空。
  • 第四,数码管采用动态显示时,CPU被显示程序占用,无法在动态显示的同时扫描键盘。所以需要开中断,实现有键按下扫描键盘,无键按下动态显示的效果。

二、键盘线反转法+数码管动态显示

1、硬件仿真

首先应该放一个元件电路图,但是我做了两个,所以这里还是分成两个部分吧。在这一部分,我用的是矩阵键盘的线反转法和数码管的动态显示法来实现这个计算器的功能。当然,用到了数码管的动态显示,同时需要对键盘做出响应,就需要开中断了。于是电路图是这样的:
在这里插入图片描述
用到的元件有:80C51、BUTTON、7SEG-MPX4-CC-BLUE、RESPACK-8、4082(四输入的AND GATE),以及POWER

有了第一次对元件库中的KEYPAD-SMALLCALC的了解,它在采用线反转法时,没法在线反转后正常获取键的位置,当然,这了说的是仿真时出现的问题,属于封装问题,真实硬件则不许考虑这一问题。所以我自己用BUTTON做了一个键盘。

2、软件程序

1)初始化

include头文件,定义全局变量。

#include<reg51.h>
int key=0;//存放键值
int ans=0;//存放计算结果
int newnum=0;//存放第二个操作数
char op='\0';//存放运算符
int b[4]={10,10,10,10};//存放数字的个位、十位、百位、千位
int digit[11]={0x3f,0x06,0x5B,0x4f,0x66,0x6D,0x7D,0x07,0x7f,0x6f,0x71};//数字0~9对应的七段数码管字形码
int cs[4]={0x0E,0x0D,0x0B,0x07};//四位七段数码管的片选信号
int shownum=0;//存放需要显示的数

2)键盘扫描程序

void keyscan(){
	int temp;
	temp=P1&0x0f;//P1口高四位给低电平后,读入低四位
	if(temp!=0x0f){//低四位不全为高电平表示有键按下
		delayms(1);//软件去抖
		temp=P1&0x0f;//再读一次低四位
		if(temp!=0x0f){
			P1=0xf0;//第四位给低电平,读高四位
			key=temp|(P1&0xf0);//获得键值
		}
	}
	while(P1!=0xf0);//当按键抬起结束一次键盘扫描
	b[3]=b[2]=b[1]=b[0]=10;
	P1=0x0f;
}

3)定义按键的功能

void act(){
	switch(key){
		case 0x77:clear();turnLight();break;//清零键,清零+跑马灯
		case 0xB7:savenum(0);break;//按下数字0,保存数字0
		case 0xD7:output();break;//按下等于号,输出结果
		case 0xE7:saveop('+');break;//按下运算符,保存符号
		case 0x7B:savenum(1);break;
		case 0xBB:savenum(2);break;
		case 0xDB:savenum(3);break;
		case 0xEB:saveop('-');break;
		case 0x7D:savenum(4);break;
		case 0xBD:savenum(5);break;
		case 0xDD:savenum(6);break;
		case 0xED:saveop('*');break;
		case 0x7E:savenum(7);break;
		case 0xBE:savenum(8);break;
		case 0xDE:savenum(9);break;
		case 0xEE:saveop('/');break;
	}
}

4)主函数,把主要框架搭起来

void main(){
	int i;
	EA=1;//开总中断
  	EX0=1;//外部中断0开中断
 	IT0=0;//外部中断0设置低电平触发
	P1=0x0f;
	while(1){//显示程序
		for(i=0;i<4;i++){
			todigit(shownum);
			show();
		}
	}
}

5)中断服务程序

void int0()interrupt 0{
	keyscan();
	act();
}

6)其他函数

主要的程序是上面的主程序、键盘扫描程序、中断服务程序和按键功能配置函数。其他的函数可以按照自己需要的功能修改。

延时程序:
void delayms(int n){
	int i,j;
	for(i=0;i<n;i++)
		for(j=0;j<120;j++);
}
跑马灯函数:
void turnLight(){
	int light=0x03;
	int i=24;
	P2=0x00;
	while(i){
		P0=light;
		light=(light>>(8-1))|(light<<1);
		i--;
		delayms(50);
	}
}
显示函数:
void show(){
	int i;
	for(i=0;i<4;i++){
		P2=cs[i];
		P0=digit[b[i]];
		delayms(15);
	}
}
清零函数:
void clear(){
	int i;
	for(i=0;i<4;i++){
		b[i]=10;
	}
	key=0;
	ans=0;
	newnum=0;
	op='\0';
	shownum=0;
}
运算函数:
void operat(){
	switch(op){
		case '+':ans=ans+newnum;break;
		case '-':ans=(ans-newnum<0)?10000:ans-newnum;break;
		case '*':ans=ans*newnum;break;
		case '/':ans=(newnum==0)?10000:ans/newnum;break;
	}
	newnum=0;
}
保存符号:
void saveop(char p){
	if(op!='\0'){//如果已经有了符号,即这是第2+次运算,前面已经保存了两个操作数,需要先进行计算
		operat();
	}
	op=p;
	newnum=0;
	shownum=ans;//显示ans
}
保存数字:
void savenum(int n){
	if(op!='\0'){//如果已经有运算符保存,例如1+2,此时输入的数应该是2,则数字保存到newnum
		newnum=newnum*10+n;
		shownum=newnum;
	}
	else{//如果没有存有运算符,例如1+2的顺序,此时输入的应该是1,则保存到ans
		ans=ans*10+n;
		shownum=ans;
	}
}
转化字形码:
void todigit(int n){
	int i;
	if(n<10000){
		for(i=0;i<4;i++){
			b[3-i]=n%10;
			n=(n-b[3-i])/10;
			if(n==0) break;
		}
	}
	else b[3]=b[2]=b[1]=b[0]=10;//数字超过4位,输出‘F’
}
输出函数:
void output(){
	operat();
	shownum=ans;
}

3、效果

在这里插入图片描述

三、键盘线扫描法+数码管静态显示

1、硬件仿真

在采用逐行扫描法+静态显示的电路中,用到的元件有80C51、KEYPAD-SMALLCALC(键盘)、7SEG-MPX1-CC(七段数码管)×4、74LS273(锁存器)×4、RESPACK-8(电阻)以及GROUND和POWER
连接的电路图如下:
在这里插入图片描述
具体连线方式参照【51单片机】矩阵键盘逐行扫描法仿真实验+超详细Proteus仿真和Keil操作步骤【51单片机】七段数码管显示实验+详细讲解,这里不再赘述。

2、软件程序

大部分的程序与线反转法+动态显示的程序差不多,主要改变的是键盘扫描程序和显示的程序,并且对部分代码进行了优化。以下是改变的部分。
代码如下:

1)初始化

#include <reg51.h>
int ans=0;
int newnum=0;
char op='\0';
int b[4]={12,12,12,0};
int cs[4]={0x07,0x0B,0x0D,0x0E};
int digit[13]={0x3f,0x06,0x5B,0x4f,0x66,0x6D,0x7D,0x07,0x7f,0x6f,0x71,0x40,0x00};//在数码管上显示0~9、'F'、'-'和不显示
int p1line[4]={0xf7,0xfb,0xfd,0xfe};//逐行扫描法时给键盘的行值

2)主程序(键盘扫描程序)

void main(void){ 
	unsigned int key1=0;
	unsigned int key2=0;
	unsigned int key=0;
	int i;
	show(0);
    while (1){
		key=key1=key2=0;
	    P1=0xf0;
		key1=P1&0xf0;
	    if(key1!=0xf0){
		    delayms(1);
		    for(i=0;i<4;i++){
				P1=p1line[i];
				key2=P1&0xf0;
				if(key2!=0xf0){
					key=(p1line[i]&0x0f)|key1;
					break;
				}
			}
			while((P1&0xf0)!=0xf0);
			act(key);
		}
	}
 }

3)定义按键的功能

void act(){
	switch(key){
		case 0x77:clear();turnLight();break;//清零键,清零+跑马灯
		case 0xB7:savenum(0);break;//按下数字0,保存数字0
		case 0xD7:output();break;//按下等于号,输出结果
		case 0xE7:saveop('+');break;//按下运算符,保存符号
		case 0x7B:savenum(1);break;
		case 0xBB:savenum(2);break;
		case 0xDB:savenum(3);break;
		case 0xEB:saveop('-');break;
		case 0x7D:savenum(4);break;
		case 0xBD:savenum(5);break;
		case 0xDD:savenum(6);break;
		case 0xED:saveop('*');break;
		case 0x7E:savenum(7);break;
		case 0xBE:savenum(8);break;
		case 0xDE:savenum(9);break;
		case 0xEE:saveop('/');break;
	}
}

4)其他函数

主要的程序是上面的主程序、键盘扫描程序、中断服务程序和按键功能配置函数。其他的函数可以按照自己需要的功能修改。

延时程序:
void delayms(int n){
	int i,j;
	for(i=0;i<n;i++)
		for(j=0;j<120;j++);
}
跑马灯函数:
void turnlight(){
	int i=24;
	int light=0x03;
	while(i){
		P2=0x00;
		P0=light;
		P2=0xff;
		light=(light>>7)|(light<<1);
		delayms(50);
	}
}
显示函数:
void show(int num){
	int i;
	todigit(num);
	for(i=0;i<4;i++){
		P2=cs[i];
		P0=digit[b[i]];
		P2=0xff;
	}
}
清零函数:
void clear(){
	ans=newnum=0;
	op='\0';
	turnlight();
	show(0);
运算函数:
void operat(){
	switch(op){
		case '+':ans=ans+newnum;break;
		case '-':ans=ans-newnum;break;
		case '*':ans=ans*newnum;break;
		case '/':ans=(newnum==0)?10000:(ans/newnum);break;
	}
	newnum=0;
}
保存符号:
void saveop(char p){
	if(op!='\0')
		operat();
	op=p;
	show(ans);
}
保存数字:
void savenum(int n){
	if(op=='\0'){
		ans=ans*10+n;
		show(ans);
	}
	else{
		newnum=newnum*10+n;
		show(newnum);
	}
}
转化字形码:
void todigit(int num){
	int i;
	int j;
	if(num<10000&&num>= 0){
		for(i=0;i<4;i++){
			b[i]=num%10;
			num=(num-b[i])/10;
			if(num==0) break;
		}
		for(j=i+1;j<4;j++){//没有的位上不显示
			b[j]=12;
		}
	}
	else if(num>=-999&&num<0){
		num=-num;
		for(i=0;i<4;i++){
			b[i]=num%10;
			num=(num-b[i])/10;
			if(num==0) break;
		}
		b[++i]=11;//显示负号
		for(j=i+1;j<4;j++){
			b[j]=12;
		}
	}
	else b[0]=b[1]=b[2]=b[3]=10;//超出显示范围
}
输出函数:
void output(){
	operat();
	show(ans);
}

3、效果

在这里插入图片描述


总结

此次的小目标已经完成,对单片机的I/O也有了了解,并对前面的代码做出优化,包括显示负数。后面将学习单片机的其它内容,并且在闲暇之余也会学习python,在原有的基础上更进一步。作为一名单片机小白,这段时间的收获颇丰,以后也会继续在CSDN记录我的学习。

  • 13
    点赞
  • 129
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

武的阶乘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值