前言
开发中经常遇到按键数很少,却要实现多种类型触发功能的场合,比如短按、长按、双击等等,即所谓按键复用。一般情况下,都是在中断函数中收到终端,做延时判断、防抖等处理。但中断处理不能太久,所以稍微好一些的实现,基本是用定时器来扫描处理。 关于这个按键复用功能,乐鑫ESP32/ESP8266的一个开源项目esp-iot-solution中,有一个非常精彩的实现,虽然平台不同,大家可以参考思路。 本文描述的是一个简单实现方法,简洁易用。一、实现原理
- 处理按键的上升和下降中断
- 设置一分辨率略高的定时器,比如1ms,用于计数自增
- 在按键触发(上升或下降)中断到来时,启动定时器
- 在按键松开中断到来时,停止定时器,并根据计数情况判断是短按还是长按
二、使用步骤
1.按键中断初始化
代码如下(示例):
具体引脚根据应用替换,这里是PF5,注意优先级设的比较高
void exti_button_key_init(void)
{
/ Init key pin //
GPIO_DeInit(GPIOF);
GPIO_Init(GPIOF, GPIO_Pin_5, GPIO_Mode_In_PU_IT);
EXTI_SetPortSensitivity(EXTI_Port_F, EXTI_Trigger_Rising|EXTI_Trigger_Falling);
EXTI_SelectPort(EXTI_Port_F);
EXTI_SetHalfPortSelection(EXTI_HalfPort_F_MSB ,ENABLE);
EXTI_SetHalfPortSelection(EXTI_HalfPort_F_LSB ,DISABLE);
ITC_SetSoftwarePriority( EXTIE_F_PVD_IRQn, ITC_PriorityLevel_1);
}
2.定时器操作
代码如下(示例):
减少其他处理的干扰,中断优先级设的比较高,另外如果无需高精度,可将定时器调整为10ms或更多。
void keyscan_timer_init(void)
{
CLK_PeripheralClockConfig(CLK_Peripheral_TIM3, ENABLE);
TIM3_DeInit();
/* (1/16MHz)*16*1000 = 1ms */
TIM3_TimeBaseInit(TIM3_Prescaler_16, TIM3_CounterMode_Up, 1000);
TIM3_ITConfig(TIM3_IT_Update, ENABLE);
TIM3_ARRPreloadConfig(ENABLE);
TIM3_SetCounter(0);
ITC_SetSoftwarePriority(TIM3_UPD_OVF_TRG_BRK_USART3_TX_IRQn , ITC_PriorityLevel_1);
}
void keyscan_timer_start(void)
{
keyscan_counter = 0;
TIM3_SetCounter(0);
TIM3_Cmd(ENABLE);
}
void keyscan_timer_stop(void)
{
TIM3_Cmd(DISABLE);
}
3. 中断处理
- 定时器中断处理
INTERRUPT_HANDLER(TIM3_UPD_OVF_TRG_BRK_USART3_TX_IRQHandler,21)
{
TIM3_ClearITPendingBit(TIM3_IT_Update);
keyscan_counter++;
}
- 外部中断处理
INTERRUPT_HANDLER(EXTIE_F_PVD_IRQHandler,5)
{
// 读取状态(判断按下、松开)后清中断
BitStatus state = GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_5);
EXTI_ClearITPendingBit(EXTI_IT_PortF);
if( state == RESET ) { // 按下,开始计数(这里是Falling)
keyscan_timer_start();
return; // 返回即可,等待松开中断
} else { // 松开,停止计数(这里是Rising)
keyscan_timer_stop();
}
// 短按判断
// 示例为超过100ms小于3000ms为短按,否则为长按
// 调整该值可以调整灵敏度和长按区分
if( keyscan_counter >= 100 && keyscan_counter < 3000 ) {
g_key_press = KEY_SHORT_PRESS; // 其他地方处理按键状态
}
// 长按判断
else if( keyscan_counter >= 3000 ) {
g_key_press = KEY_LONG_PRESS; // // 其他地方处理按键状态
}
}
总结
- 上述方案满足基本功能,如果需要支持双击,还需要做额外的工作,这里不再展开
- 因上述实现方法问题,要按键要松开后才会响应处理,一直长按是不响应的,如果要实现长按即处理,可在定时器中断处理中,根据引脚值自增,并做计数判断即可。
希望对大家有帮助。