在STM32的HAL库中实现单击、长按和双击功能

4

我正在尝试实现单击、双击和长按功能以执行不同的操作。到目前为止,我已经理解了单击和长按的逻辑,但是我无法想出如何检测双击。至于代码,我已经使用计数器实现了单击和长按,但代码只停留在第一个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将保持高电平1秒钟;如果我按下按键800毫秒,PB7将保持高电平1秒钟。然而,在运行程序时,无论我按住按钮多长时间,PB0都会变高,并且PB7保持低电平。所以我有两个问题:

  • 如何编辑我的代码,使得单次按压可以让PB0变高,长按可以让PB7变高?
  • 如何实现双击功能?

谢谢!


如果检测到按钮按下边缘并且上次按下是在时间阈值内,则执行双击操作。使用常量作为“双击”所需的时间量可能会根据用户反馈进行微调。 - Michael Dorgan
20毫秒对于开关抖动来说几乎太短了,你无论如何都不可能在那么短的时间内释放按钮。为什么这么短——它只需要比“长按”时间更短就可以了。 - Clifford
1个回答

10

不要在开始时使用延迟。当您处于延迟状态时,您无法看到按钮正在执行的操作(或进行任何其他有用的操作)。相反,您需要持续轮询(或使用中断)以获取按钮状态,并在状态更改时进行时间戳,并根据时间做出操作决策。

首先,您需要具有防抖动的强大按钮状态检测。有许多方法。以下是一个示例:

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() 总是立即返回 - 没有延迟,但在状态改变后暂停20ms重新读取按键,以防误解开关弹跳为多次状态更改。

然后您需要一个按钮状态轮询函数,以检测按钮按下和松开事件的时间。例如:

     ____________________________
____|                            |_____________
     <----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 ;

之后在转换/选择结构语句后:

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接