不同的架构实现并不相同,所以我分成了两中: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按键的长按,短按,双击的更多相关文章

  1. 按键板的原理和实现--基于GPIO的按键板

    上篇介绍简单的ADC实现,需要IC提供一个额外的ADC.但出于IC成本的考虑,无法提供这个的ADC时,但提供了多个额外的GPIO(General Purpose Input Output:双向的:可以 ...

  2. 基于心跳的socket长连接

    http://coach.iteye.com/blog/2024444 基于心跳的socket长连接 博客分类: http socket 案例: 心跳: socket模拟网页的报文连接某个网站,创建t ...

  3. AM335x(TQ335x)学习笔记——GPIO按键驱动移植

    还是按照S5PV210的学习顺序来,我们首先解决按键问题.TQ335x有六个用户按键,分别是上.下.左.右.Enter和ESC.开始我想到的是跟学习S5PV210时一样,编写输入子系统驱动解决按键问题 ...

  4. 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

    本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...

  5. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验五:按键模块④ — 点击,长点击,双击

    实验五:按键模块④ - 点击,长点击,双击 实验二至实验四,我们一共完成如下有效按键: l 点击(按下有效) l 点击(释放有效) l 长击(长按下有效) l 双击(连续按下有效) 然而,不管哪个实验 ...

  6. 【CC2530强化实训02】普通延时函数实现按键的长按与短按

    [CC2530强化实训02]普通延时函数实现按键的长按与短按 [题目要求]      用一个按键实现单击与双击的功能已经是很多嵌入式产品的常用手法.使用定时器的间隔定时来计算按键按下的时间是通用的做法 ...

  7. 【CC2530强化实训01】普通延时函数实现按键的长按与短按

    [CC2530强化实训01]普通延时函数实现按键的长按与短按 [题目要求]     用一个按键实现长按与短按的功能已经是很多嵌入式产品的常用手法.使用定时器的间隔定时来进行按键按下的时间是通用的做法, ...

  8. STM32f103按键检测程序实现长按短按

    背景 只要使用单片机,按键检测基本上是一定要实现的功能.按键检测要好用,最重要的是实时和去抖.初学者往往会在主循环调用按键检测程序(实时)并利用延时去抖(准确).这种在主循环内延时的做法对整个程序非常 ...

  9. Knative 实战:三步走!基于 Knative Serverless 技术实现一个短网址服务

    短网址顾名思义就是使用比较短的网址代替很长的网址.维基百科上面的解释是这样的: 短网址又称网址缩短.缩短网址.URL 缩短等,指的是一种互联网上的技术与服务,此服务可以提供一个非常短小的 URL 以代 ...

  10. 长连接 短连接 RST报文

    https://baike.baidu.com/item/短连接 短连接(short connnection)是相对于长连接而言的概念,指的是在数据传送过程中,只在需要发送数据时,才去建立一个连接,数 ...

随机推荐

  1. 行为型模式 - 备忘录模式Memento

    学习而来,代码是自己敲的.也有些自己的理解在里边,有问题希望大家指出. 模式的定义与特点 在备忘录模式(Memento Pattern)下,为的是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该 ...

  2. eosio.cdt发布带来的变化

    change of version 1.3.x+,EOSIO.CDT After eos version 1.3.x, generation of cdt tools, Smart Contracts ...

  3. 【学习笔记】XR872 GUI Littlevgl 8.0 移植(显示部分)

    LVGL 介绍 官方网站:LVGL - Light and Versatile Embedded Graphics Library 源码位置:GitHub - lvgl/lvgl: Powerful ...

  4. CoppeliaSim(原V-REP)教育版不给下载的解决方法

    CoppeliaSim(原V-REP)教育版不给下载的解决方法 首先进入CoppeliaSim官网 网址:https://www.coppeliarobotics.com/downloads http ...

  5. STM32F1库函数初始化系列:定时器中断

    1 static void TIM3_Configuration(void) //10ms 2 { 3 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 4 ...

  6. Unreal 各种指针类型是怎么回事

    引言 读完本篇文章,你会了解为何UE中C++作为其开发语言,使用的指针,为何各式各样. 你需要对UE有所了解,如果不了解也没关系,也可以看下这篇文章,就当了解一下最复杂的应用的系统指针设计是如何. 可 ...

  7. JZOJ 1078. 【GDOI2006】The Kth Element

    \(\text{Problem}\) 给定一个整数序列 \(a[1..N]\),定义 \(sum[i][j]=a[i]+a[i+1]+...+a[j]\),将所有的 \(sum[i][j]\) 从小到 ...

  8. Windows 10系统设置多用户同时远程登录教程 and rdpwrap下载 and Win10多用户同时远程桌面的另类解决方案---支持1809和1909和2004版本V2.0

    转载简书: Windows 10系统设置多用户同时远程登录教程 - 简书 (jianshu.com) 转载github: 发布 ·stascorp/rdpwrap ·GitHub 转载csdn: Wi ...

  9. 2013-12-2 ISBN号码

    问题描述 试题编号: 201312-2 试题名称: ISBN号码 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 每一本正式出版的图书都有一个ISBN号码与之对应,ISBN码包 ...

  10. CCRD_TOC_2007年12月_总第13期

    中信国健临床通讯 2007年12月, 总第13期 ACR2007专辑 目 录   类风湿关节炎 1.        来自CORRONA的数据:TNF抑制剂停用后临床获益仍持续存在 Lee SJ, et ...