初学51单片机矩阵按键与消抖

单片机的上电复位功能,

这是单片机的上电复位功能电路,RST与51单片机的第9脚(RST端)相连,给单片机上电的瞬间单片机就完成了复位功能。一般51单片机完成复位功能只需2个机器周期(2us)即RST脚高电平保持2us,本案的单片机用的是无源晶振11.0592M。

每个单片机的复位电压不完成一样,按照通常值的0.7VCC作为复位电压值。本案就是0.7*5=3.5V

即高于3.5V被认为是高电平。

对于本案的复位电路来说,当一上电,电容类似一条导线,5V电压全部在R31的电阻上,随着电容充电,电容两端慢慢(这个所谓的慢慢其实极快是微秒级的)变大,这个过程的状态是

5V=Uc+I*R31,

对于电压大于3.5V复位,复位时间的计算教材给出的解释是难以计算,有个经验式t1=1.2RC

可以得出t=1.2*4700*0.0000001=0.000564=564us,远远大于2us。

按键相关

独立按键原理图 图示1

图示2 双向IO口内部图,现在大部分单片机这个三极管换成功能类似的mos管

图示3

图示1开关弹起的时候keyin1是高电平,当按下开关keyin1就接地了。

图示2可知对于单片机IO口

         当内部输出位为低电平的时候,经过非门,三极管基极为高电平,该三极管导通(饱和导通CE两端电压几乎相同),单片机IO口输出低电平,无论外部的按键开关按下还是弹开,IO口一直保持低电平。即不受外部信号的控制。

      当内部输出为高电平的时候,经过非门,三极管基极为低电平,三极管未导通(CE两端的阻值很大,电阻R与之相比可以忽略不计)这时IO口输出高电平。当按下按键,IO口就被接地,这时IO口输出低电平。

由此可知只有当IO口输出为高电平的时候,IO口的输出才受外部电路的控制

矩阵按键

在某个系统中,如果需要使用很多的按键,做成独立的按键会大量的占用IO口,因此引入了矩阵按键的涉及。如图示4

配合图示3可知

Keyout1 - Keyout4是单片机的P2.3 - P2.0 4个端口。

Keyin1 - Keyin4是单片夹的P2.4 - P2.7 4个端口。

从图中不难看出当Keyout1输出1个低电平就相当于GND,这时K1、K2、K3、K4就能控制Keyout1、Keyout2、Keyout3、Keyout4的电压了。我们控制1个LED点亮来演示看程序

#include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

sbit LED9 = P0^7;
sbit LED8 = P0^6;
sbit LED7 = P0^5;
sbit LED6 = P0^4;

sbit key1 = P2^4;
sbit key2 = P2^5;
sbit key3 = P2^6;
sbit key4 = P2^7;


void main()
{
	
  ENLED = 0;
	ADDR3 = 1; //ADDR这里取值是38译码器使LED输出使能
	ADDR2 = 1;
	ADDR1 = 1;
	ADDR0 = 0;
	P2 = 0xF3;
	while(1)
	{
    LED9 = key1; //若keyout1和keyout2都置0则K5功能和K1一致
    LED8 = key2;
    LED7 = key3;
    LED6 = key4;	
    
	}


}

P2 = 0xF3=1111 0011 可以看到P2端口高4位都置1,即keyin1 - keyin4端口都被置1。根据我们前文IO口的说明,若想外部电路控制IO,它的输出需要是高电平。然后是P2.3和P2.2端口置0根据图4的原理图可知是 keyout1、keyout2都置0了。

按照程序LED9 = key1;
              LED8 = key2;
              LED7 = key3;
              LED6 = key4;    

在循环执行的函数中可知当Key1输出低电平的时候LED9=P0.7=0这时开发版上的LED9就会被点亮。

由程序可知key1=p2.4 ,P2.4端口的电压是来自keyin1的电压。 而keyin1的电压由开发版上的开关K1,K5控制。也就是按下K1.和K5都能使LED9点亮。

看结果。按键控制LED点亮_哔哩哔哩_bilibili

按键控制数码管0-9刷新

现在做个程序使按键可以控制数码管刷新。

按一次按键开关,就会产生“按下”和“弹起”两个动态的动作,选择在“弹起”时对数码管进去加1操作。

#include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

//sbit LED9 = P0^7;
//sbit LED8 = P0^6;
//sbit LED7 = P0^5;
//sbit LED6 = P0^4;

