找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 32875|回复: 19
收起左侧

按键消抖方法与抖动原因分析(状态机,延时,逻辑关系消抖)单片机源程序

  [复制链接]
ID:391723 发表于 2018-8-31 02:34 | 显示全部楼层 |阅读模式
本人第一次发帖,如果有错误,欢迎大家指正!!!有关按键消抖的话题已经有很多人提出来了,在这里我把它进行一个总结。希望能帮助到大家!
1.按键抖动的原因
       按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。形成以下的波形
按键抖动图1.png     按键抖动图2.png
2.按键抖动的原理
      按键抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。键抖动会引起一次按键被误读多次。为确保单片机对按键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到按键释放到稳定状态后再去作处理。
3.按键消抖的方法
(1)硬件的消抖
      利用电容的充放电特性来对抖动过程中产生的电压毛刺进行平滑处理,从而实现消抖。在按键的两端并联一个0.1uf的电容。如按键消抖硬件图
按键消抖硬件图.png
(2)软件的消抖
     1.延时消抖
        刚接触单片机的时候,最常用的方法,利用延时,消去抖动。代码如下:
       if(按键是否按下)
       {                        //按键按下的条件下
           Deadly_1ms(5); // 延时消去抖动
           if(按键是否按下)
          {                   //按键再次确认按下
              //执行按键功能
             while(按键是否松开);
          }
       }
   注:按键详细代码见工程文件延时消抖
    2.状态机消抖
      将按键的状态分为三种 是否按下,是否抖动,是否弹起。检测到按下时,转向判断是否抖动,并执行按键功能。等按键功能执行完了以后直接跳到按键是否弹起的状态。等弹起后,再来判断是否按下的状态。
       在按键的原理图中,将1、2相接,则构成的是4*4的矩阵键盘,并且矩阵键盘与单片机的P3口相接。
       //按键原理图见上传的图片
         按键模块原理图.png
//设置按键的三种状态及按键接口
#define KEY P3
#define key_state_0  0  //判断是否按下
#define key_state_1  1 //判断是否为抖动
#define key_state_2  2 //判断是否弹起

unsigned char key_read(void)
{
  static char key_state = key_state_0;
  unsigned char key_return=0, key_press;
  unsigned char key1,key2;
        
        KEY=0xf0;   
        key1=KEY&0xf0;
        KEY=0x0f;
        key2=KEY&0x0f;
        key_press =key1|key2;  //读取矩阵按键每个按键的状态,存在key_press里
        
switch (key_state)  // 判断按键状态
{         
   case key_state_0:    //是否按下
   if (key_press!=0xff) key_state = key_state_1;  //没有按键按下时则读取出的0xff,若有不同说明有按键按下,则跳转到判断是否为抖动的状态
   break;
         
   case key_state_1:     
   if (key_press !=0xff) //再次确认是否按下,防止因单片机的速度过快,导致误判。
   {   
       switch (key_press)//根据key_press的值来执行相对应的功能
       {
           case 0xee: key_return = 19; //这里的0xee是怎么的来的,简单来推一下
           case 0xed: key_return = 18; //单片机的P3口初始化输出的是高电平,先将P3口输出0xf0,如果有按键按下,则P3口中的高四位的某一位的电平会被拉低,此时记录P3口的状态(key1)
           case 0xeb: key_return = 17; //读取了高四位的电平,之后再来读取低四位的电平(key2) ,之后 | 在一起得到key_press,得到的key_press中一定有两位是0;
           case 0xe7: key_return = 16;//举例 : 当我按下s19时,key1 = 0xe0;(即P3^4 = 0,高四位的第一位电平被拉低),key2 = 0x0e;(即P3^0 = 0,低四位的第一位电平被拉低),这样key_press = key_press =key1|key2  = 0xee;
           case 0xde: key_return = 15;//以此类推
           case 0xdd: key_return = 14;
           case 0xdb: key_return = 13;
           case 0xd7: key_return = 12;
           case 0xbe: key_return = 11;
           case 0xbd: key_return = 10;
           case 0xbb: key_return = 9;
           case 0xb7: key_return = 8;
           case 0x7e: key_return = 7;
           case 0x7d: key_return = 6;
           case 0x7b: key_return = 5;
           case 0x77: key_return = 4;
       }
                        key_state = key_state_2; //这里跳转到判断弹起的状态
   }
   else
                        key_state = key_state_0;  
   break;

   case key_state_2:
   if (key_press==0xff) key_state = key_state_0;  //等待按键完全弹起
   break;
}
return key_return;
}
注:具体用法见工程状态机消抖  

    3.逻辑关系消抖
       将按键的原理图中,将2、3相接,则构成的是独立键盘(s4,s5,s6,s7),并且独立键盘与单片机的P3口的低四位相接,另一端与gnd相接。
    利用逻辑关系 与&、或 | 、非 !、异或 ^,来消除抖动。先贴出源代码:

    unsigned char ReadData = KEYPORT^0xff;
    Trg = ReadData & (ReadData ^ Cont);      
    Cont = ReadData;

    Trg是判断那个按键按下,Cont用来判断长按、短按的;
