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

Jos*_*ohn 2 c embedded hal stm32 stm32f4

我正在尝试实现单按、双按和长按功能来执行不同的功能。到目前为止,我已经理解了单按和长按的逻辑,但我不知道如何检测双按。至于代码,我已经使用计数器实现了单按和长按,但代码只停留在第一个 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);
      }
  }
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢!

Cli*_*ord 5

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

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

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 ;
}
Run Code Online (Sandbox Code Playgroud)

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

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

     ____________________________
____|                            |_____________
     <----long-press min-->
                           ^
                           |_Long press detected
     ______     _____
____|      |___|     |_________________________
                     ^
                     |_Double press detected
     ______
____|      |___________________________________
           <------->
              ^    ^
              |    |_Single press detected
              |_ Double press gap max.
Run Code Online (Sandbox Code Playgroud)

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

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 ;
}
Run Code Online (Sandbox Code Playgroud)

最后,您需要经常轮询按钮事件:

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...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

            } break ;
Run Code Online (Sandbox Code Playgroud)

然后在 switch/case 之后:

    if( now - single_press_ts > 1000 )
    {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, 0);
    }
Run Code Online (Sandbox Code Playgroud)

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