sbit key1 = P2^4;
sbit key2 = P2^5;
sbit key3 = P2^6;
sbit key4 = P2^7;
unsigned char code LedChar[] ={
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,     //数码管真值
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

void main()
{
	bit backup = 1;       //定义一个变量,保存前一次扫描的按键值
	unsigned char cnt = 0;//定义一个变量,记录按键按下的次数
  ENLED = 0;
	ADDR3 = 1;            //选择数码管DS1进行显示
	ADDR2 = 0;
	ADDR1 = 0;
	ADDR0 = 0;
	P2 = 0xF7;           //1111 0111
	P0 = LedChar[cnt];   //显示按键初值
	while(1)
	{
    if(key4 != backup)  //当前值与前次值不相对说明按键有动作
		{
		  if(backup == 0) // 如果当前值为0,则说明当前是由0变1,即按键弹起
			{
			  cnt++;       //按键次数+1
				if(cnt >= 10) //只用1个数码管显示,加到10就清零重新开始
				{
				  cnt = 0;
				}
				P0 = LedChar[cnt]; //计数值显示到数码管上
			 }
		 backup = key4;//跟新备份当前值,以备进行下次比较
			
		 }

		
    
	}


}

看下结果无延时_哔哩哔哩_bilibili

可以看到每次按下开关数码管有时进1为有时进2位。这是怎么产生的呢?

通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上就稳定接通,在断开时也不会一下子彻底断开,而且在闭合和断开的瞬间伴随一连串的抖动。如图

按键稳定闭合时间长短由操作人员决定,通常都会在100ms以上,刻意快速按的话能达到40-50ms,很难在低了。抖动时间是由按键的机械特性决定的,一般都会在10ms以内。为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是去响应动作,而是先等待闭合或者断开稳定后再进行处理。

按键消抖

可以分为硬件消抖和软件消抖。硬件消抖是在按键上并联一个电容,利用电容的充放电特性来对抖动中产生的电压毛刺进行平滑处理。但在实际应用中,这种方式的效果往往不是很好,还增加了成本,因此实际中使用的并不多。

觉大多数是用软件即程序来消抖。最简单的消抖原理,就是当检测到按键状态变化后,先等待1个10ms左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确定按键已经稳定动作了。看程序

#include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

//sbit LED9 = P0^7;
//sbit LED8 = P0^6;
//sbit LED7 = P0^5;
//sbit LED6 = P0^4;

sbit key1 = P2^4;
sbit key2 = P2^5;
sbit key3 = P2^6;
sbit key4 = P2^7;
unsigned char code LedChar[] ={
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,     //数码管真值
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};
void delay();

void main()
{
	bit backup = 1;       //定义一个变量,保存前一次扫描的按键值,    
	bit keybuf = 1;       //按键值暂存,临时保存按键的扫描值
	unsigned char cnt = 0;//定义一个变量,记录按键按下的次数
  ENLED = 0;            //选择数码管DS1来进行显示
	ADDR3 = 1;
	ADDR2 = 0;
	ADDR1 = 0;
	ADDR0 = 0;
	P2 = 0xF7;          //P2.3置0,即keyout1输出低电平,这个赋值让P2^7为高电平即key4 = 1;
	P0 = LedChar[cnt];  //显示按键初值
	while(1)
	{
     keybuf = key4;  //实时监测Key4的状态把key4的状态值给keybuf储存起来
		 if (keybuf != backup) //检测到当前值与前次值不相等说明此时按键有动作
		 {
		      delay(); //delay 10ms
			  if(keybuf ==key4) //判断扫描值有没有发生改变,即按键抖动
				{
				  if(backup == 0) //如果前次的值为0,则说明当前是弹起动作
					{
					  cnt++;       //按键次数加1
						if(cnt >= 10)
						{
						 cnt = 0;    //只用1个数码管显示,所以到10就被清零重新开始
						}
						P0 = LedChar[cnt];  //计数值显示到数码管上
						
					}
					backup = keybuf;  //更新备份为当前值,以备进行下次比较
				}
			 
		 }
		     
		  
			 
			
	}


}

void delay()
{
  unsigned int i =1000;
	while(i--);
}

弹起加1_哔哩哔哩_bilibili 这个程序来自教材

可以看到按动按键的时候不会发生之前按一次跳两次的状况。并且是在按键弹起的时候加1;那如果要求按住按键的时候加1要如何操作呢。 把判断语句if(backup = = 0)改成if(backup = = 1)就可以了。视频就不放出了。

这个程序的关键是这个语句backup = keybuf;为什么放在这?和为什么用if(backup = = 0)这个语句来判断?

可以看到程序

看到程序这句是放在if(keybuf == key4;)下面的,它的逻辑是经过延时后如果扫描值确实没有变化,则把这个变化的新值赋值给backup。无论是否进入if判断语句它都会发生。backup获得一个新稳态的值。根据程序如果这个新稳态的值是0,就会进入if判断语句让cnt++。

逻辑反推:若想进入if(backup == 0),必然backup依然被赋值为0,而程序是只有稳态发生变化的时候才能进入,则这个过程必然是按键按住(0)到按键弹起的过程(1)。

倘若backup=1呢,从程序可以从按键弹起状态(1)到按下按键(0)这个状态才能进入第1个if语句,但是显然无法进入第二if语句if(backup == 0),直接执行backup = keybuf;这句。这个时候backup的状态值就是0。然后又执行前文所说的动作。

第二个为什么用一定if(backup == 0)来判断,直接用if(key4==0)来判断在逻辑上不是更加直观。事实上这种也是能够正常工作的。笔者自己写的程序就是这种。

#include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

//sbit LED9 = P0^7;
//sbit LED8 = P0^6;
//sbit LED7 = P0^5;
//sbit LED6 = P0^4;

sbit key1 = P2^4;
sbit key2 = P2^5;
sbit key3 = P2^6;
sbit key4 = P2^7;
unsigned char code LedChar[] ={
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,     //数码管真值
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};
 void delay();

void main()
{
	bit past = 1;       //实时态的前一稳态,有两种稳态 按住是0 保持弹起是1
	bit now = 1;       // 按键开关实时状态
	unsigned char cnt = 0;//定义一个变量,记录按键按下的次数
	//unsigned int j = 50000;
  ENLED = 0;
	ADDR3 = 1;
	ADDR2 = 0;
	ADDR1 = 0;
	ADDR0 = 0;
	P2 = 0xF7;      //key4即p2.7端口电压是高电平 即key4= 1;
	P0 = LedChar[cnt];
	while(1)
	{
      now = key4;  //把按键开关的状态赋值给now
		  if(past != now) //确认前一个稳态和现在这个实时态不同进入程序,说明状态变化
			{
				  delay();  //延时10ms
				if(now == key4) // 延时后按键开关保持的状态和之前的实时态相同,说明进入新稳态
				{
				 if(key4 == 0) //开关的实时态处于按住状态
				 {
			
				   cnt++;   //数码管输出+1
					if(cnt >= 10)
					 {
					   cnt = 0;
					 }
					P0 = LedChar[cnt]; //数码管输出
				}
		      past = now;  //把这个实时态也是新稳态赋值给past
				 // while(j--);
			  }
			}
			 
				 
		
    
	}


}
 void delay()
{
 unsigned int i = 1000;
	while(i--);


}

看下结果视频。该程序笔者的命名是延时10ms延时10ms_哔哩哔哩_bilibili可以看到结果正常,与上一个程序相比这个是按住的时候数码管加1。

使语句if(key4 ==1)这得到和之前相同的结果,弹起的时候数码管加1。看结果视频

延时10mskey4=1_哔哩哔哩_bilibili

注意:当i=1000的时候while(i--)延时是10ms笔者做过debug,笔者的晶振是11.0592M的因此晶振不同这个时间也是不一样的

100次是1ms,1000次就是10ms

然后笔者在调试的时候拍了一些视频也发给各位瞧瞧

1:延时10ms去掉past = now这句会发生什么?延时10mskey4=1无past=now_哔哩哔哩_bilibili

2:延时50ms去掉past = now这句会发生什么?延时50ms无past=now_哔哩哔哩_bilibili

3:延时100ms去掉past = now这句会发生什么?延时100ms无past=now_哔哩哔哩_bilibili

4:延时100ms(没有去掉past = mow)会发生什么?延时100ms_哔哩哔哩_bilibili

5:不要past = now用while(50000 --)代替会发生什么?延时500ms无past=now_哔哩哔哩_bilibili

6:留个问题,保留past = now然后后面加上while(50000--)会发生什么呢?

本案提供一种消抖方式,下篇博文将提供第二种消抖方式也是相对更好的方式。

    

  • 32
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值