不要使用延迟作为开始。在您处于延迟状态的整个过程中,您看不到按钮正在做什么(或做任何其他对此有用的事情)。相反,您需要连续轮询(或使用中断)按钮状态,当状态发生变化时,为其添加时间戳,并根据时间做出操作决策。
首先,您需要具有去抖功能的强大按钮状态检测。有多种方法。一个例子:
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 ;
}
So 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 调度程序并轮询任务中的按钮事件。