说明:此文章仅是我学习过程中的一些记录,如有侵权,请联系我删除,文章中难免有遗漏错误之处,欢迎指出。
目录
一、键盘的分类
二、独立键盘的识别
三、一个读取独立按键状态的程序
四、矩阵键盘的识别
五、按下矩阵键盘使数码管上显示0~F间的一个数的程序
一、键盘的分类
键盘分编码键盘和非编码键盘。键盘上闭合键的识别由专用的硬件编码器实现,并产生键编码号或键值的称为编码键盘,如计算机键盘。
而靠软件编程来识别的称为非编码键盘;
在单片机组成的各种系统中,用的最多的是非编码键盘。也有用到编码键盘的。
非编码键盘有分为:独立键盘和行列式(又称为矩阵式)键盘。
二、独立键盘的识别
独立键盘的接法通常如上图所示,通过将键盘连接在单片机的I/O口,并利用I/O口来读取按键是否被按下。I/O口连接到按钮的一端,按钮另一端接地。
那么,具体是如何利用I/O口来检测的呢,我们首先要了解下面的内容。
我们暂且将单片机输出与输入定义为TTL电平,具体内容详情郭老师的教材。
在TTL电路中,如果I/O口没有三态状态,它与跟它相连的线是线与关系。
而在89C51型号的单片机中P1,P2,P3口都是准双向8位I/O口,每个口可独立控制,内带上拉电阻,这种接口输出没有高阻状态,输入也不能锁存,故不是真正的双向I/O口。之所以称它为“准双向”,是因为该口在作为输入使用前,要先向该口进行写1操作。
说了这么多,我们最终要知道的就是,准双向I/O口即没有三态状态,所以它与跟它相连的线是线与的关系。上图中的P3口是准双向I/O口,它通过按钮与GND相连,所以当按钮按下,相连的P3口变成低电平状态,并且在按钮按下之前,我们必须先向其写1,才可以通过当前是高电平还是低电平判断是否按下。
注:如果I/O口有三态功能,即有高阻态功能,例如89C51型号单片机的P0口,它与跟它相连的线则是线或关系。
三、一个读取独立按键状态的程序
现在让我们来具体写一个程序,判断开发板上的独立按键之S2按键是否被按下,按下时,第一个发光二极管点亮,松手时熄灭。
首先是找到S2按键对应单片机的哪个I/O口。与键盘相关的原理图如下。
可以看到S2按键对应的是单片机的P3.4口,因此要对其进行操作。
对单片机I/O口的操作主要有两步,一是位定义,方便后面程序的书写,二是进行“先写1”操作,在本程序中,我们选用的是直接对整个P3口进行写1操作,当然也可以只对我们想操作的I/O口进行单独操作。
然后就是判断按键是否按下了:
if(key1 == 0)
当我们按下按键时,单片机I/O口与地是线与的关系,所以该I/O口是低电平状态。
以及松手检测
while(!key1);
当I/O口是低电平状态,即按键在按下状态时,一直在while循环中等待,直到松手了再执行下面的语句。
那么,为什么要添加消抖和松手检测呢?原因如下:
按键在闭合和断开时,触点会存在抖动现象。
示意图如上。当我们手动按下与松开一次按键时,由于抖动的关系,如果只是用简单的一个if语句进行判断,计算机会判断我们按下了多次按键,也许在一个简单的按下按键点亮发光二极管的程序中,并不明显看出,但是如果我们的程序时,每按下一次按键,数码管显示内容自动加1,就可以很明显的看出,有时候我们按下一次按键,数码管却加了好几个数。所以,我们应该消抖,防止噪声。
消抖有两种方法,一种是软件法,利用delay函数,进行两次if语句的判断,delay函数消除掉按下抖动的时间,而释放抖动一般不用考虑,当然也可以写。
if(key1 == 0)
{
delay(5);
if(key1 == 0)
......
释放抖动的消除其实就是在松手检测语句下面写一个delay函数,然后再写一句松手检测语句。
while(!key1);
delay(5);
while(!key1);
至于delay的时间,一般可取5~10ms,常取10ms,当然也可以取5ms,尽量不让CPU过多地等待。
还有一种是硬件法,接法如下,这里不介绍。
代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
sbit D1 = P1^0;
sbit key1 = P3^4; //P3.4口对应键盘S2
void delay(uint z) //延时函数,z的取值为这个函数的延时ms数,如delay(200);大约延时200ms.
{ //delay(500);大约延时500ms.uint x,y;for(x=z;x>0;x--)for(y=110;y>0;y--);
}
void main()
{P3 = 0xff;//先写1while(1){if(key1 == 0) {delay(10); //消抖if(key1 == 0){D1 = 0; //发光二极管亮} while(!key1); //松手检测delay(10);while(!key1); }else{D1 = 1;} }
}
四、矩阵键盘的识别
下面是矩阵键盘的典型接法。各连线的I/O口之间仍为线与的关系。分行读取,每次只能读取一行中某个按键的按下。
矩阵键盘的识别方法其实和独立键盘类似,都是检测低电平,但矩阵键盘没固定接地,接的是I/O口,地(即低电平)由写程序给它。
以扫描第一行为例,通过给P3.0口赋低电平,其他口给高电平为基础,通过对P3.4~P3.7口的读取,若P3.4口读取结果是低电平,则第一行第一个按键按下,依次类推,后面三个按键。
检测完第一行,检测第二行,同理,给P3.1口赋低电平,其他口给高电平,再读取P3.4~P3.7口的电平情况,第三、四行也如此,通过分别给P3.2,P3.3口赋值低电平,然后对P3.4~P3.7口进行读取,来判断是否有按键按下。
五、按下矩阵键盘使数码管上显示0~F间的一个数的程序
观察开发板上的矩阵键盘连接图,可以看到与典型接法一致。
具体代码如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = {
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71};
sbit wela = P2^7;
sbit dula = P2^6;
uchar num,temp;
void delay(uint z) //延时函数,z的取值为这个函数的延时ms数,如delay(200);大约延时200ms.
{ //delay(500);大约延时500ms.uint x,y;for(x=z;x>0;x--)for(y=110;y>0;y--);
}
void main()
{dula = 1;P0 = 0;dula = 0;wela = 1;P0 = 0xc0;wela = 0;while(1) //始终检测键盘{P3 = 0xfe; //检测第一行temp = P3; //读回来,如果有键按下肯定不一样temp = temp&0xf0;//比较while(temp != 0xf0)//第一行有按键按下{delay(5); //去抖temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3; //循环检测同一行switch(temp){case 0xee:num = 1;break;case 0xde:num = 2;break;case 0xbe:num = 3;break;case 0x7e:num = 4;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}dula = 1;P0 =table[num - 1];dula = 0;}}P3 = 0xfd; //检测第二行temp = P3;temp = temp&0xf0;while(temp != 0xf0){delay(5);temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3;switch(temp){case 0xed:num = 5;break;case 0xdd:num = 6;break;case 0xbd:num = 7;break;case 0x7d:num = 8;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}dula = 1;P0 =table[num - 1];dula = 0;}}P3 = 0xfb; //第三行temp = P3;temp = temp&0xf0;while(temp != 0xf0){delay(5);temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3;switch(temp){case 0xeb:num = 9;break;case 0xdb:num = 10;break;case 0xbb:num = 11;break;case 0x7b:num = 12;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}dula = 1;P0 =table[num - 1];dula = 0;}}P3 = 0xf7; //第四行temp = P3;temp = temp&0xf0;while(temp != 0xf0){delay(5);temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3;switch(temp){case 0xe7:num = 13;break;case 0xd7:num = 14;break;case 0xb7:num = 15;break;case 0x77:num = 16;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}dula = 1;P0 =table[num - 1];dula = 0;}}}
}
其中关键语句是:
P3 = 0xfe;
扫描第一行,给P3.0口赋值低电平,其他赋值高电平,然后是
temp = P3;
把P3口的状态赋值给temp,接着变是利用按位与操作,读取P3口的高四位是否有按下
temp = temp&0xf0;
将temp与0xf0即11110000进行按位与操作,如果没有按键按下,因为P3 = 0xfe的缘故,temp与0xf0按位与的结果仍然是0xf0,而如果按键按下,则temp的高四位将有低电平的出现,按位与的结果就不会是0xf0,利用按位与之后的temp与0xf0去比较,可以判断出按键是否有按下,最后再将P3的值赋值给temp,利用switch case语句判断按下的是哪个按键,进而进行处理,后面三行同样是这种操作。
上述代码有重复性的内容,在实际中,常利用带返回值的子函数来书写这个代码,如下:
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code table[] = {
0x3f , 0x06 , 0x5b , 0x4f , 0x66 , 0x6d ,
0x7d , 0x07 , 0x7f , 0x6f , 0x77 , 0x7c ,
0x39 , 0x5e , 0x79 , 0x71,0};
sbit wela = P2^7;
sbit dula = P2^6;
uchar keyscan();
void display(uchar num11);
uchar num,temp,num1;
void delay(uint z) //延时函数,z的取值为这个函数的延时ms数,如delay(200);大约延时200ms.
{ //delay(500);大约延时500ms.uint x,y;for(x=z;x>0;x--)for(y=110;y>0;y--);
}
void main()
{num = 17;dula = 1;P0 = 0;dula = 0;wela = 1;P0 = 0xc0;wela = 0;while(1) //始终检测键盘{display(keyscan());}
}uchar keyscan()
{P3 = 0xfe; //检测第一行temp = P3; //读回来,如果有键按下肯定不一样temp = temp&0xf0;//比较while(temp != 0xf0){delay(5); //去抖temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3; //循环检测同一行switch(temp){case 0xee:num = 1;break;case 0xde:num = 2;break;case 0xbe:num = 3;break;case 0x7e:num = 4;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}}}P3 = 0xfd; //第二行temp = P3;temp = temp&0xf0;while(temp != 0xf0){delay(5);temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3;switch(temp){case 0xed:num = 5;break;case 0xdd:num = 6;break;case 0xbd:num = 7;break;case 0x7d:num = 8;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}}}P3 = 0xfb; //第三行temp = P3;temp = temp&0xf0;while(temp != 0xf0){delay(5);temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3;switch(temp){case 0xeb:num = 9;break;case 0xdb:num = 10;break;case 0xbb:num = 11;break;case 0x7b:num = 12;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}}}P3 = 0xf7; //第四行temp = P3;temp = temp&0xf0;while(temp != 0xf0){delay(5);temp = P3;temp = temp&0xf0;while(temp != 0xf0){temp = P3;switch(temp){case 0xe7:num = 13;break;case 0xd7:num = 14;break;case 0xb7:num = 15;break;case 0x77:num = 16;break;}while(temp!=0xf0){temp = P3;temp = temp&0xf0;//松手检测,退出while}}}return num;
}void display(uchar num11)
{dula = 1;P0 = table[num11 - 1];dula = 0;
}