如何实现基于GPIO按键的长按,短按,双击
作者:互联网
不同的架构实现并不相同,所以我分成了两中:STM32平台和其他平台:
STM32平台
首先要分析:该如何判断当前的按键状态:单机和双击是通过在有限时间内是否有新的按动作产生 —— 所以需要一个记录按键次数和松开后相隔时的数据结构;短按和长按的区别就是按键的持续时间 —— 所以需要一个记录持续按键时间的数据结构;
因为双击和单机应该是在松开一段时间之后才会执行,长按应该是到达时间就会执行;所以需要设立一个记录需要被执行的时间类型和一个记录是否被执行的数据机构:
最终的数据结构如图所示:
1 typedef struct{ 2 struct{ 3 uint8_t check:1; // 是否需要被判断 4 uint8_t key_state; // RELEASE ; PRESS ; IDEL 三种状态 5 uint8_t once_event; // 表示是否有事件需要被处理 6 uint8_t press_time; // 区分长短按;0短1常 7 }flag; 8 uint8_t event_current_type; // 事件类型 9 uint8_t event_previous_type; 10 uint8_t press_count; // 按下的次数 11 uint16_t time_idle; // 按键空闲时间计数器 12 uint16_t time_continus; // 按键持续事件计数器 13 }KEY_PROCESS_TypeDef; 14 KEY_PROCESS_TypeDef key;
然后需要定义一些宏 —— 表示事件类型,案件类型;超时时间等
#define KEY_TIME_IDLE 400 // 按键动作空闲时间 #define KEY_TIME_CONTINUS 500 // 按键动作持续时间 #define KEY_TIME_OUT 2000 // 按键超时 #define EVENT_KEYPRESS_UNCLICK 0 #define EVENT_KEYPRESS_SHORT 1 #define EVENT_KEYPRESS_LONG 2 #define EVENT_KEYPRESS_DOUBLE 3 #define KEY_STATE_IDLE 0 #define KEY_STATE_PRESS 1 #define KEY_STATE_RELEASE 2 #define SHORT_CLICK 1 #define LONG_CLICK 2 #define DOUBLE_CLICK 3
按下键产生的中断只需要改变按键状态
时间产生的中断则需要查看按键状态,去改变结构体的数据:
Step:配置中断(在STM32平台中需要配置中断控制NVIC,GPIO, TIM)
void NVIC_EXTI_Config(void); void NVIC_TIM2_Config(void); void EXTI_GPIO_Config(void); void TIMx_Config(TIM_TypeDef*); void KEY_config(){ NVIC_EXTI_Config(); NVIC_TIM2_Config(); EXTI_GPIO_Config(); TIMx_Config(TIM2); } void NVIC_EXTI_Config(){ NVIC_InitTypeDef nvic_init_structure; nvic_init_structure.NVIC_IRQChannel = EXTI0_IRQn; nvic_init_structure.NVIC_IRQChannelPreemptionPriority = 1; nvic_init_structure.NVIC_IRQChannelSubPriority = 1; nvic_init_structure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&nvic_init_structure); } void NVIC_TIM2_Config(){ NVIC_InitTypeDef NVIC_Init_Struct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_Init_Struct.NVIC_IRQChannel = TIM2_IRQn; NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_Init_Struct.NVIC_IRQChannelSubPriority = 2; NVIC_Init_Struct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_Init_Struct); } void EXTI_GPIO_Config(){ GPIO_InitTypeDef GPIO_Init_Struct; EXTI_InitTypeDef EXTI_Init_Struct; // 和实现相关,有可能是APB2,有可能是AHB1 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_Init_Struct.GPIO_Pin = GPIO_Pin_0; GPIO_Init_Struct.GPIO_Mode = GPIO_Mode_IN; /* * 驱动电路的响应速度 —— 也就是驱动电路可以不失真地通过信号的最大频率 * 对于USART最大的波特率只有115k,所以2MHz就够了 * 对于I2C的400k,则需要10M的速度; * 对于可以到19M的SPI,则需要50Mhz */ GPIO_Init_Struct.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, &GPIO_Init_Struct); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // 根据按键结构体,如果键是PA0, 则就是GPIOA的Pin0 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); EXTI_Init_Struct.EXTI_Line = EXTI_Line0; EXTI_Init_Struct.EXTI_Mode = EXTI_Mode_Interrupt; // 这样弹起和按下就都可以产生中断了,更方便 EXTI_Init_Struct.EXTI_Trigger = EXTI_Trigger_Rising_Falling; EXTI_Init_Struct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_Init_Struct); } void TIMx_Config(TIM_TypeDef* TIMx){ TIM_TimeBaseInitTypeDef TIM_Time_Base_Stuct; // 使用APB1上的TIM2 —— 通用定时器 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 预分频为71 + 1也就是72Mhz/72 = 1MHz TIM_Time_Base_Stuct.TIM_Prescaler = 71; // 定时器周期是1000 按照1MHz,一个周期是1us;则定时器为1ms TIM_Time_Base_Stuct.TIM_Period = 1000; // 也就是定时器频率和数字滤波器频率相同 TIM_Time_Base_Stuct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_Time_Base_Stuct.TIM_CounterMode = TIM_CounterMode_Up; // 根据配置初始化TIM2 TIM_TimeBaseInit(TIMx, &TIM_Time_Base_Stuct); TIM_ARRPreloadConfig(TIMx, ENABLE); TIM_ClearFlag(TIMx, TIM_FLAG_Update); TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE); TIM_Cmd(TIMx, DISABLE); }
在设置了中断之后;就需要添加中断函数(注意名称必须是这个不能修改)
其中如果是按下的状态,则设置状态,并且需要被检查;持续时间为0(毕竟重新开始)
如果是释放状态,则设置状态,需要被检查,并且空闲时间为0(毕竟刚按完,刚开始空闲)
key_process是处理状态,形成事件的函数;key_scan是生成时间的函数
void EXTI0_IRQHandler(){ uint16_t status = EXTI_GetITStatus(EXTI_Line0); if(EXTI_GetITStatus(EXTI_Line0) != RESET){ key.flag.key_state = KEY_STATE_PRESS; key.flag.check = 1; key.time_continus = 0; } else{ key.flag.key_state = KEY_STATE_RELEASE; key.flag.check = 1; key.time_idle = 0; } EXTI_ClearITPendingBit(EXTI_Line0); } void TIM2_IRQHandler(){ // SR是状态寄存器;清0应该就是清除状态; TIM2->SR = 0x0000; key_process(); key_scan(); }
这是我的一个判断的流程图:
具体的实现代码:
1 void key_process(){ 2 switch(key.flag.key_state){ 3 case KEY_STATE_PRESS: 4 if(key.time_continus < KEY_TIME_OUT) 5 key.time_continus += 1; 6 if(key.time_continus > KEY_TIME_CONTINUS){ 7 if(key.event_current_type != EVENT_KEYPRESS_UNCLICK){ 8 if(key.press_count > 1) 9 key.press_count -= 1; 10 key.flag.once_event = 1; 11 }else{ 12 key.flag.press_time = 1; 13 key.flag.key_state = KEY_STATE_IDLE; 14 key.event_current_type = EVENT_KEYPRESS_LONG; 15 key.flag.once_event = 1; 16 key.press_count = 1; 17 key.time_idle = KEY_TIME_OUT; 18 } 19 } 20 if(key.flag.check){ 21 key.flag.check = 0; 22 if(!key.flag.press_time){ 23 if(key.time_idle < KEY_TIME_IDLE) 24 key.press_count += 1; 25 else 26 key.press_count = 1; 27 } 28 key.flag.press_time = 0; 29 } 30 break; 31 case KEY_STATE_RELEASE: 32 if(key.time_idle < KEY_TIME_OUT) 33 key.time_idle ++ ; 34 if(key.flag.check){ 35 key.flag.check = 0; 36 if(!key.flag.press_time){ 37 if(key.press_count > 1) 38 key.event_current_type = EVENT_KEYPRESS_DOUBLE; 39 else{ 40 key.event_current_type = EVENT_KEYPRESS_SHORT; 41 key.press_count = 1; 42 } 43 } 44 } 45 if(key.time_idle > KEY_TIME_IDLE) 46 if(!key.flag.press_time){ 47 key.flag.once_event = 1; 48 key.flag.key_state = KEY_STATE_IDLE; 49 } 50 break; 51 default: 52 break; 53 } 54 } 55 56 void key_scan(){ 57 if(key.flag.once_event){ 58 switch(key.event_current_type){ 59 case EVENT_KEYPRESS_SHORT: 60 result = SHORT_CLICK; 61 break; 62 case EVENT_KEYPRESS_LONG: 63 result = LONG_CLICK; 64 break; 65 case EVENT_KEYPRESS_DOUBLE: 66 result = DOUBLE_CLICK; 67 break; 68 default: 69 result = 0x12; 70 break; 71 } 72 key.event_previous_type = key.event_current_type; 73 key.event_current_type = EVENT_KEYPRESS_UNCLICK; 74 } 75 }
对于另外一个平台
我时机需要使用的是没有弹起和按下两种判断的,只有按下去的中断
所以需要修改很多逻辑:
我多添加了两个全局变量:
static u8_t in_long_press;
// 表示正在长按中;如果之前是按下的,但是当前没有按下;则是release状态(没有release中断,只能这样判断了)
// 刚好release并且之前in_long_press表示之前一直按着,则表示刚刚结束长按,吧下面的置1 static u8_t finish_long_press;
// 表示刚刚结束长按
然后以每1ms轮询的方式判断当前按键的状态:
if(key.flag.key_state == KEY_STATE_PRESS && get_gpio_pin1() == 0){ // 之前状态是按下的,现在起来了 key.flag.key_state = KEY_STATE_RELEASE; key.flag.check = 1; if(in_long_press){ // 之前是长按 finish_long_press = 1; in_long_press = 0; } }else if(key.flag.key_state == KEY_STATE_IDLE && get_gpio_pin1() == 1){ key.flag.key_state = KEY_STATE_PRESS; finish_long_press = 0; key.time_idle = 0; if(!in_long_press){ key.flag.check = 1; key.time_continus = 0; } }else if(key.flag.key_state == KEY_STATE_RELEASE && get_gpio_pin1() == 0){ finish_long_press = 0; key.flag.key_state = KEY_STATE_IDLE; }
处理函数类似:
void key_scan(){ if(key.flag.once_event){ key.flag.once_event = 0; switch(key.event_current_type){ case EVENT_KEYPRESS_SHORT: if(!finish_long_press){ key.press_count = 0; } else finish_long_press = 0; break; case EVENT_KEYPRESS_LONG: if(!in_long_press){ in_long_press = 1; } break; case EVENT_KEYPRESS_DOUBLE: if(!in_long_press){ key.press_count = 0; } break; default: break; } key.event_previous_type = key.event_current_type; key.event_current_type = EVENT_KEYPRESS_UNCLICK; } } void key_process(){ switch(key.flag.key_state){ case KEY_STATE_PRESS: if(key.time_continus < KEY_TIME_OUT) key.time_continus += 1; if(key.time_continus > KEY_TIME_CONTINUS){ if(key.event_current_type != EVENT_KEYPRESS_UNCLICK){ if(key.press_count > 1) key.press_count -= 1; key.flag.once_event = 1; }else{ key.flag.press_time = 1; key.event_current_type = EVENT_KEYPRESS_LONG; key.flag.once_event = 1; key.press_count = 1; key.time_idle = KEY_TIME_OUT; } } if(key.flag.check){ key.flag.check = 0; if(!key.flag.press_time){ if(key.time_idle < KEY_TIME_IDLE) key.press_count += 1; else key.press_count = 1; } key.flag.press_time = 0; } break; case KEY_STATE_RELEASE: if(key.flag.check){ key.flag.check = 0; if(!key.flag.press_time){ if(key.press_count > 1) key.event_current_type = EVENT_KEYPRESS_DOUBLE; else{ key.event_current_type = EVENT_KEYPRESS_SHORT; key.press_count = 1; } } } break; case KEY_STATE_IDLE: if(key.time_idle < KEY_TIME_OUT) key.time_idle += 1; if(!key.flag.press_time){ if( (key.event_current_type == EVENT_KEYPRESS_DOUBLE || key.event_current_type == EVENT_KEYPRESS_SHORT) && key.time_idle > KEY_TIME_IDLE) key.flag.once_event = 1; } break; } }
(写的时候思路很好,总结的时候思路几乎就没有了)
加油练习!
标签:KEY,短按,event,flag,key,time,press,GPIO,双击 来源: https://www.cnblogs.com/celesial-dancers/p/16541412.html