8.4 按键 8.4.1 独立按键 常用的按键电路有两种形式,独立式按键和矩阵式按键,独立式按键比较简单,它们各自与独立的输入线相连接,如图 8-6 所示。 图 8-6 独立式按键原理图 4 条输入线接到单片机的 IO 口上,当按键 K1 按下时,+5V 通过电阻 R1 然后再通过按键K1 最终进入GND 形成一条通路,那么这条线路的全部电压都加到了R1 这个电阻上,KeyIn1 这个引脚就是个低电平。当松开按键后,线路断开,就不会有电流通过,那么 KeyIn1 和+5V 就应该是等电位,是一个高电平。我们就可以通过 KeyIn1 这个 IO 口的高低电平来判断是否有按键按下。 准双向IO 口,如果要正常读取外部信号的状态,必须首先得保证自己内部输出的是 1,如果内部输出 0,则无论外部信号是 1 还是 0,这个引脚读进来的都是 0。 8.4.2 矩阵按键 在某一个系统设计中,如果需要使用很多的按键时,做成独立按键会大量占用IO 口,因此我们引入了矩阵按键的设计。如图 8-8 所示,是我们的 KST-51 开发板上的矩阵按键电路原理图,使用 8 个 IO 口来实现了 16 个按键。 图 8-8 矩阵按键原理图 如果独立按键理解了,矩阵按键也不难理解,那么我们一起来分析一下。图8-8 中,一共有 4 组按键,我们只看其中一组,如图 8-9 所示。大家认真看一下,如果 KeyOut1 输出一个低电平,KeyOut1 就相当于是 GND,是否相当于 4 个独立按键呢。当然这时候 KeyOut2、KeyOut3、KeyOut4 都必须输出高电平,它们都输出高电平才能保证与它们相连的三路按键 不会对这一路产生干扰,大家可以对照两张原理图分析一下。 图 8-9 矩阵按键变独立按键示意图 8.4.4 按键消抖 通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图 8-10 所示。 图 8-10 按键抖动状态图按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在 10ms 以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理先等待一个10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。将上一个的程 序稍加改动,得到新的带消抖功能的程序如下。 那么消抖操作所需要的延时该怎么处理呢?其实除了这种简单的延时,我们还有更优异的方法来处理按键抖动问题。举个例子:我们启用一个定时中断,每 2ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描8 次后,看看这连续8 次的按键状态是否是一致的。8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段,如图 8-12。 图 8-12 按键连续扫描判断 假如左边时间是起始 0 时刻,每经过 2ms 左移一次,每移动一次,判断当前连续的 8 次
按键状态是不是全 1 或者全 0,如果是全 1 则判定为弹起,如果是全 0 则判定为按下,如果0 和 1 交错,就认为是抖动,不做任何判定。想一下,这样是不是比简单的延时更加可靠?利用这种方法,就可以避免通过延时消抖占用单片机执行时间,而是转化成了一种按键状态判定而非按键过程判定,我们只对当前按键的连续 16ms 的 8 次状态进行判断,而不再关心它在这16ms 内都做了什么事情. 8.4.5 矩阵按键的扫描 我们讲独立按键扫描的时候,大家已经简单认识了矩阵按键是什么样子了。矩阵按键相当于 4 组每组各 4 个独立按键,一共是 16 个按键。那我们如何区分这些按键呢?想一下我们生活所在的地球,要想确定我们所在的位置,就要借助经纬线,而矩阵按键就是通过行线 和列线来确定哪个按键被按下的。那么在程序中我们又如何进行这项操作呢? 前边讲过,按键按下通常都会保持 100ms 以上,如果在按键扫描中断中,我们每次让矩阵按键的一个 KeyOut 输出低电平,其它三个输出高电平,判断当前所有 KeyIn 的状态,下次中断时再让下一个 KeyOut 输出低电平,其它三个输出高电平,再次判断所有 KeyIn,通过快速的中断不停的循环进行判断,就可以最终确定哪个按键按下了,这个原理是不是跟数码 管动态扫描有点类似?数码管我们在动态赋值,而按键这里我们在动态读取状态。至于扫描 间隔时间和消抖时间,因为现在有 4 个 KeyOut 输出,要中断 4 次才能完成一次全部按键的扫描,显然再采用 2ms 中断判断 8 次扫描值的方式时间就太长了2*4*8=64ms),那么我们就改用 1ms 中断判断 4 次采样值,这样消抖时间还是 16ms(1*4*4)。下 /**************按键扫描*********************** * 函数名称:Keyscan * 函数参数:void * 函数功能:完成矩阵按键或单独按键的扫描,矩阵按键使用二维数组;单独按键使用一维数组 * 作者:ZYD * 日期:2018 */ void Keyscan(void) //本函数要循环4次起作用 { uint8i; staticuint8 keyout = 0; staticuint8 keybuf[4][4] = { {0xff,0xff,0xff,0xff,}, //可用缓冲区 {0xff,0xff,0xff,0xff,}, {0xff,0xff,0xff,0xff,}, {0xff,0xff,0xff,0xff,}, }; /***********按键的out伏低电平,扫描低电平***********/ switch(keyout) { case0:P2=0x7f;break; //0111 1111 case1:P2=0xbf;break; //1011 1111 case2:P2=0xdf;break; //1101 1111 case3:P2=0xef;break; //1110 1111 default:break; } //P21= 1; //单独赋值,保证数码管最后一位正常 /************按键按列扫描移位,一个语句完成一列*******/ keybuf[keyout][0]<<=1;keybuf[keyout][0]|=P20; //in1 // keybuf[keyout][1]<<=1;keybuf[keyout][1]|=P21; //in2 // keybuf[keyout][2]<<=1;keybuf[keyout][2]|=P22; //in3 // keybuf[keyout][3]<<=1;keybuf[keyout][3]|=P23; //in4 P21 = 0; //单独赋值,保证数码管最后一位正常 for(i=0;i<4;i++) //4次中断的取样 { if((keybuf[keyout]&0x0f)== 0x0f) //只要扫描不连续的四位就可以,形成16ms消抖 { //4*4MS内都是0000,就成立了 keysta[keyout]= 1; } elseif((keybuf[keyout]&0x0f) == 0x00) keysta[keyout]= 0; else {} } keyout++; keyout&=0x03;//与字来清零 }
|