【51单片机】矩阵键盘线反转法实验仿真

51单片机 矩阵键盘线反转法仿真

  • 前言
  • 一、实验环境
  • 二、拙
    • 1、硬件
    • 2、软件程序
      • 1)线反转法和逐行扫描法
      • 2)线反转法程序
  • 三、悟
    • 1、换一个键盘
    • 2、加入如键盘功能
    • 3、效果
  • 总结


前言

在上篇文章【51单片机】〈C语言+Keil5+Proteus仿真〉矩阵键盘逐行扫描法-20210414中,提到了矩阵键盘的线反转法,但是在仿真上出现了一些问题,导致没能做出来。当时都已经开始怀疑自己,课本上的虽然是汇编写的代码段,但是我用C来实现居然会出错,不禁让我陷入沉思……后来经过不断地控制变量反复实验,终于我发现,这是仿真软件的问题,与我无瓜
主要体现在逐行扫描法可以完美运行,一换到线反转法就出错。所以这篇文章主要用于记录矩阵键盘线反转法的仿真实现。


一、实验环境

由于目前学校的实验课程尚未开始,即使实验课程开始我也不会用实验室的器材来记录,所以CSDN上的学习记录必将长期或绝大部分用软件仿真来实现。其实不论是仿真还是实际操作,其原理和目的都是一样的。

  • Proteus 8 Professional
    这是一个常用的仿真软件,具体操作在这篇讲矩阵键盘的逐行扫描法中写到过,可以作为参考。其实,Proteus也可以写程序,但是同样需要先下载安装有Keil才能使用C语言写,否则只能是汇编语言。汇编语言程序我也能写,但是现在还是更倾向于用C。
  • Keil5
    这是一个比较常用的单片机程序的编译软件,支持C、汇编以及其他语言的文件,软件界面类似VC++6.0。使用中的注意事项有:
    • 不能很好地支持中文!!!连中文的注释都有可能乱码,文件名也最好发放弃中文命名的习惯,软件有可能找不到中文命名的源文件地址!导致编译失败等等问题。(估计是只有我才有这样的习惯吧……)
    • 一定要记得添加源文件到项目中,这是基本操作了。但是我还是会偶尔忘记。
    • 一定要记得在“option of target”的“output”中勾选创建.hex文件,并且要记得创建的位置。

好了,具体操作不在赘述,上面的提到的文章有。下面开始正题。

二、拙

1、硬件

一开始,我以为可以按照逐行扫描法时一样的电路进行操作。所以选择的硬件都没变:Keypad-Smallcalc(键盘),80C51(芯片),Respack(排阻),Led-Bargraph-GRN(Led)以及电源端和接地端。所用到的原件如图:
【51单片机】矩阵键盘线反转法实验仿真_第1张图片
然后一顿操作猛如虎,我按照上次逐行扫描法的方式连线,即:【51单片机】矩阵键盘线反转法实验仿真_第2张图片
每次看着整整齐齐的线路图,倒还挺舒服的。是不是连线也相当简单?因为挂载的设备少,而且功能也比较简单,所以我们就不用扩展接口芯片了,直接用8051的P1口连接就按盘,P0口连接Led即可。
注意:P0口要有上拉电阻才能输出高电平。

2、软件程序

在电路的基础上,我还是想实现一个计算器的基本功能。上次用逐行扫描法做的计算器只能计算两个数的计算,现在至少得进步一下才能看到新东西。所以这次琢磨中,实现了连续运算的功能。

1)线反转法和逐行扫描法

