最好的按键扫描和消抖方法,适用于复合、长按、按下或抬起响应按键

刚参加工作的时候,看了一些同事采用的按键扫描和消抖方法,对比学校里和网上查到的按键处理,发现觉得不尽善尽美,有以下几点:

1. 消抖复杂,效率低。有人直接在电平判断后使用delay()函数,进行消抖,耽误时间;有人在按键电平中断中进行消抖和处理,导致其他的服务反应慢,不适合做实时系统;

2. 许多功能在不同界面下是不同的,把按键处理在中断进行,导致分支很多,业务流不清晰。

3. 特殊功能按键的处理麻烦。在需要长按作为特殊按键、复合按键响应、复合按键长按响应的时候,需要增加很多的标志位,反复使用if..else判断,流程看起来很乱。

4. 跟硬件设计或业务关联很深,不便于移植和修改,导致每个项目都要更改一次。

想了很久之后,我结合PC的键盘处理方法,编写了自己的按键函数,经过几次修改,定了下来。这十多年来,无论更换单片机,还是采用端口/扫描方式,还是采用前后台或操作系统,都一直在用,方便移植,也比较清晰。

/****/

它主要有几个特点:

  1. 按键扫描和取值分开。

    在中断中,每隔10ms调用keyScan()进行按键扫描,多次扫描进行消抖,获得的按键值不返回,作为消息放到全局变量中;

    在业务层需要判断的地方使用getKeyValue()获取当前的键值,进行处理。

  2. 每一个按键,都有单独的标志位和计时变量。

    消抖计时:

    每调用一次10ms中断,如果按键按下,gucKeyOkTimer(以OK按键为例)增加;
    gucKeyOkTimer超过消抖的阀值(我一般10次,即100ms),则确认有按键了。
    
    任何一次扫描到按键没有按下,gucKeyOkTimer清零,重新开始;

    标志位:

    如果按下的电平时间超过阈值,一直按着,会有gfOkPressing的标志,表明按键一直有效中;
    
    如果按下过一次,需要响应,会有gfOkNeedAck,这个标志只置位一次;
  3. 复合按键的响应:

    因为每个按键,都有自己的标志位和计时变量。复合按键的判断,使用多个按键pressing的标志判断是否有效。同样每个复合按键有自己pressing的标志,和NeedAck的标志;
  4. 长按键的响应:

    按键超过指定时间,则作为新的按键,也会有pressing标志,和NeedAck标志。

我没有使用怪癖诡异的编程方法。有很多取巧的方法可使实现按键的扫描,甚至有人写了三行代码就实现消抖。——我个人不喜欢这样的程序风格。我喜欢思路清晰的编程方法,易于维护和移植。当然代价就是多了一些ROM和RAM占用,但我觉得时间和代码的质量更重要。

如果你跟我的思路相同,也遇见过这样的困惑,可以考虑我的按键扫描方法。

/**硬件说明**/

这是个常用的按键定义,四个按键:上、下、确认、取消;长按确认为开关机按键;开机后同时按下上下按键,为菜单按键。

/*****软件代码**/

首先是按键扫描,需要每10ms调用一次,在使用STM32的系统中,可以直接使用SysTick,累积10秒调用一次按键扫描函数。

在void SysTick_Handler(void)中,添加以下代码:

    //key sacn, each 10ms
    giKeyScanTimer++;
    if(giKeyScanTimer>=10)
    {
        giKeyScanTimer=0;
        keyScan();
    }

在按键扫描文件key.c中,以下为按键端口的宏定义。项目使用了HAL库,但为了节约时间,端口扫描直接调用了GPIO寄存器。

#define PORT_KOK        ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR4)
#define PORT_KUP        ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR5)
#define PORT_KDOWN      ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR6)
#define PORT_KCANCEL        ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR7)

按键扫描需要的变量。因为使用的STM32的RAM较大,所以标志位直接用uint8_t,在RAM紧张的地方,可以改为位定义。

