在电子产品中经常用到按键,尤其是经常需要MCU判断短按长按这两种动作,本篇我们来专门聊下这个话题。

只谈理论太无聊,我们还是结合着实际应用来说明。之前写过一篇关于《CH573第一篇:实现自拍杆蓝牙遥控器1》的文章,例子默认的功能是蓝牙连接后不断的发送数据,从而不断的拍照。而实际中的遥控器通常是按一次按键,控制一次,我们在来实现该功能。

板子上只有两个按键,一个是RESET按键,一个是DOWNLOAD按键,我们使用DOWNLAOD按键,按键的一端接GND,另外一端接CH573的PB22引脚。

原理图中有一个NC的C5,但是实际板子上我却没有找到它,可能是版本不一致。

提前说明一下:CH573的代码里跑了TMOS(Task Management Operating System),可以理解为一个简单的操作系统,所以下面的代码一般的裸机代码看着略有不同,不过核心思想都是一样的,用在其他地方也很容易移植,只需要将其中的定时器部分改写即可。

最初我是这么做的,把PB22配置为上拉输入,开启下降沿中断,在中断服务函数里,启动一个事件,执行蓝牙发送。代码如下:

void Key_Init()
{GPIOB_ModeCfg( GPIO_Pin_22, GPIO_ModeIN_PU );GPIOB_ITModeCfg( GPIO_Pin_22, GPIO_ITMode_FallEdge );PFIC_EnableIRQ( GPIO_B_IRQn );
}
void GPIOB_IRQHandler( void )
{if(GPIOB_ReadPortPin(GPIO_Pin_22)==0){GPIOB_ClearITFlagBit( GPIO_Pin_22);tmos_set_event( hidEmuTaskId, START_REPORT_EVT );}
}

这么写能工作,但是有问题,就是经常会出现按一下误判为多次按下。原因大家应该都清楚,因为按键存在抖动,所以一次按下有可能进入多次进入中断。

理想中的按下-弹起波形是这样的:

但是实际由于按键抖动的存在,实际的波形可能是这样的:

不信的话你可以接上示波器看看,或者软件验证,比如在GPIO中断服务函数里,设置一个全局变量,让它每次进入中断后加1,按按键观察这个变量的值。

那么该如何消除抖动呢?一种方法是硬件消抖,即按键两端并联一个小电容(电容大小由按键的机械特性来决定),另外一种方法是我们今天要重点介绍的软件消抖。

方法一:常用的加延时函数

在中断服务函数中加一个比如10ms的延时函数,延时时间的长短取决于实际所用的按键特性,只要延时时间比抖动时间略大即可。原理很简单,加了延时就避开了抖动的这段时间,在延时之后判断引脚电平,如果为低电平就表示是按下。

void GPIOB_IRQHandler( void )
{if(GPIOB_ReadPortPin(GPIO_Pin_22)==0){mDelaymS(10);if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)tmos_set_event( hidEmuTaskId, START_REPORT_EVT );GPIOB_ClearITFlagBit( GPIO_Pin_22);}
}

这个方法很简单,但是不好的地方是延时占用MCU资源。尤其是这里的BLE应用,在中断服务函数中执行时间长会引起蓝牙连接中断,所以这里不能这么用,我实际测试当按键按快一点就很容易引起蓝牙连接中断。

方法二:加定时器

它的原理和方法一类似,只不过是不在中断服务函数中阻塞等待,而是用一个定时器,代码如下:

void GPIOB_IRQHandler( void )
{if(GPIOB_ReadPortPin(GPIO_Pin_22)==0){GPIOB_ClearITFlagBit( GPIO_Pin_22);tmos_stop_task(hidEmuTaskId, START_DEBOUNCE_EVT);tmos_start_task(hidEmuTaskId, START_DEBOUNCE_EVT,16);}
}
if(events & START_DEBOUNCE_EVT){if(GPIOB_ReadPortPin(GPIO_Pin_22)==0){PRINT("short press\n");tmos_set_event( hidEmuTaskId, START_REPORT_EVT );}return (events ^ START_DEBOUNCE_EVT);}

它的逻辑是每次抖动的下降沿重新开启10ms定时器,在定时器时间到之后判断IO电平状态来判断按键是否按下。

需要注意的是:10ms定时器不是一个周期性的定时器,它是一次性的,即时间到了之后就停止计时了。另外每次进中断后先让定时器重新重头开始计时。如果大家用其他代码实现时要注意这两点。

