c-在STM32的HAL中实现单压、长压和双压功能



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

  • 如何编辑代码,使单次按下PB0变高,长按PB7变高
  • 如何实现双按功能

谢谢!

不要使用延迟启动。当你一直处于延迟状态时,你看不到按钮在做什么(或做任何其他有用的事情(。相反,你需要不断地轮询(或使用中断(按钮状态,当状态发生变化时,给它打上时间戳,并根据时间做出行动决定。

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

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调度器并轮询任务中的按钮事件。

最新更新