在STM32的HAL中实现单按、长按和双按功能
Implementing a single press, long press and a double press function in HAL for STM32
我正在尝试实现单按、双按和长按功能来执行不同的功能。到目前为止,我已经理解了单按和长按的逻辑,但我不知道如何检测双按。至于代码,我已经使用计数器实现了单按和长按,但代码只停留在第一个 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 将变高一秒钟。然而,在 运行 程序中,当我按下按钮时,无论我按住按钮多长时间,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()
始终 returns 立即 - 没有延迟,但在状态更改后重新读取按钮会暂停 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 调度程序并轮询任务中的按钮事件。
我正在尝试实现单按、双按和长按功能来执行不同的功能。到目前为止,我已经理解了单按和长按的逻辑,但我不知道如何检测双按。至于代码,我已经使用计数器实现了单按和长按,但代码只停留在第一个 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 将变高一秒钟。然而,在 运行 程序中,当我按下按钮时,无论我按住按钮多长时间,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()
始终 returns 立即 - 没有延迟,但在状态更改后重新读取按钮会暂停 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 调度程序并轮询任务中的按钮事件。