如何实现基于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;
}
}
(写的时候思路很好,总结的时候思路几乎就没有了)
加油练习!
如何实现基于GPIO按键的长按,短按,双击的更多相关文章
- 按键板的原理和实现--基于GPIO的按键板
上篇介绍简单的ADC实现,需要IC提供一个额外的ADC.但出于IC成本的考虑,无法提供这个的ADC时,但提供了多个额外的GPIO(General Purpose Input Output:双向的:可以 ...
- 基于心跳的socket长连接
http://coach.iteye.com/blog/2024444 基于心跳的socket长连接 博客分类: http socket 案例: 心跳: socket模拟网页的报文连接某个网站,创建t ...
- AM335x(TQ335x)学习笔记——GPIO按键驱动移植
还是按照S5PV210的学习顺序来,我们首先解决按键问题.TQ335x有六个用户按键,分别是上.下.左.右.Enter和ESC.开始我想到的是跟学习S5PV210时一样,编写输入子系统驱动解决按键问题 ...
- 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接
本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验五:按键模块④ — 点击,长点击,双击
实验五:按键模块④ - 点击,长点击,双击 实验二至实验四,我们一共完成如下有效按键: l 点击(按下有效) l 点击(释放有效) l 长击(长按下有效) l 双击(连续按下有效) 然而,不管哪个实验 ...
- 【CC2530强化实训02】普通延时函数实现按键的长按与短按
[CC2530强化实训02]普通延时函数实现按键的长按与短按 [题目要求] 用一个按键实现单击与双击的功能已经是很多嵌入式产品的常用手法.使用定时器的间隔定时来计算按键按下的时间是通用的做法 ...
- 【CC2530强化实训01】普通延时函数实现按键的长按与短按
[CC2530强化实训01]普通延时函数实现按键的长按与短按 [题目要求] 用一个按键实现长按与短按的功能已经是很多嵌入式产品的常用手法.使用定时器的间隔定时来进行按键按下的时间是通用的做法, ...
- STM32f103按键检测程序实现长按短按
背景 只要使用单片机,按键检测基本上是一定要实现的功能.按键检测要好用,最重要的是实时和去抖.初学者往往会在主循环调用按键检测程序(实时)并利用延时去抖(准确).这种在主循环内延时的做法对整个程序非常 ...
- Knative 实战:三步走!基于 Knative Serverless 技术实现一个短网址服务
短网址顾名思义就是使用比较短的网址代替很长的网址.维基百科上面的解释是这样的: 短网址又称网址缩短.缩短网址.URL 缩短等,指的是一种互联网上的技术与服务,此服务可以提供一个非常短小的 URL 以代 ...
- 长连接 短连接 RST报文
https://baike.baidu.com/item/短连接 短连接(short connnection)是相对于长连接而言的概念,指的是在数据传送过程中,只在需要发送数据时,才去建立一个连接,数 ...
随机推荐
- immutable.js学习笔记(二)----- List
一.List list与数组是兼容的,大多数的api与数组是类似的 注意 List.of(),不需要写中括号 二.List的API (一)size:取得 List 的长度 (二)set:设定指定下标的 ...
- day05-SpringMVC底层机制简单实现-01
SpringMVC底层机制简单实现-01 主要完成:核心分发控制器+Controller和Service注入容器+对象自动装配+控制器方法获取参数+视图解析+返回JSON格式数据 1.搭建开发环境 创 ...
- Nginx08 通过扩容提升整体吞吐量 nginx平滑升级-添加sticky模块和使用
1 扩容方式介绍 一个单一站点,想要扩,可以从硬件软件等多个方面来进行. 1 单机垂直扩容:硬件资源增加 2 水平扩展:集群化 3 细粒度拆分:分布式 3-1 数据分区 3-2 上游服务SOA化(原生 ...
- 构建api gateway之 openresty 中如何使用 wasm
openresty 中如何使用 wasm WASM 是什么? WebAssembly是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果.它设计的目的不是为了手写代码而是为诸如C.C++ ...
- 【Vue】计算属性 监听属性 组件通信 动态组件 插槽 vue-cli脚手架
目录 昨日回顾 1 计算属性 插值语法+函数 使用计算属性 计算属性重写过滤案例 2 监听属性 3 组件介绍和定义 组件之间数据隔离 4 组件通信 父子通信之父传子 父子通信之子传父 ref属性 扩展 ...
- Xlight安装与使用
Xlight安装与使用 一.Xlight安装 下载Xlight安装包,点击安装,默认就可以,下一步 点击左上角增加虚拟服务器,IP地址为本机服务器IP地址 右键点击新添加的虚拟服务器,点击虚拟服务器操 ...
- 基于Vue3+TS的Monorepo前端项目架构设计与实现
写在前面 你好,我是前端程序员鼓励师岩家兴!去年在另一个项目https://juejin.cn/post/7121736546000044046中,我向读者朋友们介绍了结合npm包管理工具yarn作v ...
- 推荐一款新的自动化测试框架:DrissionPage!
今天给大家推荐一款基于Python的网页自动化工具:DrissionPage.这款工具既能控制浏览器,也能收发数据包,甚至能把两者合而为一,简单来说:集合了WEB浏览器自动化的便利性和 request ...
- 云小课|MRS数据分析-通过Spark Streaming作业消费Kafka数据
阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:Spark Str ...
- [SWPUCTF 2021 新生赛]jicao
CTF web安全 阅读代码可知当传入一个post型的参数id与wllmNB相等并且传入一个get型的参数json: 但是这里有一个函数json_decode,上网搜索可知json_decode这个函 ...