如何实现按键的短按、长按检测?

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

baa6e2bd036e16f2feaa976d4cb34a4c.jpeg

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

f04096631b40a7c67ae5e9f2014cb9a1.png

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

083d67369ee16c378360bac1239e34be.png

原理图中有一个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 );}
}

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

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

cfb4d2e19ddb84e9799b1fc86048ad87.png

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

464ca3426a0cbe8643d08e92bb126d0f.png

不信的话你可以接上示波器看看,或者软件验证,比如在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);
}

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

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

35e23e36bfe7a762a51ae97afd8bd187.jpeg

b9164bc7017b3708aa5cd56503e42107.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/463840.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

博士也会毕业吗?

怪我读书少,一直以来我都认为博士是一种很神奇的物种,他们能学会很多我们学不会的东西,那些奇奇怪怪的数学题,还有那种要背上几天的思想政治课文,以及要听懂那种像鸟儿一样呼唤的讲课,所以,我一…

国外出差见闻之印度

前段时间到印度的马德拉斯市出差了3个月(4月到6月),今天终于有时间将自己的出差心得和见闻记录下来,可能以后就会忘记。下午16:00时在深圳坐车到香港机场,上车没多久会让填一个入境香港的单子,然后是过海关,过海关时会…

雷军的演讲以及产品发布

8月11号是小米的发布会,还有雷军的年度演讲。因为工作冲突我没看直播,晚上回来看了公众号文章和知乎上的内容讨论,也看了发布的新产品。雷军那个年代能够做上程序员一定是非常牛逼的人,而雷军是这些牛逼人的公司总经理&#xff0c…

轻松理解UML用例图时序图类图的教程

摘自https://zhuanlan.zhihu.com/p/29874146 写在前面 当你老大扔给你这样的图,或者你需要完成某些功能而去看文档的时候发现以下类似这样的图会不会不(一)知(脸)所(懵)措(逼&#x…

[Winodows Phone 7控件详解]控件拾遗

1.Panorama控件和Pivot控件前面讲过,没有必要再重复一遍了。参见我的博文:http://www.cnblogs.com/DebugLZQ/archive/2012/03/19/2406284.html 2.DeepZoom DeepZoom 是silverlight的特色功能之一,也同样被加到了windows phone 7中来。这个功能…

这个工具替代Notepad++,我很满意

用过notepad的人并且还一直坚持使用它的人一定觉得它是一个非常优秀的软件,这个软件用来看日志,看代码和文档非常方便,而且里面还集成了一个HEX分析的工具,当然还有列模式等等。不吹牛啊,很多软件只做到了功能&#xf…

Kubectl 部署有状态应用(下)

接上文 《Kubectl 部署有状态应用(上)》创建完StatefulSet后,本文继续介绍StatefulSet 扩展、更新、删除等内容。 StatefulSet 中的 Pod 验证序数索引和稳定的网络身份 StatefulSet 中的 Pod 具有唯一的序数索引和稳定的网络身份。 查看 …

收集的50家国产MCU信息

全球MCU市场多为欧美、日本和台湾地区企业占据,仅欧企恩智浦、美企Microchip、美企ST、欧企英飞凌就占据超80%的份额,TI、Nuvoton、罗姆、三星、东芝五家企业占据11.4%,而中国大陆企业所占份额6.5%不到。这意味着,留给中国本土供应…

Silverlight实用窍门系列:63.Silverlight中的Command,自定义简单Command

在Silverlight中的MVVM模式下将前台页面和ViewModel界面交互分离开是通过本节所要讲述的Command实现的。我们自定义一个Command需要继承于ICommand接口并且实现这个接口。它有CanExecute()、Execute()方法和CanExecuteChanged事件组成。 CanExecute():判断是否继续执…

赋值运算符和拷贝构造函数的区别与联系

转载:http://blog.csdn.net/hebbely/article/details/65437510 简述: C中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法。拷贝构造函数使用已有的对象创建一个新的对象,赋值运…

硬件有没有35岁危机?

大家好,我篇文章是我的朋友记得诚写的,分享给大家!35岁危机指的是,很多企业停止招聘35岁及以上的员工,甚至会裁掉年纪大的人。我们经常说程序员,说互联网从业者有35岁危机。这个危机一方面是自己的焦虑&…

强大的独立日期选择器(date picker)插件 - Kalendae

日期:2012-4-16 来源:GBin1.com 在线演示 本地下载 今天分享一个独立的日期选择插件Kalendae,Kalendae是 一个强大健壮的独立日期选择器。如果你不想使用重量的jQuery UI类库的话,这个插件肯定是一个不错的备选。Kalendae包含了…

35家名企嵌入式/软件秋招岗位等你来!

大家好,我是写代码的篮球球痴。前两天一个小伙伴在微信让我帮忙推荐嵌入式招聘岗位。我实话实说,是有认识的人让我推荐简历的,但是相对于专业的招聘网站,我手里能拿出来的岗位实在是少之又少,专业的招聘网站会对不同的…

尝试梳理下ARM处理器的发展历史

大家好,这篇文章是我的朋友Michael Yao写的,我觉得非常不错,分享给大家。1. 前言本文尝试简单梳理下ARM处理器的发展历史、架构的演进,包括不同处理器的应用方向,但我们重点还是围绕Cortex-A系列展开,也会介…

linux下的CPU、内存、IO、网络的压力测试

一、对CPU进行简单测试: 1、通过bc命令计算特别函数 例:计算圆周率 echo "scale5000; 4*a(1)" | bc -l -q MATH LIBRARY If bc is invoked with the -l option, a math library is preloaded and the default scale is set to 20.…

给你这张图,你能搜索到来历吗

如果我们想让搜索引擎帮我们找到这张图的来历,可能吗? 这是目前搜索引擎做不到的。如果能做到呢? 转载于:https://www.cnblogs.com/shangge/archive/2008/07/23/1249767.html

晚上读内核代码

最近因为要解决一个bug,需要在内核的f_hid.c里面做一些适配,需要把这部分代码研究透彻。在这几天之前我是根本不知道什么是In端点,Out端点,以及什么是endpoint 0的,而且最近的一段时间,我一直是在写应用方面…

Linux进程调度与性能优化 | 真货

作者简介:张毅峰,某主机厂架构师。一、eBPF安全可观测性的前景展望本次分享将从监控和可观测性、eBPF安全可观测性分析、内核安全可观测性展望三个方面展开。1.监控(Monitoring)vs可观测性(Observability)从上图可以看到,监控只是可观测性的冰…

教师生涯由此开始

招聘会时间:2012-03-28 09:00 招聘会地址:就业办507 为加强教师队伍建设,建设教育强县,经研究,我县决定面向全国“985”、“211”工程高等师范院校应届毕业生选聘普通高中和初中教师38名。现将有关事项公告如下&#x…