不要在开始时使用延迟。当您处于延迟状态时,您无法看到按钮正在执行的操作(或进行任何其他有用的操作)。相反,您需要持续轮询(或使用中断)以获取按钮状态,并在状态更改时进行时间戳,并根据时间做出操作决策。
首先,您需要具有防抖动的强大按钮状态检测。有许多方法。以下是一个示例:
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( button_down != buttonState() )
{
button_down = !button_down ;
if( button_down )
{
button_down_ts = now ;
}
else
{
button_up_ts = now ;
if( double_pending )
{
button_event = DOUBLE_PRESS ;
double_pending = false ;
}
else
{
double_pending = true ;
}
long_press_pending = false ;
}
}
if( !button_down && double_pending && now - button_up_ts > DOUBLE_GAP_MILLIS_MAX )
{
double_pending = false ;
button_event = SINGLE_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(;;)
{
switch( getButtonEvent() )
{
case NO_PRESS : { ... } break ;
case SINGLE_PRESS : { ... } break ;
case LONG_PRESS : { ... } break ;
case DOUBLE_PRESS : { ... } break ;
}
}
}
观察如何没有延迟,允许您实时检查按钮事件并执行其他工作。显然,“其他工作”也必须在没有过多延迟的情况下执行,否则它将破坏您的按钮事件计时。因此,例如要在一次按下中实现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调度程序,并在任务中轮询按钮事件。