当前位置:网站首页>如何实现按键的短按、长按检测?
如何实现按键的短按、长按检测?
2022-08-05 09:01:00 【嵌入式Linux,】
在电子产品中经常用到按键,尤其是经常需要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;
}
else
tmos_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);
}
上面的这段代码初次看着有点绕,但是看明白了之后会觉得这个实现逻辑还是挺好的,注释写了,这里不再详细解释了,我在多个项目里使用的都是它。它兼顾了去抖和短按/长按的检测,并且长按可以判断出长按按下/长按弹起。短按是检测到弹起时认为是短按动作。另外如果想同时支持多个长按,也很方便添加。
轮询和中断各有优缺点,大家可以根据实际情况来选择,你一般常用哪种方式呢?
边栏推荐
- selectPage 动态改变参数方法
- 原型&原型链
- sphinx matches the specified field
- 代码审计—PHP
- What is the connection and difference between software system testing and acceptance testing? Professional software testing solution recommendation
- sql server收缩日志的作业和记录,失败就是因为和备份冲突了吗?
- 【ASM】字节码操作 方法的初始化 Frame
- love is a sad song
- leetcode points to Offer 10- I. Fibonacci sequence
- 请问如果想往mysql里面写数据,直接用flink-connector-jdbc就可以吧,可是我在f
猜你喜欢
Why is pnpm hitting npm and yarn dimensionality reduction?
Creo 9.0 基准特征:基准平面
sql server收缩日志的作业和记录,失败就是因为和备份冲突了吗?
全面讲解GET 和 POST请求的本质区别是什么?原来我一直理解错了
How to make pictures clear in ps, self-study ps software photoshop2022, simple and fast use ps to make photos clearer and more textured
Comprehensively explain what is the essential difference between GET and POST requests?Turns out I always misunderstood
工程制图试题
Spark cluster deployment (third bullet)
15.1.1、md—md的基础语法,快速的写文本备忘录
Creo 9.0 基准特征:基准轴
随机推荐
基因数据平台
Creo 9.0 基准特征:基准平面
Dynamic memory development (C language)
好资料汇总
egg framework
这样写有问题吗?怎么在sql-client 是可以做到数据的同步的
Code Audit - PHP
thinkPHP5 realizes clicks (data increment/decrement)
“充钱”也难治快手的“亏亏亏”?
tensorflow.keras cannot introduce layers
Moonbeam团队发布针对整数截断漏洞的紧急安全修复
thinkPHP5 实现点击量(数据自增/自减)
使用HBuilder离线本地打包ipa教程
明天去订票,准备回家咯~~
哪个是你爱情的颜色?
Going to book tickets tomorrow, ready to go home~~
Chapter 12 Bayesian Networks
Redis cache and existing problems--cache penetration, cache avalanche, cache breakdown and solutions
CVPR 2022 | 将X光图片用于垃圾分割,港中大(深圳)探索大规模智能垃圾分类
Undefined symbols for architecture arm64解决方案