一种轮询式长短按按键识别程序

2020寒假期间原本想要絮絮叨叨一点关于前阵子一个项目里使用的按键处理算法,但是写了这么点又搁置了。暂时没有整理的想法,所以可能看上去有点乱。。。还是先丢出来吧,看日后有心思了再补充一点。

这是一种轮询式的按键扫描程序,其通过定时扫描的方式,反馈是否有按键被按下,并反馈其按下方式为长按还是短按.

目录

0 - 设计背景

1 - 需求分析

1.1 - 短按的单次与连续

1.2 - 长按与按住

2 - 算法流程图

3 - 代码


0 - 设计背景

在电路板上,使用一块51内核的MCU作为主控,其在P2端口连续连接了4个独立按键,原理图如下:

这里需要主控识别这四个独立按键分别的是否被按下,以及按下是短按的形式还是长按的形式。

1 - 需求分析

我们先来看一下这里我们需要达成怎样的效果。由于选用的是简单的51内核的mcu,不准备在其上跑复杂的程序以及实时系统,这里就希望将按键扫描程序做成轮询式非阻塞的方式,通过调用该查询函数,可以完成一次按键扫描,通过函数的返回值,我们可以知道当前是否有按键被按下,如果有按键被按下,那么被按下的是哪个按键,按键以什么方式被按下。

1.1 - 短按的单次与连续

每一个按键我们需要识别两种按压方式,短按以及长按,其他的不需要了。那么,在当前的轮询方式下,怎样的按键扫描结论可以归纳与按键短按,怎样的归纳与长按呢?细究的话有很多方式,例如模糊处理,但是简单起见,在这里就只使用最为简单的阈值判定方法就可以了。

即在连续的多次扫描结论里,我们发现按键为被按下的电平超过a次,但是在超过b次之前按键被松开来了,这样可以判定为一次短按的发生。在这里,根据需求,a与b是可以选择的数,这两个数的选择既影响了你 “按键的手感”,也影响着你按键的灵敏度。过于灵敏,可能会将一些外界干扰的信号识别为一次用户短按,造成 “虚假的触发”。例如在pcb设计较差时,外界的电磁干扰可能被耦合到你的按键引脚线路上;又或者,一次意外的按键抖动。

当然,这里a与b的设置也需要根据你设置的扫描周期进行修改,或者说,根据你实际期望的短按判别时间来。我们先来看一下正常的一次短按事件的时序图(这里我用的WaveDrom来绘制的):

上图中:

clk可以视为一个实际时钟。

chk代表了你程序查询按键,高电平代表其在查询。

btn1是你的一个按键,这里高电平表示被按下。

首先要说明的是,这里的之间可能比例不太确切,毕竟如果将clk分太细,可能这个图片得很长,理解一下意思就可以了。。

你的按键程序的查询周期并不是你总体程序的运行周期,因此当你需要计算按键的“手感”时,你应当计算我的按键程序多久进行一次扫描,然后再根据需要的短按手感(即按下松开这段时间为多久)再来确定你的阈值a。例如,我计算后发现,我想要的短按手感,在1~3次按键扫描时都连续发现了按键被按下都是可以接受的,那么我的阈值a可以设为1,阈值b设为3。上图中,在连续的4次扫描期间,有两次连续的发现按键被按下,那么在第四次按键扫描时发现按键被松开时,我就可以汇报发生了一次按键短按了。

短按事件是相对来说比较好分割的,因为其要求在短按事件发生前的一个扫描,必须为未有按键按下;而在按键被短按事件末尾,必须有按键被松开,不然就可能可以算作长按了。正是由于这里短按的逻辑判定,是必须要求末尾的下降沿存在的,所以这种短按事件是末尾触发,而末尾触发意味着短按事件的抛出时,用户是已经结束了按键操作,按键此时必定是松开的,所以短按的连按操作并不需要特别的判定方式。

但是,这种短按操作是有一个问题的,若你的用户是飞快的进行连续短按操作的话,你的程序会进行误判,例如下面这样:

当用户的松开与按下速度超过你的程序扫描周期,而在上例中,你的短按阈值只为1,长按判据为2的话,你的程序会将用户这样的 光速 短按连按识别为一次长按了。当然,你也可以不使用查询去读取按键状态的方式,而是使用按键的上升与下降沿来进行处理,然后通过中断函数进行判别,不过这并不属于我们当前的讨论范围了。这样的情况不能说不存在,但是毕竟一般的按键扫描程序周期都为10ms左右,如果你的用户能做到5ms内完成按键的松开与按压,这里建议去代为申请吉尼斯世界纪录。

同样的,也不建议将短按阈值a设置的太低:

如上图,前面一次的按键脉冲实际上是一次短暂的误触发,而若你的短按阈值设置的太低,可能会将这一次误触发识别为一次短按事件的发生,而接连起来,则识别为了两次短按的连续发生。

1.2 - 长按与按住

