单片机按键去抖的方法_单片机按键扫描消抖的方法 - CSDN
  • 单片机按键去抖原理

    2016-04-07 20:02:38
    按键去抖由上图可以看出理想波形与实际波形之间是...因此单片机在检测键盘是否按下时都要加上抖动操作,有专用的抖动电路,也有专门的抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。/* 软件去抖 */

    按键去抖


    这里写图片描述

    由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。


    /*   软件去抖  */                                                                                                                                  
    if0 == K1 )          //如果有键按下                                                                                                                  
    {                                                                                                                                                
        delay_ms(8);        //延时一段时间去抖                                                                                                    
        if (0 == K1)        //如果真的有键按下,检测到得是稳定闭合状态                                                                                       
          {                                                                                                                                
                 ...        //按键以后需要做的事情                                                                                
          }                                                                                                                              
          while(!K1);    //松手检测,如果按住不放则一直在循环里                                                                                      
    }          
    展开全文
  • 单片机按键

    2018-08-24 16:55:59
    一、按键分析  按键按下去分为以下几种: 1、按下立刻弹起,识别为一次按下动作 这种情况下,闭合时间取决于人手的按键速度,但是通常都在100ms以上 2、按下不抬起(保持一段时间),识别为单次点击或者连续点击 3...

    转自:https://blog.csdn.net/elikang/article/details/77053845 

    一、按键分析

      按键按下去分为以下几种:
    1、按下立刻弹起,识别为一次按下动作
    这种情况下,闭合时间取决于人手的按键速度,但是通常都在100ms以上
    2、按下不抬起(保持一段时间),识别为单次点击或者连续点击
    3、按下不抬起是一个状态(闭合保持),然后抬起是另一个状态(断开保持) 

    检测按键可以用两种方法:
    1、电压检测,需要不断扫描IO电平,比较消耗CPU资源
    2、中断检测,不必持续检测,节省CPU的资源

    按键抖动情况:
    1、电压检测
    按键刚按下和抬起的时候,电平是不稳定的,需要考虑去抖。
    按键动作会有一段稳定的状态,一般采用延迟采样或者持续采样就能解决这个问题。
    2、中断检测
    中断状态下,有可能按下一次产生超过一次中断,比较多见的是触发两次中断。
    解决这个问题,需要考虑按键动作的时间,在一段时间内发生的多次中断都要识别为一次。
    并且,中断检测方式不能用于保持按下按键,持续产生按键动作的情况。   
     

    二、按键抖动原因


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



    按键稳定闭合时间长短是由操作人员决定的,通常都会在100ms以上,刻意快速按的话能达到40-50ms左右,很难再低了。

    抖动时间是由按键的机械特性决定的,一般都会在10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。

     

    三、按键去抖分析

    按键消抖可分为硬件消抖和软件消抖。

    硬件消抖:
    利用电容的充放电特性来对抖动过程中产生的电压毛刺进行平滑处理,从而实现消抖。
    但实际应用中,这种方式的效果往往不是很好,而且还增加了成本和电路复杂度,所以实际中使用的并不多。



    软件实现消抖:
    通过延时程序过滤。
    最简单的消抖原理,就是当检测到按键状态变化后,先等待一个10ms左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。

    如果需要更精细的检测,可以考虑结合持续对IO状态进行采样来去抖。

    ======================

    软件去抖的几个思路:

    1、延迟
    先定义一个变量记录按键的状态:char key;
    然后轮询检测按键状态,当按键状态改变的时候,判断为有按键动作,接下来进入延迟函数;
    等到延迟时间过了之后,再读取按键状态,如果按键状态仍为按下状态,则说明确实是有按键动作,不是抖动。

    2、中断加延迟方式
    单片机总是轮询状态会浪费很多资源,尤其是单片机运行的时间,可以通过中断方式来解决。
    中断模式下,单片机不需要轮询按键状态,当有中断产生的时候才会进入延时函数,进行按键去抖程序。

    这种情况下,延时函数在哪里进行处理就有两种选择:
    一种是放在中断函数中进行,这个时候中断函数占用的时间就比较长,影响响应速度;
    另一种是放在中断函数之外进行,这个时候就缩短了中断处理时间,但是这个时候就需要一个标志位来表明是否有中断产生,而且单片机也需要不断查询,只是节约了查询时候读取IO状态的步骤。

    3、持续采样
    持续采样会大大提高采样的准确度,但是同时也会增加CPU的开销。
    在使用中,需要根据需要选择不同的采样频率,一般每10ms采集一次就足够了。

    ==
    关于延迟方法:
    简单的延时,可以采用空循环来实现,这个方法比较消耗CPU的资源,CPU任务较重的时候不建议使用。延时函数可以考虑使用定时器替换,但这又消耗了定时器资源,不过只要够用的话还是尽量用,毕竟可以减轻CPU的负担。

    关于一次按键过程中产生多次中断的处理:
    一次按键过程,可能产生多次中断,可以设置一个标志位来显示是否处于按键识别处理阶段,如果处于按键识别处理阶段即便产生中断也不进行任何响应,这样就可以忽略多余的中断了。

    四、我的按键去抖方案


    1、使用中断检测按键触发



    只有在int_flag为0的时候才会设置int_flag的值为1,此时只有在后续的处理完成后,才会将ing_flag重新设为0,才会响应下一次按键中断。这就消除了一次按键动作会产生多次中断的情况。

    2、产生中断后,后续的按键处理程序,可以保证正确识别按键按下还是按键抖动 


    这里为了演示方便,直接使用了延时函数,实际使用中为了节省CPU开销,应该使用定时中断。

    FIRE_Key.h

    /*!
     *     COPYRIGHT NOTICE
     *     Copyright (c) 2013,野火科技
     *     All rights reserved.
     *     技术讨论:野火初学论坛 http://www.chuxue123.com
     *
     *     除注明出处外,以下所有内容版权均属野火科技所有,未经允许,不得用于商业用途,
     *     修改内容时必须保留野火科技的版权声明。
     *
     * @file       FIRE_KEY.h
     * @brief      KEY驱动头文件
     * @author     野火科技
     * @version    v5.0
     * @date       2013-07-10
     */
    
    #ifndef __FIRE_KEY_H__
    #define __FIRE_KEY_H__
    
    
    //下面是定义按键的时间,单位为 : 10ms(中断时间)
    #define KEY_DOWN_TIME           1       //消抖确认按下时间
    #define KEY_HOLD_TIME           50      //长按hold确认时间,最多253,否则需要修改 keytime 的类型
                                            //如果按键一直按下去,则每隔 KEY_HOLD_TIME - KEY_DOWN_TIME 时间会发送一个 KEY_HOLD 消息
    
    //定义按键消息FIFO大小
    #define KEY_MSG_FIFO_SIZE       20      //最多 255,否则需要修改key_msg_front/key_msg_rear类型
    
    //按键端口的枚举
    typedef enum
    {
        KEY_U,  //上
        KEY_D,  //下
    
        KEY_L,  //左
        KEY_R,  //右
    
        KEY_A,  //取消
        KEY_B,  //选择
    
        KEY_START,  //开始
        KEY_STOP,   //停止
    
        KEY_MAX,
    } KEY_e;
    
    
    //key状态宏定义
    typedef enum
    {
        KEY_DOWN  =   0,         //按键按下时对应电平
        KEY_UP    =   1,         //按键弹起时对应电平
    
        KEY_HOLD,
    
    } KEY_STATUS_e;
    
    //按键消息结构体
    typedef struct
    {
        KEY_e           key;
        KEY_STATUS_e    status;
    } KEY_MSG_t;
    
    
    void            key_init(KEY_e key);            // KEY初始化函数(key 小于 KEY_MAX 时初始化 对应端口,否则初始化全部端口)
    KEY_STATUS_e    key_check(KEY_e key);           //检测key状态(带延时消抖)
    
    
    //定时扫描按键
    uint8   get_key_msg(KEY_MSG_t *keymsg);         //获取按键消息,返回1表示有按键消息,0为无按键消息
    void    key_IRQHandler(void);                   //需要定时扫描的中断复位函数(定时时间为10ms)
    
    
    #endif  //__FIRE_KEY_H__

    FIRE_Key.c

    /*!
     *     COPYRIGHT NOTICE
     *     Copyright (c) 2013,野火科技
     *     All rights reserved.
     *     技术讨论:野火初学论坛 http://www.chuxue123.com
     *
     *     除注明出处外,以下所有内容版权均属野火科技所有,未经允许,不得用于商业用途,
     *     修改内容时必须保留野火科技的版权声明。
     *
     * @file       FIRE_KEY.c
     * @brief      KEY驱动函数实现
     * @author     野火科技
     * @version    v5.0
     * @date       2013-07-10
     */
    
    
    /*
     * 包含头文件
     */
    #include "common.h"
    #include "MK60_port.h"
    #include "MK60_gpio.h"
    #include "FIRE_key.h"
    
    
    /*
     * 定义 KEY 编号对应的管脚
     */
    
    PTXn_e KEY_PTxn[KEY_MAX] = {PTD10, PTD14, PTD11, PTD12, PTD7, PTD13, PTC14, PTC15};
    
    
    /*!
     *  @brief      初始化key端口(key 小于 KEY_MAX 时初始化 对应端口,否则初始化全部端口)
     *  @param      KEY_e    KEY编号
     *  @since      v5.0
     *  Sample usage:       KEY_init (KEY_U);    //初始化 KEY_U
     */
    void    key_init(KEY_e key)
    {
        if(key < KEY_MAX)
        {
            gpio_init(KEY_PTxn[key], GPI, 0);
            port_init_NoALT(KEY_PTxn[key], PULLUP);         //保持复用不变,仅仅改变配置选项
        }
        else
        {
            key = KEY_MAX;
    
            //初始化全部 按键
            while(key--)
            {
                gpio_init(KEY_PTxn[key], GPI, 0);
                port_init_NoALT(KEY_PTxn[key], PULLUP);         //保持复用不变,仅仅改变配置选项
            }
    
        }
    }
    
    /*!
     *  @brief      获取key状态(不带延时消抖)
     *  @param      KEY_e           KEY编号
     *  @return     KEY_STATUS_e    KEY状态(KEY_DOWN、KEY_DOWN)
     *  @since      v5.0
     *  Sample usage:
                        if(key_get(KEY_U) ==  KEY_DOWN)
                        {
                            printf("\n按键按下")
                        }
     */
    KEY_STATUS_e key_get(KEY_e key)
    {
        if(gpio_get(KEY_PTxn[key]) == KEY_DOWN)
        {
            return KEY_DOWN;
        }
        return KEY_UP;
    }
    
    
    /*!
     *  @brief      检测key状态(带延时消抖)
     *  @param      KEY_e           KEY编号
     *  @return     KEY_STATUS_e    KEY状态(KEY_DOWN、KEY_DOWN)
     *  @since      v5.0
     *  Sample usage:
                        if(key_check(KEY_U) ==  KEY_DOWN)
                        {
                            printf("\n按键按下")
                        }
     */
    KEY_STATUS_e key_check(KEY_e key)
    {
        if(key_get(key) == KEY_DOWN)
        {
            DELAY_MS(10);
            if( key_get(key) == KEY_DOWN)
            {
                return KEY_DOWN;
            }
        }
        return KEY_UP;
    }
    
    
    /*********************  如下代码是实现按键定时扫描,发送消息到FIFO  ********************/
    /*
     * 定义按键消息FIFO状态
     */
    typedef enum
    {
        KEY_MSG_EMPTY,      //没有按键消息
        KEY_MSG_NORMAL,     //正常,有按键消息,但不满
        KEY_MSG_FULL,       //按键消息满
    } key_msg_e;
    
    /*
     * 定义按键消息FIFO相关的变量
     */
    KEY_MSG_t           key_msg[KEY_MSG_FIFO_SIZE];             //按键消息FIFO
    volatile uint8      key_msg_front = 0, key_msg_rear = 0;    //接收FIFO的指针
    volatile uint8      key_msg_flag = KEY_MSG_EMPTY;           //按键消息FIFO状态
    
    
    /*!
     *  @brief      发送按键消息到FIFO
     *  @param      KEY_MSG_t       按键消息
     *  @since      v5.0
     *  Sample usage:
                        KEY_MSG_t *keymsg;
                        keymsg.key      = KEY_U;
                        keymsg.status   = KEY_HOLD;
                        send_key_msg(keymsg);                   //发送
     */
    void send_key_msg(KEY_MSG_t keymsg)
    {
        uint8 tmp;
        //保存在FIFO里
        if(key_msg_flag == KEY_MSG_FULL)
        {
            //满了直接不处理
            return ;
        }
        key_msg[key_msg_rear].key = keymsg.key;
        key_msg[key_msg_rear].status = keymsg.status;
    
        key_msg_rear++;
    
        if(key_msg_rear >= KEY_MSG_FIFO_SIZE)
        {
            key_msg_rear = 0;                       //重头开始
        }
    
        tmp = key_msg_rear;
        if(tmp == key_msg_front)                   //追到屁股了,满了
        {
            key_msg_flag = KEY_MSG_FULL;
        }
        else
        {
            key_msg_flag = KEY_MSG_NORMAL;
        }
    }
    
    
    /*!
     *  @brief      从FIFO里获取按键消息
     *  @param      KEY_MSG_t       按键消息
     *  @return     是否获取按键消息(1为获取成功,0为没获取到按键消息)
     *  @since      v5.0
     *  Sample usage:
                        KEY_MSG_t keymsg;
                        if(get_key_msg(&keymsg) == 1)
                        {
                            printf("\n按下按键KEY%d,类型为%d(0为按下,1为弹起,2为长按)",keymsg.key,keymsg.status);
                        }
     */
    uint8 get_key_msg(KEY_MSG_t *keymsg)
    {
        uint8 tmp;
    
        if(key_msg_flag == KEY_MSG_EMPTY)               //按键消息FIFO为空,直接返回0
        {
            return 0;
        }
    
        keymsg->key = key_msg[key_msg_front].key;       //从FIFO队首中获取按键值
        keymsg->status = key_msg[key_msg_front].status; //从FIFO队首中获取按键类型
    
        key_msg_front++;                                //FIFO队首指针加1,指向下一个消息
    
        if(key_msg_front >= KEY_MSG_FIFO_SIZE)          //FIFO指针队首溢出则从0开始计数
        {
            key_msg_front = 0;                          //重头开始计数(循环利用数组)
        }
    
        tmp = key_msg_rear;
        if(key_msg_front == tmp)                        //比较队首和队尾是否一样,一样则表示FIFO已空了
        {
            key_msg_flag = KEY_MSG_EMPTY;
        }
        else
        {
            key_msg_flag = KEY_MSG_NORMAL;
        }
    
        return 1;
    }
    
    /*!
     *  @brief      定时检测key状态
     *  @since      v5.0
     *  @note       此函数需要放入 定时中断复位函数里,定时10ms执行一次
     */
    void key_IRQHandler(void)
    {
    
        KEY_e   keynum;
        static uint8 keytime[KEY_MAX];                          //静态数组,保存各数组按下时间
    
        KEY_MSG_t keymsg;                                       //按键消息
    
        for(keynum = (KEY_e)0 ; keynum < KEY_MAX; keynum ++)    //每个按键轮询
        {
            if(key_get(keynum) == KEY_DOWN)                     //判断按键是否按下
            {
                keytime[keynum]++;                              //按下时间累加
    
                if(keytime[keynum] <= KEY_DOWN_TIME)            //判断时间是否没超过消抖确认按下时间
                {
                    continue;                                   //没达到,则继续等待
                }
                else if(keytime[keynum] == KEY_DOWN_TIME + 1 )  //判断时间是否为消抖确认按下时间
                {
                    //确认按键按下
                    keymsg.key = keynum;
                    keymsg.status = KEY_DOWN;
                    send_key_msg(keymsg);                       //把按键值和按键类型发送消息到FIFO
                }
                else if(keytime[keynum] <= KEY_HOLD_TIME)       //是否没超过长按HOLD按键确认时间
                {
                    continue;                                   //没超过,则继续等待
                }
                else if(keytime[keynum]  == KEY_HOLD_TIME + 1)  //是否为长按hold确认时间
                {
                    //确认长按HOLD
                    keymsg.key = keynum;
                    keymsg.status = KEY_HOLD;
                    send_key_msg(keymsg);                       //发送
                    keytime[keynum] = KEY_DOWN_TIME + 1;
                }
                else
                {
                    keytime[keynum] = KEY_DOWN_TIME + 1;        //继续重复检测 hold 状态
                }
            }
            else
            {
                if(keytime[keynum] > KEY_DOWN_TIME)             //如果确认过按下按键
                {
                    keymsg.key = keynum;
                    keymsg.status = KEY_UP;
                    send_key_msg(keymsg);                       //发送按键弹起消息
                }
    
                keytime[keynum] = 0;                            //时间累计清0
            }
        }
    }

     

    展开全文
  • 51单片机按键

    2018-05-05 10:28:53
    抖动是机械按键存在的现象,是必须要进行处理的。一般处理有两种方式:一种是硬件消,另一种是软件消。...下面是针对51单片机的独立按键写的一个通过定时器来消的程序:#include&lt;reg52.h&gt;sbi...

    抖动是机械按键存在的现象,是必须要进行处理的。一般处理有两种方式:一种是硬件消抖,另一种是软件消抖。硬件消抖不仅会增加开发成本,而且有时不稳定。一般都会选择软件消抖。

      因为单片机最重要的是测试状态,而不是测试过程。一般不会选择通过状态延时来消抖,而是通过定时循环测试按键的状态来消抖.下面是针对51单片机的独立按键写的一个通过定时器来消抖的程序:

    #include<reg52.h>

    sbit  ADDR0 = P1^0;
    sbit  ADDR1 = P1^1;
    sbit  ADDR2 = P1^2;
    sbit  ADDR3 = P1^3;
    sbit  ENLED = P1^4;
    sbit  KEY4 = P2^7;
    //数码管的数字十六进制表示
    unsigned char code LedChar[]=
    {
     0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
     0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
    };


    bit KeySta = 1;


    void main()
    {
     bit backup = 1;
     unsigned char cnt = 0;

    //打开总中断
     EA = 1;
     ENLED = 0;
     ADDR3 = 1;
     ADDR2 = 0;
     ADDR1 = 0;

     ADDR0 = 0;

    //设置TMOD的状态

     TMOD = 0x01;

    //定时为2ms

     TH0 = 0xF8;
     TL0 = 0xCD;
     ET0 = 1;
     TR0 = 1;
     P2  = 0xF7;
     P0 = LedChar[cnt];


     while(1)
     {
      if(KeySta !=backup)
       {
        if(backup==0)
    {
    cnt++;
    if(cnt>=10)
    {
     cnt=0;
    }
    P0 = LedChar[cnt];
    }
    backup=KeySta;
        
       }
     }


    }


    void InterruptTimer0() interrupt 1
    {
      static unsigned char keybuf = 0xFF;
      TH0 = 0xF8;
      TL0 = 0xCD;
      keybuf = (keybuf<<1) |KEY4;
      if(keybuf == 0x00)
      {
       KeySta = 0;
      }
      else if(keybuf == 0xff)
      {
       KeySta = 1;
      }
      
    }
    展开全文
  • C语言编写的51单片机按键去抖程序,不是延时去抖,是定时器去抖
  • STM32单片机按键和FPGA按键消大全 按键去抖:由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动...

    写在前面:在这里插入图片描述

    STM32单片机按键消抖和FPGA按键消抖大全

    按键去抖:由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。

    1、单片机中按键消抖程序

    1.1 单片机中,比如STM32中,一般的方法(最简单的方法)

    软件消抖程序:

    if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==1)
    {
    delay_ms(20);//延时20ms再去检测按键值
    if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==0) // 相当于下降沿

    {

    KEY1 = 1; //表示KEY1被按下

    }

    }

    1.2 比较全面的按键消抖程序及按键状态检测程序

    第一步:初始化全局时间戳的定时器,一般采用SysTick定时器来产生,每ms一次tick即可。

    第二步:初始化按键对应的IO,复用为边沿触发的外部中断。

    第三步:在外部中断函数中添加按键事件处理函数。

    代码部分:

    typedef struct _Key_t

    {

    u32 last_time;

    enum

    {

    May_Press,  
    
    Release,  
    

    }private_state;

    enum

    {

    No_Press,  
    
    Short_Press,  
    
    Long_Press,  
    

    }state;

    }Key_t;

    #define Is_ShortPress_Threshold 1500

    简单定义一个按键状态的结构体,用于管理每个按键的状态。顺便再定义一个长短按的识别阈值,用于区分按键的长短按。

    if(key_state.private_state==Release)

    {

    if(KEY==0)

    {

    key_state.private_state=May_Press;  
    
    key_state.last_time=course_ms();  
    

    }

    }

    else if(key_state.private_state==May_Press)

    {

    if(KEY==1)

    {

    if((course_ms()-key_state.last_time>10)&&(course_ms()-key_state.last_time
    
    {  
    
      key_state.state=Short_Press;  
    
      key_state.private_state=Release;  
    
    }  
    
    else if(course_ms()-key_state.last_time>Is_ShortPress_Threshold)  
    
    {  
    
      key_state.state=Long_Press;  
    
      key_state.private_state=Release;  
    
    }  
    
    else  
    
      key_state.private_state=Release;  
    

    }

    }

    以上为需要添加到中断处理函数的按键事件处理函数,算法的核心是一个状态机。在本例中,按键被默认上拉,按下接地。course_ms()为获取全局时间戳的函数。

    思路解释如下:按键状态结构体有一个用于识别的状态位,默认处于Release,也就是释放的状态。一旦按键被按下,中断触发,此时检查是否是Relase状态,如果是就检查按键是否被拉低,如果是,此时进入May_Press状态,也就是可能是按下的,并且记录此时的时间戳,这一步是消抖的关键。当按键被释放,由于是边沿触发,会再次进行处理,此时检查和上一次触发之间的时间戳之差,如果小于10ms我们就认为是抖动,此时不会对按键输出状态进行修改,而是直接将按键状态置回Relase状态,反之检查差值和长短按阈值之间的关系,将state置位为对应的状态。消抖的核心在于记录时间戳,而这只是一个简单的赋值操作,并不耗费时间。

    效率上来说,延时消抖花费时间在无意义延时上,而相对较好的定时轮询还是不可避免的在轮询,而现在这种方式完全是中断性质的。唯一多出的开销(全局时间戳)并不是只可以用于按键消抖,另外在HAL库中存在直接获取tick的函数,这样实现就更方便了。经实际测试,消抖效果可以达到其他两种消抖算法的水平。

    2、FPGA按键消抖程序

    首先,做两个假定,以方便后面的描述:

    假定按键的默认状态为0,被按下后为1

    假定按键抖动时长小于20ms,也即使用20ms的消抖时间

    核心:方案

    最容易想到的方案

    在按键电平稳定的情况下,当第一次检测到键位电平变化,开始20ms计时,计时时间到后将按键电平更新为当前电平。

    或许这才是最容易想的方案

    在20ms计时的过程中,有任何的电平变化都立即复位计时

    消除按键反应延时抖方案

    在有电平变化时立即改变按键输出电平,并开始20ms计时,忽略这其中抖动

    测试平台设计(修改代码以仿真的1us代替实际1ms)

    无抖动 上升沿抖动5毫秒

    下降沿抖动15毫秒

    上升和下降沿均抖动19毫秒

    附加测试(可以不通过)

    抖动25毫秒

    代码

    方案1

    module debounce( input wire clk, nrst, input wire key_in, output reg key_out
    ); // 20ms parameter// localparam TIME_20MS = 1_000_000;
    localparam TIME_20MS = 1_000; // just for test // variable
    reg [20:0] cnt; reg key_cnt;
    // debounce time passed, refresh key state
    always @(posedge clk or negedge nrst) begin
    if(nrst == 0)
    key_out <= 0; else if(cnt == TIME_20MS - 1)
    key_out <= key_in; end

    // while in debounce state, count, otherwise 0
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;        else if(key_cnt)
            cnt <= cnt + 1'b1;
        else
            cnt <= 0; 
    end
     
     // 
     always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                key_cnt <= 0;            else if(key_cnt == 0 && key_in != key_out)
                key_cnt <= 1;            else if(cnt == TIME_20MS - 1)
                key_cnt <= 0;     endendmodule
    

    方案2

    module debounce( input wire clk, nrst, input wire key_in, output reg key_out
    );// localparam TIME_20MS = 1_000_000;
    localparam TIME_20MS = 1_000; reg key_cnt; reg [20:0] cnt; always @(posedge clk or negedge nrst) begin
    if(nrst == 0)
    key_cnt <= 0; else if(cnt == TIME_20MS - 1)
    key_cnt <= 0; else if(key_cnt == 0 && key_out != key_in)
    key_cnt <= 1; end

    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;        else if(key_cnt) begin
            if(key_out == key_in)
                cnt <= 0;            else
                cnt <= cnt + 1'b1;
        end
        else
            cnt <= 0;    end
     
     always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                key_out <= 0;            else if(cnt == TIME_20MS - 1)
                key_out <= key_in;     endendmodule
    

    方案3

    module debounce( input wire clk, nrst, input wire key_in, output reg key_out
    );// localparam TIME_20MS = 1_000_000;
    localparam TIME_20MS = 1_000; // just for test

    reg key_cnt;    reg [20:0] cnt;    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_cnt <= 0;        else if(key_cnt == 0 && key_out != key_in)
            key_cnt <= 1;        else if(cnt == TIME_20MS - 1)
            key_cnt <= 0;    end
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;        else if(key_cnt)
            cnt <= cnt + 1'b1;
        else
            cnt <= 0;    end
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_out <= 0;        else if(key_cnt == 0 && key_out != key_in)
            key_out <= key_in;    endendmodule
    

    测试代码

    // 按键消抖测试电路// 时间单位`timescale 1ns/10ps// modulemodule debounce_tb; // time period parameter
    localparam T = 20; // variable
    reg clk, nrst; reg key_in; wire key_out; // instantiate debounce uut(
    .clk (clk ),
    .nrst (nrst ),
    .key_in (key_in ),
    .key_out(key_out)
    ); // clock
    initial begin
    clk = 1; forever #(T/2) clk = ~clk; end

    // reset
    initial begin
        nrst = 1;
        @(negedge clk) nrst = 0;
        @(negedge clk) nrst = 1;    end
    
    // key_in
    initial begin
        // initial value
        key_in = 0;        
        // wait reset
        repeat(3) @(negedge clk);        
        // no bounce        // key down
        key_in = 1;        // last 60ms
        repeat(3000) @(negedge clk);        // key up
        key_in = 0;        // wait 50ms
        repeat(2500) @(negedge clk);        // down 5ms, up 15ms        // key down, bounce 5ms
        repeat(251) @(negedge clk) key_in = ~key_in;        // last 60ms
        repeat(3000) @(negedge clk);        // key up, bounce 15ms
        repeat(751) @(negedge clk) key_in = ~key_in;        // wait 50ms
        repeat(2500) @(negedge clk);        // down 19ms, up 19ms        // key down, bounce 19ms
        repeat(951) @(negedge clk) key_in = ~key_in;        // last 60ms
        repeat(3000) @(negedge clk);        // key up, bounce 19ms
        repeat(951) @(negedge clk) key_in = ~key_in;        // wait 50ms
        repeat(2500) @(negedge clk);        
        // additional, this situation shoud not ever happen        // down 25ms, up 25ms        // key down, bounce 25ms
        repeat(1251) @(negedge clk) key_in = ~key_in;        // last 60ms
        repeat(3000) @(negedge clk);        // key up, bounce 25ms
        repeat(1251) @(negedge clk) key_in = ~key_in;        // wait 50ms
        repeat(2500) @(negedge clk);        // stop        $stop;    endendmodule
    

    放在最后的,并不一定是最不重要的

    对于上面的三种方案,我比较喜欢第三种方案,它更贴合实际的按键状态,以上的代码我都做过modelsim仿真,但还没有在实际的项目中验证。在整理准备这个博客的时候,我又想到了一个感觉是更巧妙的方案,具体是这样的:在第三个方案的基础上,因为按键输入有变化的第一时刻,输出就已经改变了,在这种情况下,我可以把计时的时长改为一个很小的值,该值只要比抖动中的最长高低电平变化时间长即可。但想想也没这个必要,且这个抖动的高低电平变化时长我也很难去给它界定一个值。
    在这里插入图片描述

    展开全文
  • 初学单片机时,讲到了一个按键“消”概念,视屏教程中只是说到要确定按键是不是真正按下,所以需要加一个延时来判断。 附上延时消程序代码: 代码1 void keypros() { if(k1==0) //检测按键K1是否按下 { ...
  • 单片机按键程序

    2014-09-15 15:41:37
    通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如...
  • 单片机按键

    2019-08-20 17:23:29
    却不明白真正操作起来会遇到的一系列问题,由于机械触点的弹性,一个按键开关在闭合后不会马上稳定接通,断开时也不会马上断开,如果不处理的话,会导致按键识别为多下,故我们就来研究一下按键(针对的是K1 K2 ...
  • 单片机按键分析

    2019-01-12 09:58:01
    单片机按键分析http://c.biancheng.net/cpp/html/1901.html
  • 使用定时器给按键计时消,在大循环while里不堵塞,可以同时检测到每个按键各自的“按下”、“长按”、“抬起”的状态,实现组合键的检测处理。button.c和button.h直接添加到工程里,编译然后修改到对应的布尔变量...
  •   按键是我们学习单片机时一个重要的器件,我们在按下按键时,难免会一位手抖动或者接触不稳定,但如果不消按键按下的瞬间将会产生成百上千次按键有效的结果,最终的现象不可预料,所以有必要进行消。...
  • 开发板:普中A7开发板 核心板:51和STM32F103C8T6 ...因为其中一种按键抖方法就是学自宋老师课程,需要详细查看的话可以看《手把手教你51单片机.pdf》文档。 我知道的软件消(轮询方式)有两种方式: 1、在...
  • 按键原理 一、首先来回顾一下按键延时消 按键由于是机械结构,按下的时候难免产生抖动,一般抖动会在按下的时候与松开的时候产生,抖动时间大概是10ms 于是针对按键抖动就有了延时消的一种简单的解决方法: ...
  • 51单片机_独立按键延时消_独立按键定时器消_矩阵键盘定时器消 1.独立按键_延时消 //独立按键_延时消 #include<reg52.h> #define uchar unsigned char #define uint unsigned int #define ...
  • 在实际工程中常用到中断的算法来实现按键。 思路:启动一个定时中断,每2ms进一次中断扫描按键状态并储存。连续扫描8次后,观察这8次按键状态是否一致。如果一致,即按键没有发生动作,处于稳定状态。 被监测到...
  • STM32单片机定时器做按键 原因:直接用软件延时做消会暂用整个资源,导致程序全部卡死等待延时,如下面程序: if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)) { delay_ms(10);//抖动 key_up=0; ...
1 2 3 4 5 ... 20
收藏数 1,969
精华内容 787
关键字:

单片机按键去抖的方法