此方法的好处不像加延时函数那样占用MCU资源。我实际测试这个方法可用,不会引起蓝牙连接中断。

以上介绍了使用中断的方式来判断按键短按,可以看到它判断的依据是按键按下(由高电平变到低电平)这个状态。下面在方法二的基础上我们来实现长按的检测,判断长按的依据是按下后持续的维持一段时间低电平。代码如下:

if(events & START_DEBOUNCE_EVT)
{if(GPIOB_ReadPortPin(GPIO_Pin_22)==0){PRINT("short press\n");tmos_set_event( hidEmuTaskId, START_REPORT_EVT );tmos_start_task( hidEmuTaskId, START_LONGCHECK_TIMER,16 );}return (events ^ START_DEBOUNCE_EVT);
}
if(events & START_LONGCHECK_TIMER){static int cnt=0;if(GPIOB_ReadPortPin(GPIO_Pin_22)==0){cnt++;if(cnt>100){PRINT("long press\n");tmos_stop_task( hidEmuTaskId, START_LONGCHECK_TIMER);cnt =0;}elsetmos_start_task( hidEmuTaskId, START_LONGCHECK_TIMER,16 );}else{cnt=0;tmos_stop_task( hidEmuTaskId, START_LONGCHECK_TIMER );}return (events ^ START_LONGCHECK_TIMER);}

实现的逻辑是:当检测到短按时,再开启一个10ms定时器,在定时器到时之中判断电平状态,如果为低电平,就让cnt变量加1,否则cnt=0,当cnt>100,即低电平持续1s认为是长按。我在这里当判断到长按之后或者IO变高之后会停止掉这个定时器,否则周期定时,因为没必要一直开着定时器。

除了上述的中断方式,还可以使用轮询的方式来实现,代码如下:

void Key_Init()
{GPIOB_ModeCfg( GPIO_Pin_22, GPIO_ModeIN_PU );
}
if(events & START_KEYSCAN_EVT)
{KeyScan();tmos_start_task(hidEmuTaskId, START_KEYSCAN_EVT,160);// 100ms执行一次KeyScan()return (events ^ START_KEYSCAN_EVT);
}
bool key_press_flag = false;      // 按下标志
bool key_long_press_flag = false; // 长按标志void KeyScan()
{if(GPIOB_ReadPortPin(GPIO_Pin_22) == 0) // 低电平{if(key_press_flag == false)tmos_start_task( hidEmuTaskId, START_LONGCHECK_TIMER, 1600 ); // 启动1s定时器key_press_flag = true;    // 置位按下标志}else if(key_press_flag == true) // 高电平同时按键被按下过 ,表示是按下后的弹起{key_press_flag = false; // 清除按下标志if(key_long_press_flag == false)// 短按后的弹起{tmos_stop_task(hidEmuTaskId, START_LONGCHECK_TIMER);PRINT("short press\n");tmos_set_event( hidEmuTaskId, START_REPORT_EVT );}else // 长按后的弹起{key_long_press_flag =false;}}else{key_press_flag = false;key_long_press_flag = false;}}
if(events & START_LONGCHECK_TIMER)
{key_long_press_flag =true;PRINT("long press\n");return (events ^ START_LONGCHECK_TIMER);
}

上面的这段代码初次看着有点绕,但是看明白了之后会觉得这个实现逻辑还是挺好的,注释写了,这里不再详细解释了,我在多个项目里使用的都是它。它兼顾了去抖和短按/长按的检测,并且长按可以判断出长按按下/长按弹起。短按是检测到弹起时认为是短按动作。另外如果想同时支持多个长按,也很方便添加。

轮询和中断各有优缺点,大家可以根据实际情况来选择,你一般常用哪种方式呢?

如何实现按键的短按、长按检测?相关推荐

  1. 51单片机——矩阵按键逐行扫描短按长按一直按方案1.2

    1.删减了<51单片机--独立按键.矩阵按键多种方案1.1>里的一些不怎么用的代码. 2.添加了逐行扫描按键(需要定时器20毫秒配合使用). 3.支持按键短按.长按.一直按,代码里只做了短 ...

  2. 51单片机学习笔记:基于状态机的按键对时程序(短按,长按,连发)

    之前的电子钟程序中,用的按键消抖处理方法是10ms的延时,这种方法效率比较低 所以现在利用状态机原理重写一下,效率很高啊 4个独立按键中用到3个, keys5用于切换对时分秒等状态,keys2是减小数 ...

  3. STM32超级简便的按键代码 只需三行 可实现短按+长按

    [蓝桥杯]STM32三行按键详解 长按 短按 用的是国信长天的嵌入式方向的开发板,使用的芯片是STM32F103RBT6,基于stm32f1的固件库编程. 当初写下这篇博客的本意也是让自己的知识更加巩 ...

  4. 乐鑫Esp32学习之旅⑦ esp32上利用GPIO中断做一个按键的短按和长按的回调事件,再也无须担心触发源。(附带Demo)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. 爬坑学习新旅程,虚拟机搭建esp32开发环境,打印 " ...

  5. 按键的拓展:长按短按 (2)

    上一篇文章中,按键的长按短按是按照 短按--第一次长按--第二次长按,按顺序来的. 但是今天重读考题发现,考察的不是按顺序先短按,后长按一个一个来:而是对按下时间长短有一个判断:比如说:只要按下按键, ...

  6. 【按键】短按,长按,按键释放,三种模式的按键扫描程序(软件消抖动)--- 矩阵键盘

    请先阅读上篇: 短按,长按,按键释放,三种模式的按键扫描程序(软件消抖动) 上面的程序适用于单个按键,那是不是也可以适用于矩阵键盘呢? 答案是肯定的. 接下来在这里做一个简单的扩展,具体框架不用改变, ...

  7. 【按键】短按,长按,按键释放,三种模式的按键扫描程序(软件消抖动)

    先来说一下这三种模式的意思: 1. 短按模式:单击按键时,返回一次有效按键值:长按时也只返回一次有效按键值.这样可以有效地排除因不小心长按带来的返回多次有效按键,进而执行多次按键处理程序. 2. 长按 ...

  8. 【按键你不知道的那些事】简单几句话实现按键按下下、长按、抬起、单击、双击动作

      闲来没事整理一下文档.这一篇我们来讲一讲按键的各种触发事件.   下面我给大家简单介绍一下有哪些按键检测的方式. 方式1 阻塞式检测按键 void KeyScan(void) {if(HAL_GP ...

  9. [Android开发] 从后台恢复前台界面需要输入密码的demo|监听Home短按长按锁屏

    一.效果图 二.实现原理 通过接收系统广播来判断home按键,广播Application全局,在接收到广播的时候在BaseActivity的onStop里面打开输入密码的对话框,再次回到界面就已经是显 ...

最新文章

  1. 用PHP控制Nagios进程
  2. python系列------计算机运算过程
  3. Java内存管理之软引用(Soft Reference)
  4. js ajax java传参_ajax参数传递与后台接收
  5. html vbs 输入框,HTML_vbs实现的下拉框对应键入值,vbs实现的下拉框对应键入值 - phpStudy...
  6. 基于leveldb,levigo做二次开发
  7. bool类型数组转换成一个整数_Go 学习笔记 02 | 基本数据类型以及 byte 和 rune 类型...
  8. 为 ASP.NET 创建缓存配置对象[转载]
  9. mfc调取摄像头显示并截图_用OpenCV在MFC Dialog中Picture控件上显示摄像头采集实时视频...
  10. 开涛spring3(4.4) - 资源 之 4.4 Resource通配符路径
  11. nginx下apk下载,ie9的问题
  12. 炼丹中遇到的一些BUG
  13. 游戏动作3d模型素材推荐 精品 小众
  14. linux GSM0710(2)
  15. 金融计算器--麦考利久期(Macaulay_Duration)
  16. Android基站定位详解
  17. David Silver强化算法学习地址和PPT
  18. 眼中体:大家眼中的IT男
  19. PHP phpoffice/phpspreadsheet导出excel
  20. 小米6鲁大师html5评测,小米6跑分超110万?鲁大师官方:网友PS的图片

热门文章

  1. 山西大同大学计算机分数线,山西大同大学录取分数线2021是多少分(附历年录取分数线)...
  2. Java8新特性之Joining
  3. gt; 和 lt; 代表大于号gt; 和小于号lt; 以及其英文的全称
  4. STM32系统时钟详解
  5. ctfshow sql注入 web171-web253 wp
  6. 服务器运维1-failed to start LSB
  7. 2017年1月15日 星期日 --出埃及记 Exodus 22:5
  8. HDU1116 Play on Words——欧拉路(有向图+并查集)
  9. Redis key前缀的设计与使用
  10. Teach repeat replan 安装中遇到的问题记录