长按相比短按其实差别不大,简单来看,长按只是单纯的将短按的判别时间增长了。

长按是一个很暧昧的存在,尤其在你的程序中需要判别长按的单次与连续发生时,但是长按的连按又是部分场景。用户并不知道“按多久”可以触发一次长按,”再按多久“可以触发长按的连按。在连续的多次扫描结果里,我们发现按键为被按下的电平超过了b次,那么当前肯定是处于长按,我们可以反馈回当前发生了一次长按。

有的时候,用户想要连续的触发,如果是连续的短按,感觉其实并不需要特定的去判别,毕竟短按事件的发生其要求就是在某一段连续查询之中,发生了按键电平从未触发,到触发并保持一小段a时间,然后松开。这样子,短按与短按之间是有明显的事件分割的。

2 - 算法流程图

算法的扫描流程图如下:

第一层,查看当前是否有按键被按下。在这里,KEY_MASK代表的是所有按键未被按下的默认状态,每一次读取按键端口可以获得一个状态,通过判定是否为KEY_MASK来判定是否有某个按键被按下。

第二层,这里引入了 lastKey 静态变量,该变量存储的是上一次扫描中,得到的按键结果,可以是某一按键值,或者若上一次扫描未能得到成立的按键结果,则存储的是 KEY_MASK。在左侧的第二层左侧,若本次按键状态为KEY_MASK且lastKey也为KEY_MASK,则直接返回KEY_MASK,即表明没有按键被按下。

之后引入了 press_count 静态变量,该变量存储的是同一按键的在连续扫描中被连续发现的次数,当扫描结果出现变换时,该变量会被清空。同时,引入了 正在长按 静态变量,该变量标识上次识别到了一个长按动作,且未结束长按。该标识符在检测到当前按键状态为KEY_MASK后,会被清空。

3 - 代码

key.h文件

/*
*    8E=142 4E=78
*    8D=141 4d=77
*    8B=139 4B=75
*    87=135 47=71
*
*   129=81    65=41
*   130=82    66=42
*   132=84    68=44
*   136=88    72=48
*/
#ifndef _KEY_H_
#define _KEY_H_

#define KEY_PORT    P2
#define KEY_MASK    0x0F
#define KEY_NONE    0x00

#define KEY_MENU    0x01
#define KEY_ENTER   0x02
#define KEY_UP      0x04
#define KEY_DOWN    0x08

#define KEY_LONGTHR 100       //按键长短判定阈值,10ms扫描情况下按住1000ms算长按

#define KEY_SHORTPRESS   0x80   //短按
#define KEY_LONGPRESS    0x40   //长按

unsigned char KeyScanPolling();

#endif

key.c文件

#include "key.h"
#include "../stc15f2k60.h"
#include "../common.h"

/*
*   轮询调用按键扫描
*   反馈按压方式及按键类型的或
*   按下低电平
*   none none up up none none up up none none up up up up up noen none none up none
*/
u8 KeyScanPolling()
{
    static u8 _lastKeyVal = KEY_NONE;
    static u8 _lastKeyCount = 0;
    static u8 _isLastKeyLongPress = 0;
    u8 _keyValue,_returnVal;

    if((KEY_PORT & KEY_MASK) != KEY_NONE)
    {
        //消抖
        DelayMs(10);
        if((KEY_PORT & KEY_MASK) != KEY_NONE)
        {
            /*  有按键按下  */
            _keyValue = (KEY_PORT & KEY_MASK);
            if(_lastKeyVal == _keyValue)
            {
                _lastKeyCount++;
                if(_lastKeyCount > KEY_LONGTHR)
                {
                    _lastKeyCount /= 2;
                    _isLastKeyLongPress = 1;
                    _returnVal = _lastKeyVal | KEY_LONGPRESS;
                }
                else
                {
                    _returnVal = KEY_NONE;
                }
                
            }
            else
            {
                if(_lastKeyVal == KEY_NONE)
                {
                    _lastKeyVal = _keyValue;
                    _returnVal = KEY_NONE;
                }
                else
                {
                    _lastKeyCount = 0;
                    _returnVal = _lastKeyVal | KEY_SHORTPRESS;
                    _lastKeyVal = _keyValue;
                }
                
            }
            return _returnVal;
        }
        /*  抖动    */
        return KEY_NONE;
    }
    else
    {
        /*  无按键  */
        if(_lastKeyVal == KEY_NONE)
        {
            _returnVal = KEY_NONE;
        }
        else
        {
            _lastKeyCount = 0;
            if(_isLastKeyLongPress != 0)
            {
                /*  上次键是长按,当前松开,返回空键值  */
                _lastKeyVal = KEY_NONE;
                _returnVal = KEY_NONE;
                _isLastKeyLongPress = 0;
            }
            else
            {
                /*  返回上次键短按  */
                _returnVal = _lastKeyVal | KEY_SHORTPRESS;
                _lastKeyVal = KEY_NONE;
            }
            
        }
        return _returnVal;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值