首先我们先来捋一捋键盘检测的流程:

  • 行输低电平,列方向读入列值。当没有键按下时,列值应该为高电平,即FH。若有键按下,则列值不全为高电平。如行方向输入低电平0000B,若读入列值为1111B则表示没有键被按下,若为0111B则表示第一列右键按下。
  • 去抖动。去抖动是因为按键在电平变化是会出现尖峰抖动,影响程序判定按下的次数。所以需要对这段尖峰进行处理。常见的方法有硬件除抖动和软件除抖动。软件除抖动最简单的办法就是==“不能解决它就不要面对它”==。所以我们在程序中加入延时程序,忽略这段抖动就可以了。
  • 再读一次列值,若为列值不全为高电平则表示的确有键按下。然后就是键盘分析程序。一种是逐行扫描法,另一种是线反转法。
  • 逐行扫描法:既然我知道了有键按下,那我就逐行送入低电平,读列值。若这一列输入低电平,而列值全为高电平,则被按下的键不在这一行。换下一行。若列值有低电平,则保存此时的行值和列值。进行其他计算得到键码。
  • 线反转法:测试时读入的列值不全为高电平,则保存列值。然后行列的电平反转,即列输出低电平,读行值。这时得到一个行值和一个列值,两个数即代表了一个键的键码。

上次的键盘程序我用了逐行扫描法,这次我们就用线反转法。下面我们来测试一下程序。

2)线反转法程序

void keyscan(){
	int temp;
	while(1){
	P1=0xf0;//P1口第四位输出低电平,即行值设为全0
	if((P1&0xf0)!=0xf0){//列值不全为高电平
		delayms(5);//去抖动
		temp=P1&0xf0;//读入列值
		P1=0x0f;//列值设为低电平
		temp |= (P1&0x0f);//把行值(第四位)和列值(高四位)位或得到唯一指向按键的键码
		P0=temp;//在Led上输出键码
		while(P1!=0x0f);
		}
	}
}

按键分析程序已经完成,写一个主程序和延时程序试试线反转法效果。

#include
void delayms(int n){
   int i;
   int j;
   for(i=0;i<n;i++)
      for(j=0;j<120;j++);
   }
 
 void main(void){ 
   // Write your code here
	P0=0x00;
	while(1){
		keyscan();
		delayms(50);
	}
}

然后我就此进行了测试。发现这效果不对呀。
【51单片机】矩阵键盘线反转法实验仿真_第3张图片
我发现按下不同的键,Led显示的键码低4为永远是全1。于是我又把行列的顺序换了再进行测试。即列先给低电平,读行值,有键按下再行给低电平读列值。

void keyscan(){
	int temp;
	while(1){
	P1=0x0f;//P1口高四位输出低电平,即列值设为全0
	if((P1&0x0f)!=0x0f){//读入行值不全为高电平
		delayms(5);//去抖动
		temp=P1&0x0f;//读入行值
		P1=0xf0;//行值设为低电平
		temp |= (P1&0xf0);//把行值(第四位)和列值(高四位)位或得到唯一指向按键的键码
		P0=temp;//在Led上输出键码
		while(P1!=0xf0);
		}
	}
}

然后这次是不一样的情况,它转移了。
【51单片机】矩阵键盘线反转法实验仿真_第4张图片
变成高4位全为高电平,低4位显示正确的情形。由此判断它只是有第一次读P1口的值有效,而第二次出错。于是这个问题纠结了我好几天。最终我发现了这个keypad-smallcalc 键盘有问题

三、悟

后来通过不断尝试,想找出代码的问题,但是按照课本的汇编来说,这个逻辑并没有问题。于是我用button做了一个矩键盘,就没有问题了。

1、换一个键盘

button做的键盘有一种原始的感觉,毕竟没有封装到一起。所以它是这样的。【51单片机】矩阵键盘线反转法实验仿真_第5张图片
相同的代码,再看看效果:
【51单片机】矩阵键盘线反转法实验仿真_第6张图片
由此可见,Led高4位显示的是列值,低4位显示行值,也就是说线反转法的额程序没有问题,是可以实现的。
以上,就是对线反转法的实验探讨。下面把完整的功能实现。

2、加入如键盘功能

首先,我们想到:有了键码,就应该分配每个键的意义。用代码表示则只需要一个多分支语句。

