单片机硬件消抖_单片机 按键硬件消抖 - CSDN
  • 单片机按键消抖

    万次阅读 多人点赞 2018-08-24 16:55:59
    一、按键分析  按键按下去分为以下几种: 1、按下立刻弹起,识别为一次按下动作 这种情况下,闭合时间取决于人手的按键速度,但是通常都在100ms以上 ...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
            }
        }
    }

     

    展开全文
  • 单片机按键消抖程序

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

    按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在 10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。按键消抖可分为硬件消抖和软件消抖。

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

    在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。将上一个的程序稍加改动,得到新的带消抖功能的程序如下。
    1. #include <reg52.h>
    2.  
    3. sbit ADDR0 = P1^0;
    4. sbit ADDR1 = P1^1;
    5. sbit ADDR2 = P1^2;
    6. sbit ADDR3 = P1^3;
    7. sbit ENLED = P1^4;
    8. sbit KEY1 = P2^4;
    9. sbit KEY2 = P2^5;
    10. sbit KEY3 = P2^6;
    11. sbit KEY4 = P2^7;
    12.  
    13. unsigned char code LedChar[] = { //数码管显示字符转换表
    14. 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    15. 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
    16. };
    17.  
    18. void delay();
    19. void main(){
    20. bit keybuf = 1; //按键值暂存,临时保存按键的扫描值
    21. bit backup = 1; //按键值备份,保存前一次的扫描值
    22. unsigned char cnt = 0; //按键计数,记录按键按下的次数
    23.  
    24. ENLED = 0; //选择数码管 DS1 进行显示
    25. ADDR3 = 1;
    26. ADDR2 = 0;
    27. ADDR1 = 0;
    28. ADDR0 = 0;
    29. P2 = 0xF7; //P2.3 置 0,即 KeyOut1 输出低电平
    30. P0 = LedChar[cnt]; //显示按键次数初值
    31.  
    32. while (1){
    33. keybuf = KEY4; //把当前扫描值暂存
    34. if (keybuf != backup){ //当前值与前次值不相等说明此时按键有动作
    35. delay(); //延时大约 10ms
    36. if (keybuf == KEY4){ //判断扫描值有没有发生改变,即按键抖动
    37. if (backup == 0){ //如果前次值为 0,则说明当前是弹起动作
    38. cnt++; //按键次数+1
    39. //只用 1 个数码管显示,所以加到 10 就清零重新开始
    40. if (cnt >= 10){
    41. cnt = 0;
    42. }
    43. P0 = LedChar[cnt]; //计数值显示到数码管上
    44. }
    45. backup = keybuf; //更新备份为当前值,以备进行下次比较
    46. }
    47. }
    48. }
    49. }
    50. /* 软件延时函数,延时约 10ms */
    51. void delay(){
    52. unsigned int i = 1000;
    53. while (i--);
    54. }
    大家把这个程序下载到板子上再进行试验试试,按一下按键而数字加了多次的问题是不是就这样解决了?把问题解决掉的感觉是不是很爽呢?

    这个程序用了一个简单的算法实现了按键的消抖。作为这种很简单的演示程序,我们可以这样来写,但是实际做项目开发的时候,程序量往往很大,各种状态值也很多, while(1)这个主循环要不停的扫描各种状态值是否有发生变化,及时的进行任务调度,如果程序中间加了这种 delay 延时操作后,很可能某一事件发生了,但是我们程序还在进行 delay 延时操作中,当这个事件发生完了,程序还在 delay 操作中,当我们 delay 完事再去检查的时候,已经晚了,已经检测不到那个事件了。为了避免这种情况的发生,我们要尽量缩短 while(1)循环一次所用的时间,而需要进行长时间延时的操作,必须想其它的办法来处理。

    那么消抖操作所需要的延时该怎么处理呢?其实除了这种简单的延时,我们还有更优异的方法来处理按键抖动问题。举个例子:我们启用一个定时中断,每 2ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 8 次后,看看这连续 8 次的按键状态是否是一致的。8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段,如图 8-12。
    图 8-12  按键连续扫描判断
    图 8-12  按键连续扫描判断

    假如左边时间是起始 0 时刻,每经过 2ms 左移一次,每移动一次,判断当前连续的 8 次按键状态是不是全 1 或者全 0,如果是全 1 则判定为弹起,如果是全 0 则判定为按下,如果0 和 1 交错,就认为是抖动,不做任何判定。想一下,这样是不是比简单的延时更加可靠?

    利用这种方法,就可以避免通过延时消抖占用单片机执行时间,而是转化成了一种按键状态判定而非按键过程判定,我们只对当前按键的连续 16ms 的 8 次状态进行判断,而不再关心它在这 16ms 内都做了什么事情,那么下面就按照这种思路用程序实现出来,同样只以K4 为例。
    1. #include <reg52.h>
    2.  
    3. sbit ADDR0 = P1^0;
    4. sbit ADDR1 = P1^1;
    5. sbit ADDR2 = P1^2;
    6. sbit ADDR3 = P1^3;
    7. sbit ENLED = P1^4;
    8. sbit KEY1 = P2^4;
    9. sbit KEY2 = P2^5;
    10. sbit KEY3 = P2^6;
    11. sbit KEY4 = P2^7;
    12.  
    13. unsigned char code LedChar[] = { //数码管显示字符转换表
    14. 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    15. 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
    16. };
    17. bit KeySta = 1; //当前按键状态
    18.  
    19. void main(){
    20. bit backup = 1; //按键值备份,保存前一次的扫描值
    21. unsigned char cnt = 0; //按键计数,记录按键按下的次数
    22. EA = 1; //使能总中断
    23. ENLED = 0; //选择数码管 DS1 进行显示
    24. ADDR3 = 1;
    25. ADDR2 = 0;
    26. ADDR1 = 0;
    27. ADDR0 = 0;
    28. TMOD = 0x01; //设置 T0 为模式 1
    29. TH0 = 0xF8; //为 T0 赋初值 0xF8CD,定时 2ms
    30. TL0 = 0xCD;
    31. ET0 = 1; //使能 T0 中断
    32. TR0 = 1; //启动 T0
    33. P2 = 0xF7; //P2.3 置 0,即 KeyOut1 输出低电平
    34. P0 = LedChar[cnt]; //显示按键次数初值
    35.  
    36. while (1){
    37. //****************KeySta = KEY4; //把当前扫描值暂存
    38. if (KeySta != backup){ //当前值与前次值不相等说明此时按键有动作
    39. if (backup == 0){ //如果前次值为 0,则说明当前是弹起动作
    40. cnt++; //按键次数+1
    41. if (cnt >= 10){ //只用 1 个数码管显示,所以加到 10 就清零重新开始
    42. cnt = 0;
    43. }
    44. P0 = LedChar[cnt]; //计数值显示到数码管上
    45. }
    46. //更新备份为当前值,以备进行下次比较
    47. backup = KeySta;
    48. }
    49. }
    50. }
    51. /* T0 中断服务函数,用于按键状态的扫描并消抖 */
    52. void InterruptTimer0() interrupt 1{
    53. //扫描缓冲区,保存一段时间内的扫描值
    54. static unsigned char keybuf = 0xFF;
    55.  
    56. TH0 = 0xF8; //重新加载初值
    57. TL0 = 0xCD;
    58. //缓冲区左移一位,并将当前扫描值移入最低位
    59. keybuf = (keybuf<<1) | KEY4;
    60. //连续 8 次扫描值都为 0,即 16ms 内都只检测到按下状态时,可认为按键已按下
    61. if (keybuf == 0x00){
    62. KeySta = 0;
    63. //连续 8 次扫描值都为 1,即 16ms 内都只检测到弹起状态时,可认为按键已弹起
    64. }else if (keybuf == 0xFF){
    65. KeySta = 1;
    66. }
    67. else{
    68. //其它情况则说明按键状态尚未稳定,则不对 KeySta 变量值进行更新
    69. }
    70. }
    这个算法是我们在实际工程中经常使用按键所总结的一个比较好的方法,介绍给大家,今后都可以用这种方法消抖了。当然,按键消抖也还有其它的方法,程序实现更是多种多样,大家也可以再多考虑下其它的算法,拓展下思路。
    展开全文
  • 单片机之按键消抖

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

    我们前面在电子中的学习中就接触过按键,但是我们只清楚其电路图代表的含义,却不明白真正操作起来会遇到的一系列问题,由于机械触点的弹性,一个按键开关在闭合后不会马上稳定接通,断开时也不会马上断开,如果不处理的话,会导致按键识别为多下,故我们就来研究一下按键消抖(针对的是K1 K2 K3)


    实验原理

    在这里插入图片描述
    这个电路图我们在前面已经接触过,但我们现在主要来消除K1-K3的按键问题

    当按键被按下的时候,电路导通接地,I/O口为低电平;
    当按键未被按下时,电路断开,I/O口保持高电平。
    但一般的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,假如不加以处理,会导致按键被识别为按下多次。为了不产生这种现象而作的措施就是按键消抖。

    消抖方法
    • 硬件消抖
      RS触发器
      在这里插入图片描述
      这是一个双稳态电路,学过数字逻辑的小伙伴应该会明白

    • 软件消抖

    法一:使用延时
    检测出键闭合后执行一个延时程序,5ms~10ms的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。
    当检测到按键释放后,也要给5ms~10ms的延时,待后沿抖动消失后才能转入该键的处理程序

    法二:检测多次
    可以设定一个检测周期,如果在一个检测周期内,按键被检测为被按下达到了一定次数,则确认为真正被按下

    代码解析

    由于按下K1加1,按下K2减1
    初始化number为5000,显示在数码管上

    • 全局变量
    uint number;//显示在数码管上
    uint flag;//位选的位置
    int count1,count2,count3,count4;//用于消抖计数
    bit status_P,status_P2;//记录按键前一状态
    
    • 设置中断内容
    void InterruptTimer0()interrupt 1
    {
    	switch(flag)
    	{
    		case 0:P0=0;P2=weixuan[flag];P0=duanxuan[number/1000];break;
    		case 1:P0=0;P2=weixuan[flag];P0=duanxuan[(number/100)%10];break;
    		case 2:P0=0;P2=weixuan[flag];P0=duanxuan[(number%100)/10];break;
    		case 3:P0=0;P2=weixuan[flag];P0=duanxuan[(number%100)%10];break;
    	}
    	flag++;
    	
    	count1++;
    	if(key1==0)
    	{
    		count2++;          //记录K1的按下状态
    	}                        
    		
    	count3++;
    	if(key2==0)
    	{
    		count4++;         //记录K2的按下状态
    	}
    }
    
    • 主函数部分
    void main()
    {
    	Init();
    	while(1)
    	{
    		if(flag==4)//修正位选位置
    			flag=0;
    		
    		if(count1==30)//消抖大概6ms,总共统计次数30次
    		{
    			if(count2>=20)// 如果低电平有效次数为2/3以上
    			{
    				if(status_P==1)
    				{
    					status_P=0;
    					number++;
    				}
    			}
    			else
    				status_P=1;
    			count1=0;
    			count2=0;
    		}
    		
    		//同理 K2的按键消抖
    		if(count3==30)//消抖大概6ms,总共统计次数30次
    		{
    			if(count4>=20)// 如果低电平有效次数为2/3以上
    			{
    				if(status_P2==1)
    				{
    					status_P2=0;
    					number--;
    				}
    			}
    			else
    				status_P2=1;
    			count3=0;
    			count4=0;
    		}
    	}
    }
    

    代码比较的简单,其实也算是在一定时间内检测多次,即利用了软件消抖

    展开全文
  • 使用硬件方式对按键进行消抖处理,主要用于单片机,以及FPGA。
  • 前言: (开发板上四个管脚的独立按键真实存在的现象)由于机械触点的弹性作用,一个按键开关在闭合时...消抖目的:按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。键抖动会引起一次按...

    在这里插入图片描述
    前言:
    (开发板上四个管脚的独立按键真实存在的现象)由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。
    消抖目的:按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动
    在这里插入图片描述
    在这里插入图片描述
    所以怎样才能保证CPU对键的一次闭合仅仅做一次处理,因为管脚的输入(四组并行双向IO口,可以输入可以输出)只有高低电平之分,所以只要保证其相应管脚的高低电平可以保持实现理性波形时的高低电平情况就不会出现多次一次按键被多次误读;
    【补充】
    一般来讲,对于5V电源的单片机来说,低电平在1.3V以下,高电平在3.7V以上,数字信号尽量不要使用1.3—3.7V这个区间,这有可能会造成单片机无法识别或识别错误。
    【分析】
    ①上图所示,最开始状态时按键未按下时,电容肯定先是已经被充满电然后开路,又因为电容C1左极板与接地,所以左右极板电位0v/5v,又因为没有按下按键,所以电路处于开路,单片机管脚P02处于高电平5v(电路开路与VCC相连/也与电容右极板相连);
    ②当按键按下时,有一段时间的机械抖动,此时按键SW1右边节点的电位是5v(也是单片机管脚P02的电位为5v),又因为按键与电容形成回路,所以电容会进行放电(电容是一个非线性元件,放电需要时间),但是机械抖动的时间和电容放电的时间不是完全一致,所以当电容放电放一部时间(假设可能放到4.5v时)机械抖动就已经结束,那么管脚的电平却是始终都是表现为高电平,所以CPU识别还是高电平未变,当按键彻底按下稳定接触后,很快就会将放电完毕将电容短路(左右极板的电位变成0v/0v),然后按键SW1右边节点电位接地(也就是管脚P02因为接地电势为0v),所以此时单片机管脚P02为低电平,这也是教学视频说因为放电延时;
    ③当按键要松开时,按键有个松开时的抖动时间,只有你有松开的可能,按键这条支路就会开路而电容开始充电,电容就开始充电,但是充电需要时间在抖动的时间内电容右极板不会瞬间达到5v的电位,也就是按键SW1右边的节点电位不会一下子达到高电平(可能在这个抖动时间内电容才充电充了0.5v),所以该节点依然保持低电平(也就是CPU识别的管脚P02为低电平),当按键彻底松开稳定后,按键那条支路断开电容也充满电(两条支路都是开路),按键SW1右边节点和管脚的电位就是高电平(与直流电源VCC相连)这也是教学视频说因为充电延时
    【结论】
    ①具体分析也要参照实际电容充放电时间和加速度与抖动时间的比较;
    ②我上面的分析符合实际波形的高低高的波形情况;
    ③但是一般不提倡硬件消抖,会给电路增加较多电容电阻,所以选择软件消抖(软件消抖的程序实际上就是按照波形图进行实际编写的);
    在这里插入图片描述

    展开全文
  • 51单片机按键消抖

    千次阅读 2018-05-05 10:28:53
    一般处理有两种方式:一种是硬件消抖,另一种是软件消抖。硬件消抖不仅会增加开发成本,而且有时不稳定。一般都会选择软件消抖。 因为单片机最重要的是测试状态,而不是测试过程。一般不会选择通过状态延时来消抖,...
  • #51单片机#按键消抖

    2020-02-18 21:30:40
    为什么要按键消抖? 为了保证操作效果 先看一段代码: #include <reg52.h> unsigned char code LedChar[10]={//数码管显示的数字0~9 0XC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90}; sbit KEY1 = P3^3...
  • 单片机消抖方式

    千次阅读 2018-04-11 10:03:40
    单片机按键消抖方式详解通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间...
  • 硬件电路和软件方面进行按键消抖 什么是按键抖动及按键抖动产生的原因? 最近在项目中用到了机械弹性开关,这种开关在按下时候不会马上就有稳定的接通,在弹开时候也不会马上断开,在按下和弹开的瞬间会产生一...
  • 按键硬件消抖原理: 利用电容充放电特性来实现,因为电容为储能元件。它两端的电压不能出现突变,即有一个充放电的过程 下降沿触发电路: 上升沿触发电路: 什么是RC电路? STM8S手册中IO口的逻辑电平...
  • 程序参考郭天祥老师的视频开发板普中科技 HC6800 V2.8 连接方式如图所示,按下按键K1,LED D1点亮,数码管DS1显示计数,说明抖动现象。/******************************************************************/ ...
  • 按键消抖设计

    2020-07-04 22:38:49
    一般用软件实现按键消抖,在消抖之前先消除亚稳态,因为按键对FPGA来说是外部信号,先对按键信号进行同步处理,对按键信号寄存两次进行输出,同步到fpga时钟上。设计一个20ms的计数器,对按键消抖
  • 4*4矩阵键盘 按键消抖 单片机c语言 键盘扫描子程序
  • 今天学到的是利用TVS(也就是瞬态抑制二极管)和一阶低通滤波器构成的硬件消抖模块。 电路大致结构如下图所示: 众所周知,按键消抖可以用软件消抖,也就是延时函数,一般delay取值在10ms左右。 但是软件消抖有一个...
  • 按键原理和软硬件按键抖动

    万次阅读 2015-08-19 19:19:51
    系统的信号输入中,键盘因其结构简单而被广泛使用。因此,对键盘的输入(逻辑0或1)进行准确采样,避免错误输入是非常有必要的。理想的键盘输入特性如图1所示:按键没有按下时,输入为逻辑1,一旦按下则输入立刻变为...
  • 独立按键消抖单片机和FPGA中都是个不可避免的问题,首先,解释一下什么叫做按键抖动,如图,按键在按下和松开的那个瞬间存在大概20ms的机械抖动:    下面就是本篇的第一个重点 —— 什么时候需要按键消抖设计...
  • 单片机ADC采样算法----消抖滤波法

    千次阅读 2020-03-28 17:11:54
    消抖滤波法的实现步骤为:设置一个滤波计数器,将每次采样值与当前有效值比较,如果采样值等于当前有效值,则计数器清零。如果采样值不等于当前有效值,则计数器+1,并判断计数器是否>=上限N(溢出),如果计数器溢出,...
  • 8.6 单片机按键消抖

    2016-07-06 07:41:10
    通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如...
  • 由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。...
  • 本文设计了一种单片机键盘电路,比传统的矩阵键盘更能节省I/O端口,并且不需要扫描程序。所采用的消抖处理也有很大改进。
1 2 3 4 5 ... 20
收藏数 627
精华内容 250
关键字:

单片机硬件消抖