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

发布时间:2024-04-07  

前言

在上篇文章【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)以及电源端和接地端。所用到的原件如图:

所用到的元件

然后一顿操作猛如虎,我按照上次逐行扫描法的方式连线,即:连线图1

每次看着整整齐齐的线路图,倒还挺舒服的。是不是连线也相当简单?因为挂载的设备少,而且功能也比较简单,所以我们就不用扩展接口芯片了,直接用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      for(j=0;j<120;j++);

   }

 

 void main(void){ 

   // Write your code here

P0=0x00;

while(1){

keyscan();

delayms(50);

}

}

然后我就此进行了测试。发现这效果不对呀。

在这里插入图片描述

我发现按下不同的键,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);

}

}

}

然后这次是不一样的情况,它转移了。

在这里插入图片描述

变成高4位全为高电平,低4位显示正确的情形。由此判断它只是有第一次读P1口的值有效,而第二次出错。于是这个问题纠结了我好几天。最终我发现了这个keypad-smallcalc 键盘有问题。


三、悟

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


1、换一个键盘

用button做的键盘有一种原始的感觉,毕竟没有封装到一起。所以它是这样的。在这里插入图片描述

用相同的代码,再看看效果:

在这里插入图片描述

由此可见,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='�';//存放运算符

 

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!='�'){//如果不是第一个运算符,即前面已经有了两个数

operat();//则先对已有的ans和num计算

}

op=p;

P0=ans;//展示结果

}

 

void savedata(int n){

if(op=='�'){ //如果还没有输入过运算符,则这是第一个数,存到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='�';

turnLight();

}

void show(int m){//显示函数

P0=m;

num=0;

}

void output(){//输出的函数。即显示和等于的功能不同。按下等于之后实际上是要把num清零的,防止num的值影响后面的输入。而show函数不用,只是显示当前的值。

operat();

op='�';

num=0;

show(ans);

}

最后是程序的入口


void main(void){ 

while(1){

act(keyscan());

delayms(30);

}

}

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


3、效果

在这里插入图片描述


总结

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

把自己学习的历程发出来也是一种很好的记录方式,也希望能跟小伙伴一起学习。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>