分析:   (1)没有按键按下(初始值 Trg  =  0,Cont  = 0)
                  unsigned char ReadData = P3^0xff;  => ReadData  = 0xff ^0xff = 0x00;
                  Trg = ReadData & (ReadData ^ Cont); =>Trg  =0x00 &  (0x00 ^0x00)  = 0x00;
                  Cont = ReadData;    =>Cont  = 0x00;

                 结果 :
                         ReadData  = 0x00
                        Trg           = 0x00
                        Cont         = 0x00

            (2) s4按下
                  刚按下时
                  unsigned char ReadData = P3^0xff;  => ReadData  = 0xf7 ^0xff = 0x08;
                  Trg = ReadData & (ReadData ^ Cont); =>Trg  =0x08 &  (0x08^0x00)  = 0x08;
                  Cont = ReadData;    =>Cont  = 0x08;

               抖动时
                  unsigned char ReadData = P3^0xff;  => ReadData  = 0xff ^0xff = 0x00;
                  Trg = ReadData & (ReadData ^ Cont); =>Trg  =0x00 &  (0x00^0x08)  = 0x00;
                  Cont = ReadData;    =>Cont  = 0x00;

                 结果 :
                       刚按下时:
                        ReadData  = 0x08
                        Trg           = 0x08
                        Cont         = 0x08

                       抖动时:
                        ReadData  = 0x00
                        Trg           = 0x00
                        Cont         = 0x00


            (3) s4长按
                  unsigned char ReadData = P3^0xff;  => ReadData  = 0xf7 ^0xff = 0x08;
                  Trg = ReadData & (ReadData ^ Cont); =>Trg  =0x08 &  (0x08 ^0x00)  = 0x00;
                  Cont = ReadData;    =>Cont  = 0x08;

                 结果 :
                         ReadData  = 0x08
                        Trg           = 0x00
                        Cont         = 0x08


            (4) s4松开
                  unsigned char ReadData = P3^0xff;  => ReadData  = 0xff ^0xff = 0x00;
                  Trg = ReadData & (ReadData ^ Cont); =>Trg  =0x08 &  (0x08 ^0x00)  = 0x00;
                  Cont = ReadData;    =>Cont  = 0x00;

                 结果 :
                         ReadData  = 0x00
                        Trg           = 0x00
                        Cont         = 0x00


将上面的数据进行汇总、分析
ReadData  Trg           Cont         
没有按键按下 0x00 0x00 0x00
s4刚按下 0x08 0x08 0x08
s4有抖动时 0x00 0x00 0x00
s4长按 0x08 0x00 0x08
  s4松开 0x00 0x00 0x00


Trg只有在按键刚按下时有不到等于0的值,因此我们检测到Trg 不为0时,说明有按键按下;
Cont 只要有按键按下时,它的值就不为0,因此我们定时一段时间,来检测Cont,,若不变,则按键时长按的状态。
注:具体用法见工程逻辑关系消抖

          到这里只是简单的介绍了几种按键消抖,还有很多方法没有介绍,比如,中断消抖,门电路消抖,计时消抖…,但万变不离其中。学会了其中一种,其他的就自然懂了。
         希望这篇帖子能帮助到大家!!!!
0.png
全部资料51hei下载地址(全部程序源码):
按键.rar (68.28 KB, 下载次数: 171)

评分

参与人数 2黑币 +105 收起 理由
lakeleeson + 5 很给力!
admin + 100 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:391536 发表于 2018-8-31 14:09 | 显示全部楼层
楼主写的非常详细 好资料,51黑有你更精彩!!!
回复

使用道具 举报

ID:896842 发表于 2021-3-26 14:36 | 显示全部楼层
楼主写的很好,先收藏研究一下
回复

使用道具 举报

ID:15415 发表于 2023-5-29 16:48 | 显示全部楼层
非常好的资料,谢谢楼主
回复

使用道具 举报

