在HAL for STM32中实现单按、长按和双按功能

Posted

技术标签:

【中文标题】在HAL for STM32中实现单按、长按和双按功能【英文标题】:Implementing a single press, long press and a double press function in HAL for STM32 【发布时间】:2021-06-23 15:55:33 【问题描述】:

我正在尝试实现单按、双按和长按功能来执行不同的功能。到目前为止,我已经了解了单按和长按的逻辑,但我不知道如何检测双按。至于代码,我已经使用计数器实现了单按和长按,但代码只停留在第一个 if 条件。

          bool single_press = false;
      bool long_press = false;

      if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))
      

          HAL_TIM_Base_Start(&htim2);
          if ((TIM2->CNT == 20) && (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)))
          
              single_press = true;
              long_press = false;
          
          else if ((TIM2->CNT == 799) && (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)))
          
              single_press = true;
              long_press = true;
          
          HAL_TIM_Base_Stop(&htim2);
      

      if (single_press == true && long_press == false)
      
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 1);
          HAL_Delay(1000);
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
      
      else if (single_press == true && long_press == true)
      
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, 1);
          HAL_Delay(1000);
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, 0);
      
  

我正在尝试实现一个情况,如果我按下键 20 毫秒(单次按下)PB0 将变高一秒钟,如果我按下键 800 毫秒 PB7 将变高一秒钟。但是,在运行程序时,当我按下按钮时,无论我按住按钮多长时间,PB0 都会变为高电平,而 PB7 则保持低电平。所以我想我有两个问题:

如何编辑我的代码,以便单按 PB0 变为高电平,长按 PB7 变为高电平? 如何实现双按功能?

谢谢!

【问题讨论】:

if (button_press_edge detected && last press was within delta-time-threshold) do_double_press(); 使用常量,因为“双击”的时间量会非常直观,并且可能会在您获得用户反馈时进行一些修改。 20ms 勉强够开关去抖,在任何情况下你都不可能在那个时间释放按钮。为什么这么短 - 当然它只需要少于“长按”。 【参考方案1】:

不要使用延迟作为开始。在您一直处于延迟状态时,您看不到按钮在做什么(或做任何其他有用的事情)。相反,您需要不断地轮询(或使用中断)按钮状态,并在状态发生变化时为其添加时间戳,并根据时间做出行动决策。

首先,您需要一个强大的按钮状态检测和去抖动功能。有多种方法。一个例子:

bool buttonState()

    static const uint32_t DEBOUNCE_MILLIS = 20 ;
    static bool buttonstate = HAL_GPIO_ReadPin( GPIOC, GPIO_PIN_13 ) == GPIO_PIN_SET ;
    static uint32_t buttonstate_ts = HAL_GetTick() ;

    uint32_t now = HAL_GetTick() ;
    if( now - buttonstate_ts > DEBOUNCE_MILLIS )
    
        if( buttonstate != HAL_GPIO_ReadPin( GPIOC, GPIO_PIN_13 ) == GPIO_PIN_SET )
        
            buttonstate = !buttonstate ;
            buttonstate_ts = now ;
        
    
    return buttonstate ;

所以buttonState() 总是立即返回 - 没有延迟,但在状态更改后重新读取按钮会保持 20 毫秒,以防止将开关弹跳误解为多个状态更改。

然后你需要一个按钮状态轮询函数来检测按钮按下和按钮向上事件的时间。这样:

     ____________________________
____|                            |_____________
     <----long-press min-->
                           ^
                           |_Long press detected
     ______     _____
____|      |___|     |_________________________
                     ^
                     |_Double press detected
     ______
____|      |___________________________________
           <------->
              ^    ^
              |    |_Single press detected
              |_ Double press gap max.

请注意,单次按下是在按下按钮后经过太长时间后才检测到的,因为它是双击。以下可能需要一些调试(未经测试)视为说明:

typedef enum

    NO_PRESS,
    SINGLE_PRESS,
    LONG_PRESS,
    DOUBLE_PRESS
 eButtonEvent ;

eButtonEvent getButtonEvent()

    static const uint32_t DOUBLE_GAP_MILLIS_MAX = 250 ;
    static const uint32_t LONG_MILLIS_MIN = 800 ;
    static uint32_t button_down_ts = 0 ;
    static uint32_t button_up_ts = 0 ;
    static bool double_pending = false ;
    static bool long_press_pending = false ;
    static bool button_down = false ; ;

    eButtonEvent button_event = NO_PRESS ;
    uint32_t now = HAL_GetTick() ;

    // If state changed...
    if( button_down != buttonState() )
    
        button_down = !button_down ;
        if( button_down )
        
            // Timestamp button-down
            button_down_ts = now ;
        
        else
        
            // Timestamp button-up
            button_up_ts = now ;

            // If double decision pending...
            if( double_pending )
            
                button_event = DOUBLE_PRESS ;
                double_pending = false ;
            
            else
            
                double_pending = true ;
            

            // Cancel any long press pending
            long_press_pending = false ;
        
    

    // If button-up and double-press gap time expired, it was a single press
    if( !button_down && double_pending && now - button_up_ts > DOUBLE_GAP_MILLIS_MAX )
    
        double_pending = false ;
        button_event = SINGLE_PRESS ;
    
    // else if button-down for long-press...
    else if( !long_press_pending && button_down && now - button_down_ts > LONG_MILLIS_MIN )
    
        button_event = LONG_PRESS ;
        long_press_pending = false ;
        double_pending = false ;

    

    return button_event ;

那么最后你需要频繁地轮询按钮事件

int main()

    for(;;)
    
        // Check for button events
        switch( getButtonEvent() )
        
            case NO_PRESS :      ...  break ;
            case SINGLE_PRESS :  ...  break ;
            case LONG_PRESS :    ...  break ;
            case DOUBLE_PRESS :  ...  break ;
        

        // Do other work...
    

查看如何没有延迟,让您实时检查按钮事件并执行其他工作。显然,“其他工作”也必须以过多的延迟执行,否则会弄乱您的按钮事件时间。因此,例如,要在单次按下时实现 1 秒输出,您可能需要:

            case SINGLE_PRESS :
            
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, 1);
                single_press_ts = now ;

             break ;

然后在 switch/case 之后:

    if( now - single_press_ts > 1000 )
    
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, 0);
    

如果这是一个问题,那么您需要考虑使用中断来处理按钮事件 - 将其与去抖动处理结合起来,或者使用 RTOS 调度程序并轮询任务中的按钮事件。

【讨论】:

以上是关于在HAL for STM32中实现单按、长按和双按功能的主要内容,如果未能解决你的问题,请参考以下文章

stm32呼吸灯,两个led灯,一个快闪一个慢闪

长按和鲍勃菜单[关闭]

Vue 移动端的长按与触摸事件

STM32 HAL_Delay TIMER微控制器[关闭]

如何在stm32的hal库uart发送函数怎么用

STM32 和 HAL 函数 GetTick()