uint32_t gucKeyOkTimer, gucKeyUpTimer,gucKeyDownTimer, gucKeyCancelTimer, gucKeyMenuTimer;  //按键消抖需要的扫描计时器
uint8_t gfOkPressing, gfOkNeedAck;        //OK按键的按下标志、需要响应的标志
uint8_t gfUpPressing, gfUpNeedAck;    //UP按键的按下标志、需要响应的标志;  
uint8_t gfDownPressing, gfDownNeedAck;    //DN按键的按下标志、需要响应的标志;
uint8_t gfCancelPressing, gfCancelNeedAck;  //CANCEL按键的按下标志、需要响应的标志;
uint8_t gfMenuPressing, gfMenuNeedAck;      //MENU按键(同时按下UP、DOWN)的按下标志、需要响应的标志;
uint8_t gfONOFFPressing, gfONOFFNeedAck;    //ONOFF按键(按下OK超过3秒)的按下标志、需要响应的标志;

以下为keyScan函数,我将1个按键、1个长按按键、1个复合按键的代码完整copy下来,其他的不占用篇幅了。

//Key scan time, based on 10ms
#define KEY_100MS       10
#define KEY_200MS       20
#define KEY_500MS       50
#define KEY_1S          100
#define KEY_2S          200
/*********************函数说明*********************
函数作用:按键扫描函数
注意事项:每10ms被中断调用一次,判断是否有按键按下
         消抖时间:100ms
**********************************************/
void keyScan()
{
  //OK key
  if(PORT_KOK==0)
    {
      gucKeyOkTimer++;
      //100ms消抖后,确认需要处理
      if(gucKeyOkTimer>KEY_100MS)
        {
          //gfOkPressing代表这个按键一直被按下中
          gfOkPressing=1;
          //确认按下后,置待响应标志,这个标志只置一次,防止业务流重复处理
          if(gfOkPressing==0)
            gfOkNeedAck=1;
        }
      //如果连续按下1s,则为ONOFF按键,同样有pressing标志,和needack标志
      if(gucKeyOkTimer>KEY_1S)
        {
          gfONOFFPressing=1;
          if(gfONOFFPressing==0)
            gfONOFFNeedAck=1;
        }
    }
  else
    {
      //如果没有被按下,定时器、pressing标志都清零。needack标志不能清。
      gucKeyOkTimer=0;
      gfOkPressing=0;
      gfONOFFPressing=0;
    } 

  //Up key ...
  //Dn key ...
  //Cancel key ...
  //三个按键的处理方法相同,只是没有长按的处理。

  //如果UP和DOWN按键同时按下超过1秒,则为Menu按键;
  if(gfUpPressing&&gfDownPressing)
    {
      gucKeyMenuTimer++;
      if(gucKeyMenuTimer>KEY_1S)
        {
          gfMenuPressing=1;
          if(gfMenuPressing==0)
            gfMenuNeedAck=1;
        }
    }
  else
    {
      gucKeyMenuTimer=0;
      gfMenuPressing=0;
    } 
}

在业务流的程序处理中,调用getKeyValue()获得有效键值。一般是在某个界面的loop中。

/*********************函数说明*********************
函数作用:根据扫描结果,返回按键值
注意事项:需要判断按键的时候,调用此函数
**********************************************/
uint8_t getKeyValue()
{
  if(gfUpNeedAck) 
    {
      gfUpNeedAck=0;
      return KEY_UP;
    }

        ... ...

  if(gfMenuNeedAck)
    {
      gfMenuNeedAck=0;
      return KEY_MENU;
    }

  if(gfONOFFNeedAck)
    {
      gfONOFFNeedAck=0;
      return KEY_ONOFF;
    }

  return KEY_NONE;
}

当然,在进入某个界面前,需要清空一下按键标志,否则在上一个界面没响应的按键会影响下一个界面:

/*********************函数说明*********************
函数作用:清空按键缓冲区
注意事项:
**********************************************/
void flushKeyBuf(void)
{
  gfUpNeedAck=0;
  gfDownNeedAck=0;
  gfOkNeedAck=0;
  gfCancelNeedAck=0;
  gfMenuNeedAck=0;
  gfONOFFNeedAck=0;
}
OK了,这篇文章我在51hei发表过,但是没有说得这么详细。

/**写在后面**/