ID:202023 发表于 2023-6-21 10:55 | 显示全部楼层
不错,万丈高楼平地起,越是看似简单的东西,越要用心去学。谢谢楼主分享。
回复

使用道具 举报

ID:202023 发表于 2023-6-21 11:01 | 显示全部楼层
楼主是你的资料有问题,还是我的软件有问题。
error.JPG
回复

使用道具 举报

ID:202023 发表于 2023-6-21 11:03 | 显示全部楼层
oh,在不解压下可以打开,解压了反而打不开了,是我的原因。
回复

使用道具 举报

ID:423911 发表于 2023-8-13 09:16 | 显示全部楼层
感谢楼主,非常好的资料,很有用。
回复

使用道具 举报

ID:34149 发表于 2023-8-15 10:45 | 显示全部楼层
分析很透彻!好办法!
回复

使用道具 举报

ID:224580 发表于 2023-8-17 14:08 | 显示全部楼层
分析很透,佩服这些大神
回复

使用道具 举报

ID:1083996 发表于 2023-8-20 09:55 | 显示全部楼层
很棒的分析,感谢分享。
回复

使用道具 举报

ID:202023 发表于 2023-8-30 13:19 | 显示全部楼层

楼主写的非常详细 好资料,51黑有你更精彩!!!
回复

使用道具 举报

ID:227818 发表于 2024-8-14 13:40 | 显示全部楼层
谢谢大佬
回复

使用道具 举报

ID:1096929 发表于 2024-8-16 19:18 | 显示全部楼层
哎呀,你为什么把每个按键都定义呢,直接横扫纵扫不就行了
回复

使用道具 举报

ID:344848 发表于 2024-8-17 03:04 | 显示全部楼层
使用硬件去抖动电路:1)RS触发器去抖动,2)单稳触发器——74HC221,3)同相器组成的积分去抖动电路。
回复

使用道具 举报

ID:466250 发表于 2024-8-17 03:09 | 显示全部楼层
程序啰里啰唆,采样后仅判断2次,完全没这个必要的逻辑,后面的keycode重新赋值也根本没必要,采样出来本来已经是按键值,没必要再次转换。除非还有其他的采样,要放在一个8位的byte里面去识别。
回复

使用道具 举报

ID:1130610 发表于 2024-8-17 09:34 | 显示全部楼层
写的非常详细
回复

使用道具 举报

ID:496636 发表于 2024-8-17 12:16 | 显示全部楼层
看了一下,你的状态机消抖,你key_press!=0xff时,表明有键按下,赋key_state=1,则执行case=1语句,再判断key_press!=0xff,就几条语句,也就几个微秒而已,延时很短的,这有问题。
还是手把手教你学51单片机 宋学松处理阵列按键程序来得好。
它的思路是:采用中断的形式,1毫秒中断,在1毫秒中断中,使某一行为低电平,其它为高电平。然后把这一行中的按键状态缓存在状态字节数组中(4*4的二维数组中),有四行,则要执行4次,花4毫秒才能把各个按键的状态存储一遍。存储的字节先左移一位,然后从最低位移入状态位(列状态),1为按键没按下,0表示按下。通过16次,16毫秒扫描,就存储了四次矩阵按键状态,这样存储在字节的低四位中。
假设4次都是1,表明已稳定弹起,假设4次都为0,表明稳定键下,如果不全是1或是0,则在按键过渡期。只要把这16这字节的数组与0x0f按位与,就能判断是不是稳定按下,或是稳定弹出,如果是,就返回某按键值为1或是0,使代表按键消抖状态的二维数组中的某个数为1或是0
回复

使用道具 举报

ID:496636 发表于 2024-8-17 12:23 | 显示全部楼层
贴上宋学松的程序片段:此程序在1毫秒中断程序中执行
//将一行的 4 个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按键状态
for (i=0; i<4; i++) //每行 4 个按键,所以循环 4 次
{
if ((keybuf[keyout][i] & 0x0F) == 0x00)
{ //连续 4 次扫描值为 0,即 4*4ms 内都是按下状态时,可认为按键已稳定的按下
KeySta[keyout][i] = 0;
}
else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
{ //连续 4 次扫描值为 1,即 4*4ms 内都是弹起状态时,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
}
}
//执行下一次的扫描输出
keyout++; //输出索引递增
keyout = keyout & 0x03; //索引值加到 4 即归零
switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default: break;
}
回复

使用道具 举报

ID:1129643 发表于 2024-8-31 10:17 | 显示全部楼层
看了楼主的贴子对按键消抖又有了新的理解
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表