第五讲:独立键盘、矩阵键盘的检测原理及实现(郭天祥)

说明:此文章仅是我学习过程中的一些记录,如有侵权,请联系我删除,文章中难免有遗漏错误之处,欢迎指出。

目录

一、键盘的分类

二、独立键盘的识别

三、一个读取独立按键状态的程序

四、矩阵键盘的识别

五、按下矩阵键盘使数码管上显示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;//先写1
	while(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;
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值