- 壹零零单片机©版权所有 2008-2025 粤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
*
********************************************************/