有几个特殊的按键处理要求,我简单收一下:

  1. 是按下响应还是抬起响应。

    业务要求不一样,就会有不一样的要求。以上代码是按下响应的,如果需要抬起响应,就在if(PORT_KOK==0)的代码里不处理needack标志。在else分支里面,如果抬起之前pressing是置位的,那就置位needack。

  2. 先后顺序,或连击多少次的密码操作。

    建议还是放在业务流里面吧,没必要在按键扫描里面处理。

  3. 一个按键按不同时间,进行不同提示进入不同隐藏功能。

    这个情况下不建议再keyscan中进行处理了,因为可能会先处理按键时间短的功能。请在业务流直接判断pressing的时间吧。

  4. 按键行列扫描。

    很容易改动,把PORT_KOK==0改动一下即可。

  5. 时间问题

    10ms扫描一次,100ms消抖不是必须的,你可以根据自己的时基进行修改。

/**/

其他未尽说明,欢迎大家在下面留言,互相交流。

转载于:https://blog.51cto.com/13719208/2107561

  • 0
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目介绍 1、方案主题:基于STM32+MPU9250的重力感应游戏控制器 2、方案功能:使用STM32CubeMX配置了Nucleo-L476RG,开发了USB HID复合设备用于体感游戏控制,板载STM32L476RG ARM核微控制器支持USB2.0 全速设备,通过I2C读取九轴姿态传感器-MPU9250(三轴加速度、三轴陀螺仪、三轴磁力计),使用高效的传感器数据融合算法、姿态解算算法,实现体感控制PC游戏,精度高,超低延迟,可作为虚拟自行车等控制器,支持硬件定制。 硬件:带USB控制器的STM32都可以,我这里使用的是Nucleo-L476RG,芯片是STM32L476RG,支持USB2.0全速模式,其中 PA12---->USB_DP PA11---->USB_DM 软件:使用STM32CubeMX配置 直接用USB线连接到电脑USB即可,按照USB规范,USB线里面的绿线为DP(D+),白线为DM(D-),全速设备是要在DP线加上拉1.5K电阻的,由于STM32的USB外设内部有这个电阻,因此无需外加上拉电阻。 配置过程不解释了,查看附件的PDF文档即可 工作原理介绍 1、硬件组成与介绍: Demo板采用了Nucleo-L476RG板卡,板载STM32L476RG高性能ARM Cortex-M4内核MCU,主频高达80MHz,1MB Flash、128KB SRAM,支持浮点DSP;8个轻触按键连接到了STM32的GPIO;MPU9250采用GY-91模块,通过I2C接口连接到STM32,MPU9250由应美盛(InvenSense)出品,是MPU6050的升级版,第二代9轴组合传感器将6轴惯性测量单元(三轴加速度计+三轴陀螺仪)和三轴轴磁力计集成于3 mm x 3 mm QFN封装中。 2、此项目中STM32的功能: 通过I2C接口采集传感器数据;强大的运算性能和浮点DSP的支持保证了数据融合、姿态解算等算法的高效、准确运行;控制LED作为状态指示;扫描按键状态;通过USB接口与PC机通信。 3、软件流程介绍: 采用前后台系统,主要分为三大部分,一是外设初始化,二是数据处理,三是业务逻辑处理。使用STM32CubeMX配置生成了USB HID复合设备,是一个USB键盘和USB鼠标、2个游戏Pad,配置方法和测试工程源码点我。或者“相关文件”直接下载 如下图,可看到USB枚举成功为键盘、鼠标、游戏控制器复合设备。 程序流程图如下: 实物与演示 1、实物图片与说明: Demo板说明:Demo板配置了9个按键,Nucleo板载蓝色按键为模式切换按键,开机板载LD2绿色LED快闪表明此时为锁定状态,控制器不起作用,短按1下,切换到按键控制模式,LD2慢闪,再短按切换到体感控制模式,LD2 1s闪烁一次,再按切换到锁定状态;板载4个大按键,依次映射为WASD,4个小按键依次映射为R、shift、鼠标左键、鼠标右键。 2、演示视频: QQ飞车测试: https://player.youku.com/player.php/sid/XMjUyOTY3ND... 坦克世界测试: https://player.youku.com/player.php/sid/XMjUyOTY5NT...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值