- 壹零零单片机©版权所有 2008-2023 粤ICP备17151077号
不管设计一个什么作品,按键总是少不了的,对于按键你知道那些呢?
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
图1
抖动时间
抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。
按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒,大家可以用示波器测试一下。键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
方法
按键的消抖,可用硬件或软件两种方法。
硬件消抖
在键数较少时可用硬件方法消除键抖动。下图所示的RS触发器为常用的硬件去抖。
图2
图中两个“与非”门构成一个RS触发器。当按键未按下时,输出为1;当键按下时,输出为0。此时即使用按键的机械性能,使按键因弹性抖动而产生瞬时断开(抖动跳开B),中要按键不返回原始状态A,双稳态电路的状态不改变,输出保持为0,不会产生抖动的波形。也就是说,即使B点的电压波形是抖动的,但经双稳态电路之后,其输出为正规的矩形波。这一点通过分析RS触发器的工作过程很容易得到验证。
另一种硬件消抖的方法利用电容的放电延时,采用并联电容法,也可以实现硬件消抖,如图3所示:
图3
软件消抖
如果按键较多,常用软件方法去抖,即检测出键闭合后执行一个延时程序,5ms~10ms的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。当检测到按键释放后,也要给5ms~10ms的延时,待后沿抖动消失后才能转入该键的处理程序。还可以利用定时器中断来消抖。
下面再介绍一种按键消抖的方法:利用switch()结构,程序设计如下:
无延时的软件消抖
/********************************************* 名称:键盘扫描子函数 功能:在按键稳定期内判断键值,并返回键值 **********************************************/ uchar keyscan(void) { static char key_state = 0; static char key_value = 0; uchar key_press, key_return = 0; key_press=turn_left&turn_right; //读按键I/O电平 switch (key_state) { case 0 : // 按键初始态 if (key_press==0) key_state = 1; // 键被按下,但需要确认是否是干扰 break; case 1 : // 按键确认态 if (key_press==0) //如有键按下则不是干扰,判断键值 { if(turn_left==0) //判断是哪一个按键被按下 key_value=1; //按键较多时可采用switch选择结构 else if(turn_right==0) key_value=2; else key_value=0; key_state = 2; // 状态转换到键释放态 } else key_state = 0; // 按键已抬起,属于干扰,转换到按键初始态 break; case 2 : if (key_press==1) { key_return=key_value;//按键释放后再输出键值 key_value=0; key_state = 0; //如果按键释放,转换到按键初始态 } break; }return key_return; //返回键值 } /********************************************* 名称:按键处理子函数 功能: **********************************************/ void key_operation(void) { switch (keyscan()) //根据键值不同,执行不同的内容 { case 1: hight_votage-=1; if(hight_votage<5) hight_votage=5; break; case 2: hight_votage+=1; if(hight_votage>25) hight_votage=25; break; default :break; } }
只要有按键就一定要想到消抖,总之不管是硬件消抖还是软件消抖,在脑海里始终要想到按键按下时出现图一的情景,然后再进行相应的设计。
几个好的按键设计的总结
对于多个按键的设计常用思路是: 按照面向过程的编程方式, 将数据与过程分离. 把和按键状态相关的东西比如按键功能统统塞到结构里, 把消抖的代码放在一个函数中。这里介绍按键设计的几种方法:
1、 矩阵键盘
矩阵键盘程序设计
4×6的矩阵键盘程序设计采用的是扫描法,程序比较精简,定义行值为i(0~3),列值为j(0~5),当有键按下时返回按键位置(j-4)+6×i,当没有键按下时返回32,程序如下。
/*******************************************
函数名称:scan_key
功 能:【4X6】矩阵按键扫描模块
参 数:无
返回值 :t或者32
********************************************/
unsigned char scan_key(void)
{
unsigned char code biao[10]={0xfe,0xfd,0xfb,0xf7,0xe0,0xd0,0xb0,0x70,0x01,
0x02,};
static unsigned char i=0;//寄存器变量,此变量也可以定义为全局变量,效果一样
P1=biao[i];//把值赋到I/O口,键盘接口。
temp=P1&0xf0;
temp2=P3&0x03;
if(((temp!=0xf0)&&(temp2==0x03))||((temp==0xf0)&&(temp2!=0x03)))//判断是
否有键按下,以及按下键的位置
{
for(j=4;j<8;j++)//列扫描
if((temp==biao[j])) return (t=(j-4)+6*i);//有键按下,返回键的位置
for(j=8;j<10;j++)
if((temp2==biao[j])) return (t=(j-4)+6*i); //有键按下,返回键的位置
}
i++;//行扫描
i&=0x03;
return(32);
}
2、 ADC按键
ADC按键的优点是节省IO口,但是需要调配好电阻值,个人觉得有点麻烦。
3、 并入串出按键
一种并入串出带中断易扩展的按键输入方案74165驱动电路如图,输出直接在IO口用电平显示
4、 4*3键盘并且复用端口很牛
/******************************************************** * * 4*3矩阵按键读写函数 * ********************************************************/ //按键读写程序 //行1 P65//行2 P62//行3 P51//行4 P67 P5:1 P6:257 //列1 P63//列2 P64//列3 P66 //按键解码 //行4:0,1,2;行1:3,4,5;行3:6,7,8;行2:9,10,11; //*0# 123 789 456 const uchar key_rel[]={0x0a,0x00,0x0b,0x01,0x02, 0x03,0x07,0x08,0x09,0x04,0x05,0x06}; void scan_key(void) {uchar i; keybuf=0xff; get_bit=0; /*方法一:行输出,列输入,影响AT24C02,led闪烁 //睡眠唤醒,欠压检测,声光指示的端口方向不需改动 //行输出0 //P5CR&=0x0d;//0000 1101初始化P5全为输出口 P5CR=0xf0; P6CR&=0x5b;//0101 1011 //列输入1 P6CR|=0x58;//0101 1000/ //方法二:列输出,行输入,列1串10K,输出不了低电平可能识别不了 //不会影响AT24C02,led指示,其它操作需要从新定义端口方向 //列输出0 P6CR&=0xa7;//1010 0111 //行输入1 P5CR|=0x02;//0000 0010 P6CR|=0xa4;//1010 1000*/ /*方法三:先使用方法二扫描列,最后列1单独读写 //列1读写实验 P5CR=0xf0; P6CR&=0x5b;//0101 1011 //列输入1 P6CR|=0x58;//0101 1000/ KH1=0;KH2=0;KH3=0;KH4=0; if(KL1==0)keybuf=0x01;//列1有按键按下 while(KL1==0); //列23,读写*/ //按键读写程序终结版,低电平扫行2,行全拉低 //行输出0 P6CR&=0x5b;//0101 1011 //列输入1 P6CR|=0x58;//0101 1000/ for(i=0;i<4;i++) {switch(i) {//无AT24C02操作尽量不开启KH4给AT24C02供电 case 0:KH1=1;KH2=1;KH3=1;KH4=0;break;//扫描行四 case 1:KH1=0;KH2=1;KH3=1;KH4=0;break;//扫描行一 case 2:KH1=1;KH2=1;KH3=0;KH4=0;break;//扫描行三 //最后扫描红绿LED公共端,全部拉低,全扫描无LED点亮 case 3:KH1=0;KH2=0;KH3=0;KH4=0;break;//扫描行二 } delay(200); if(KL1==0){keybuf=0x00;get_bit=1;break;} if(KL2==0){keybuf=0x01;get_bit=1;break;} if(KL3==0){keybuf=0x02;get_bit=1;break;} } if(get_bit==1)//获取按键值 {get_bit=0; //延时去除抖动 //delay(250);// //扫描状态已经保存 switch(keybuf) {case 0x00:if(KL1==0)get_bit=1;;break; case 0x01:if(KL2==0)get_bit=1;;break; case 0x02:if(KL3==0)get_bit=1;;break; default:keybuf=0xff; } if(get_bit==1) {keybuf=i+i+i+keybuf;//解码按键 i=key_rel[keybuf];//解码按键 keybuf=i;//获取按键值 //按键声光指示 buzzer(); //等待按键弹起 KH1=0;KH2=0;KH3=0;KH4=0; delay(80); while((KL1&&KL2&&KL3)==0); } else keybuf=0xff; } else keybuf=0xff;//无效按键 //行2读写无效分析 //TCC引脚下CONT指令0x0* } /******************************************************** * * END key_scan * ********************************************************/