0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

单片机短按、长按实现方法

strongerHuang 来源:TopSemic嵌入式 2023-06-08 16:43 次阅读

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

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

16063702-05d8-11ee-962d-dac502259ad0.png

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

161f458a-05d8-11ee-962d-dac502259ad0.png

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

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

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

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

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

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

162958e0-05d8-11ee-962d-dac502259ad0.png

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

1638a066-05d8-11ee-962d-dac502259ad0.png

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

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

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

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

voidGPIOB_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应用,在中断服务函数中执行时间长会引起蓝牙连接中断,所以这里不能这么用,我实际测试当按键按快一点就很容易引起蓝牙连接中断。

方法二:加定时器

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

voidGPIOB_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("shortpress
");
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("shortpress
");
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)
{
staticintcnt=0;
if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
{
cnt++;
if(cnt>100)
{
PRINT("longpress
");
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变高之后会停止掉这个定时器,否则周期定时,因为没必要一直开着定时器。

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

voidKey_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);
}
boolkey_press_flag=false;//按下标志
boolkey_long_press_flag=false;//长按标志

voidKeyScan()
{
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;//置位按下标志
}
elseif(key_press_flag==true)//高电平同时按键被按下过,表示是按下后的弹起
{
key_press_flag=false;//清除按下标志

if(key_long_press_flag==false)//短按后的弹起
{
tmos_stop_task(hidEmuTaskId,START_LONGCHECK_TIMER);
PRINT("shortpress
");
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("longpress
");
return(events^START_LONGCHECK_TIMER);
}

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

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


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6030

    文章

    44501

    浏览量

    632274
  • 操作系统
    +关注

    关注

    37

    文章

    6703

    浏览量

    123154
  • 定时器
    +关注

    关注

    23

    文章

    3234

    浏览量

    114371

原文标题:单片机短按、长按实现方法

文章出处:【微信号:strongerHuang,微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    求助:单片机一键长按短按按键实现的c程序有问题

    单片机一键长按短按按键实现的c程序有问题,实在不知道是哪里有问题,请帮助修改一下,谢谢!功能如下:长按2秒灯全亮(我用的开发板),
    发表于 06-03 16:55

    求助!如何使飞思卡尔s128单片机键盘长按短按出现不同的...

    如何使s128单片机键盘长按短按出现不同的现象(反应),就是该怎么改变程序
    发表于 11-25 18:57

    单片机如何区别按键长按短按

      单片机工程师在面试的过程中,经常会碰到一些相同的问题,笔者总结了十个提问率较高的问题,供大家参考。现在我们来分析单片机工程师常遇面试问题之二:单片机如何区别按键长按
    发表于 01-14 16:59

    如何通过外部中断实现按键的长按短按

    瑞萨单片机通过外部中断实现按键的长按短按
    发表于 12-01 07:57

    stm32按键的长按/短按怎么实现

    stm32按键的长按/短按怎么实现
    发表于 12-02 07:41

    实现单片机按键长按短按功能的方法

    写在前面 一般我们在写单片机程序的时候都要用到按键,在按键较少的情况下我们需要一个按键可以返回不同的按下结果,也就是长按短按。程序实现 大致思路是按键按下时打开定时器,按键松开时关闭
    发表于 12-06 07:40

    单片机状态按键长按短按实现

    本文只介绍主要代码段,完整代码可参考我的“蓝桥杯单片机状态按键按下和松开实现不同功能”蓝桥杯单片机状态按键
    发表于 01-06 08:26

    按键长按短按效果

    按键长按短按效果 C51单片机源码,KEIL源文件,C语言编写
    发表于 06-20 16:15 64次下载

    基于状态单片机按键短按长按功能的实现

    本文主要介绍了基于状态单片机按键短按长按功能的实现,按键的击键过程也是一种状态的切换,也可以看着是一个状态
    发表于 12-28 08:43 1.9w次阅读
    基于状态<b class='flag-5'>机</b>的<b class='flag-5'>单片机</b>按键<b class='flag-5'>短按</b><b class='flag-5'>长按</b>功能的<b class='flag-5'>实现</b>

    单片机按键进行长按短按的流程分析和程序代码免费下载

    在写单片机程序的过程中,难免会遇到按键的写法,比如一个按键实现长按短按。本文只针对这些操作说明一下自己按键的思想。可应用到实际工程中。按键处理过程:设置一个按键标志,按下时为1,松开
    发表于 09-19 17:20 9次下载
    <b class='flag-5'>单片机</b>按键进行<b class='flag-5'>长按</b>和<b class='flag-5'>短按</b>的流程分析和程序代码免费下载

    使用单片机实现按键长按短按效果的C语言程序免费下载

    本文档的主要内容详细介绍的是使用单片机实现按键长按短按效果的C语言程序免费下载。
    发表于 11-26 17:31 54次下载

    瑞萨单片机之外部中断实现按键的长按短按(二)

    瑞萨单片机通过外部中断实现按键的长按短按
    发表于 11-22 14:21 20次下载
    瑞萨<b class='flag-5'>单片机</b>之外部中断<b class='flag-5'>实现</b>按键的<b class='flag-5'>长按</b>与<b class='flag-5'>短按</b>(二)

    如何实现单片机按键长按短按功能

    写在前面 一般我们在写单片机程序的时候都要用到按键,在按键较少的情况下我们需要一个按键可以返回不同的按下结果,也就是长按短按。程序实现 大致思路是按键按下时打开定时器,按键松开时关闭
    发表于 11-23 18:06 39次下载
    如何<b class='flag-5'>实现</b><b class='flag-5'>单片机</b>按键<b class='flag-5'>长按</b>和<b class='flag-5'>短按</b>功能

    如何区分按钮的短按长按

    怎样区分按钮的短按(按下后松开)与长按(按下并保持一段时间)。说实话,在工业现场,按钮长按实现某个功能使用的并不多。
    的头像 发表于 02-07 13:53 3480次阅读
    如何区分按钮的<b class='flag-5'>短按</b>与<b class='flag-5'>长按</b>?

    基于89C51单片机的按键长按短按效果源程序

    基于89C51单片机的按键长按短按效果源程序
    发表于 05-16 09:45 16次下载