void act(int key){
	switch(key){
		case 0x77:clear();break;//清零,并用流水灯来提示。
		case 0xB7:savedata(0);break;//数字键的功能是把数字保存起来
		case 0xD7:output();break;//等于号的功能是显示结果ans
		case 0xE7:saveop('+');break;//按下运算符键,可以对前面输入的两个数进行计算,并把新的运算符保存起来。进而可以进行连续运算。
		case 0x7B:savedata(1);break;
		case 0xBB:savedata(2);break;
		case 0xDB:savedata(3);break;
		case 0xEB:saveop('-');break;
		case 0x7D:savedata(4);break;
		case 0xBD:savedata(5);break;
		case 0xDD:savedata(6);break;
		case 0xED:saveop('*');break;
		case 0x7E:savedata(7);break;
		case 0xBE:savedata(8);break;
		case 0xDE:savedata(9);break;
		case 0xEE:saveop('/');break;
	}
}

下面是对每一个功能函数的定义:

#include

int ans=0;//存放计算结果
int num=0;//存放新的操作数
char op='\0';//存放运算符

void operat(){
	switch(op){
		case '+':ans=ans+num;break;
		case '-':ans=ans-num;break;
		case '*':ans=ans*num;break;
		case '/':ans=(num==0)?0xff:(ans/num);break;//注意,这里需要有一个除0的处理。否则可能除0会出现除零错误。前面逐行扫描法没有注意到。
		default:ans=0xff;
	}
}
	 
void saveop(char p){
	if(op!='\0'){//如果不是第一个运算符,即前面已经有了两个数
		operat();//则先对已有的ans和num计算
	}
	op=p;
	P0=ans;//展示结果
}
	 
void savedata(int n){
	if(op=='\0'){	//如果还没有输入过运算符,则这是第一个数,存到ans内
		ans=ans*10+n;//把输入的数转换成一个数
		P0=ans;
	}
	else{
		num=num*10+n;//不是第一个数则存到num中
		P0=num;
	}
}



int keyscan(){
	int temp;
	P1=0xf0;
	if((P1&0xf0)!=0xf0){
		delayms(5);
		if((P1&0xf0)!=0xf0){
			temp=P1&0xf0;
			P1=0x0f;
			temp |= (P1&0x0f);
			delayms(20);
			}
		return temp;
	}
	else return 0xff;
}
void turnLight(){//跑马灯
	int light=0x03;
	int i;
	int n;
	P2=0x0f;
	for(n=0;n<3;n++){
		for(i=0;i<8;i++){
			P0=light;
			light=(light>>(8-1))|(light<<1);//我发现<<和>>移位操作不是循环的,会丢失被移出去的数。
			delayms(10);
		}
	}
	P0=0;
}
void clear(){//清零函数
	ans=0;
	num=0;
	op='\0';
	turnLight();
}
void show(int m){//显示函数
	P0=m;
	num=0;
}
void output(){//输出的函数。即显示和等于的功能不同。按下等于之后实际上是要把num清零的,防止num的值影响后面的输入。而show函数不用,只是显示当前的值。
	operat();
	op='\0';
	num=0;
	show(ans);
}

最后是程序的入口

void main(void){ 
	while(1){
		act(keyscan());
		delayms(30);
	}
}

但这就完成了。这次的功能实现是连续运算。比如12+13==>25-10==>15*2==> =30这样的操作。

3、效果

【51单片机】矩阵键盘线反转法实验仿真_第7张图片


总结

这次的核心是把线反转法实现,附加的实现了连续运算。也算是上次的后续吧。但是我还想再把七段数码管加进来显示,让I/O更人性化。毕竟看二进制还要转换,不如用十进制,我们熟悉的方式显示。后面几天我也会再学习学习七段数码管的使用,争取早点把这个计算器完善。
把自己学习的历程发出来也是一种很好的记录方式,也希望能跟小伙伴一起学习。

你可能感兴趣的:(单片机,C/C++,单片机,c语言,嵌入式,程序人生,程序设计)