dxgdsx 发表于 2018-9-24 17:29:32

按键处理模块,实现短按、长按、连击判断,附源码与图...

本帖最后由 dxgdsx 于 2018-9-24 19:41 编辑

论坛已有几位大神发布了关于按键处理的C代码,写的都非常好,本人很受启发。
比如https://www.amobbs.com/forum.php?mod=viewthread&tid=5542774&highlight=%E6%8C%89%E9%94%AE

中秋闲来无事,我也自己动手写一写。
事先声明:本代码参考了论坛里面几位大神的开源代码,以及STM32库函数代码风格,此外也加入了个人的一点想法,欢迎批评指正!

首先简单单片机系统中,常见的按键操作有短按(点击)、长按、连击。
个人对“连击”操作的定义并不是很清晰,个人理解是在长按基础上继续按着,然后超过一段时间视为连击1次,...,直到弹起。
下图是个人对按键操作的理解:

那么源代码中定义一些操作判定用的时间常数:
//建议10ms扫描一次
//这些常数大小,可以根据项目实际需求进行修改
//按键按下消抖计数
#define KEY_PRESS_DB_CNT                        3
//判断按键长按计数
#define KEY_LONG_PRESS_CNT                150
//判断按键连击计数
#define KEY_REPEAT_PRESS_CNT                50
//按键释放消抖计数
#define KEY_RELEASE_DB_CNT                3

设计了一个按键扫描处理状态机,共有5个状态编码:
//按键扫描状态机状态编码
typedef enum
{
        //按键状态机:“按键等待被按下”处理
        KEY_FSM_WAIT_PRESS = 0,
        //按键状态机:“按键短按”处理
        KEY_FSM_SHORT_PRESS,
        //按键状态机:“按键长按”处理
        KEY_FSM_LONG_PRESS,
        //按键状态机:“按键连击”处理
        KEY_FSM_REPEAT_PRESS,
        //按键状态机:“按键释放”处理
        KEY_FSM_RELEASE
}KEY_FsmTypeDef;

定义按键的不同,共有6个状态编码:
//按键状态编码
typedef enum
{
        //按键状态:释放状态
        KEY_RELEASED = 0,
        //按键状态:短按状态
        KEY_SHORT_PRESSED,
        //按键状态:等待长按状态
        KEY_LONG_PRESS_WAIT,
        //按键状态:长按状态
        KEY_LONG_PRESSED,
        //按键状态:等待连击状态
        KEY_REPEAT_PRESS_WAIT,
        //按键状态:一次连击状态
        KEY_REPEAT_PRESSED
}KEY_StateTypeDef;

定义一个独立按键的按键句柄结构体:
//按键句柄结构体
typedef struct
{
        uint8_t KeyId; //按键编号
        KEY_StateTypeDef KeyState; //按键当前状态(高4bit为上一个状态,低4bit为当前状态)
        uint16_t KeyCnt; //内部计数器(高2bit给按下/弹出计数用,低14bit给长按/连击计数用)
        KEY_FsmTypeDef KeyFsmState; //状态机状态
}KEY_HandleTypeDef;

声明一些函数:
//按键端口读取函数声明,需要用户根据硬件情况自行实现
uint8_t KEY_Read_Port(uint8_t KeyId);
//按键初始化函数声明
void KEY_Init(void);
//按键扫描函数声明
void KEY_Scan(KEY_HandleTypeDef* keyHandle);
//按键状态获取函数声明
KEY_StateTypeDef KEY_Get_State(KEY_HandleTypeDef* keyHandle);

其中,关键函数是KEY_Scan函数,事实上其它几个函数无关紧要,存在更多只是为了形式上的完整
KEY_Scan函数就是一个状态机处理函数,源代码如下:
/**
* @brief单个按键处理函数,需要被周期性调用,建议10ms扫描一次
* @param指定单个按键的句柄
* @retval None
*/
void KEY_Scan(KEY_HandleTypeDef* keyHandle)
{
        //读取按键端口电平
        uint8_t IsKeyDown = KEY_Read_Port(keyHandle->KeyId);
       
        //获取状态机的当前状态
        switch(keyHandle->KeyFsmState)
        {
                case KEY_FSM_WAIT_PRESS:
                        if(IsKeyDown) //检测到按键按下
                        {
                                keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
                                //按键按下消抖:连续几次检测到按键按下,可以确认按键按下
                                if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_PRESS_DB_CNT)
                                {
                                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                                        keyHandle->KeyFsmState = KEY_FSM_SHORT_PRESS;
                                        keyHandle->KeyState = KEY_FULL_STATE(KEY_RELEASED, KEY_SHORT_PRESSED);
                                }
                        }
                        else
                                keyHandle->KeyCnt = 0; //检测到按键释放,复位计数器
                        break;
               
                case KEY_FSM_SHORT_PRESS:
                        keyHandle->KeyState = KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_LONG_PRESS_WAIT);
                        if(!IsKeyDown)//检测到按键释放
                        {
                                keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
                                //按键释放消抖:连续几次检测到按键释放,可以确认按键释放
                                if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_RELEASE_DB_CNT)
                                {
                                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                                        keyHandle->KeyFsmState = KEY_FSM_RELEASE;
                                        keyHandle->KeyState = KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_RELEASED);
                                }
                        }
                        else
                        {
                                keyHandle->KeyCnt = KEY_FULL_CNT(0, KEY_LOW_CNT(keyHandle->KeyCnt)+1);
                                if(KEY_LOW_CNT(keyHandle->KeyCnt) >= KEY_LONG_PRESS_CNT) //可以确认按键长按
                                {
                                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                                        keyHandle->KeyFsmState = KEY_FSM_LONG_PRESS;
                                        keyHandle->KeyState = KEY_FULL_STATE(KEY_LONG_PRESS_WAIT, KEY_LONG_PRESSED);
                                }
                        }
                        break;
               
                case KEY_FSM_LONG_PRESS:
                        keyHandle->KeyState = KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_REPEAT_PRESS_WAIT);
                        if(!IsKeyDown)//检测到按键释放
                        {
                                keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
                                //按键释放消抖:连续几次检测到按键释放,可以确认按键释放
                                if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_RELEASE_DB_CNT)
                                {
                                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                                        keyHandle->KeyFsmState = KEY_FSM_RELEASE;
                                        keyHandle->KeyState = KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_RELEASED);
                                }
                        }
                        else
                        {
                                keyHandle->KeyCnt = KEY_FULL_CNT(0, KEY_LOW_CNT(keyHandle->KeyCnt)+1);
                                if(KEY_LOW_CNT(keyHandle->KeyCnt) >= KEY_REPEAT_PRESS_CNT) //可以确认按键连击
                                {
                                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                                        keyHandle->KeyFsmState = KEY_FSM_REPEAT_PRESS;
                                        keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED);
                                }
                        }
                        break;
               
                case KEY_FSM_REPEAT_PRESS:
                        keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESSED, KEY_REPEAT_PRESS_WAIT);
                        if(!IsKeyDown)//检测到按键释放
                        {
                                keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
                                //按键释放消抖:连续几次检测到按键释放,可以确认按键释放
                                if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_RELEASE_DB_CNT)
                                {
                                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                                        keyHandle->KeyFsmState = KEY_FSM_RELEASE;
                                        keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_RELEASED);
                                }
                        }
                        else
                        {
                                keyHandle->KeyCnt = KEY_FULL_CNT(0, KEY_LOW_CNT(keyHandle->KeyCnt)+1);
                                if(KEY_LOW_CNT(keyHandle->KeyCnt) >= KEY_REPEAT_PRESS_CNT) //可以确认按键连击
                                {
                                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                                        keyHandle->KeyFsmState = KEY_FSM_REPEAT_PRESS;
                                        keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED);
                                }
                        }
                        break;
               
                case KEY_FSM_RELEASE:
                        keyHandle->KeyCnt = 0; //复位计数器,状态跳转
                        keyHandle->KeyFsmState = KEY_FSM_WAIT_PRESS;
                        keyHandle->KeyState = KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED);
                        break;

                default:
                        break;
        }
}


状态迁移图如下(部分默认分支没有画出):


KEY_Init函数是用于初始化按键对应的IO,若已有类似功能函数,则该函数就不需要再实现。
KEY_Read_Port函数是读取指定按键对应的IO口电平值,不需要消抖,一次读取即可,该函数需要用户根据硬件自行实现。
/**
* @brief按键初始化操作,如端口配置、按键句柄结构体初始化等
* @paramNone
* @retval None
*/
__weak void KEY_Init(void)
{
        /*
        hkey.KeyId = KEY_ID_1;
        hkey.KeyReadFuncPtr = KeyReadFunc;
        hkey.KeyState = KEY_RELEASED;
        hkey.KeyCnt = 0;
        hkey.KeyFsmState = KEY_FSM_FSTATE(KEY_FSM_WAIT_PRESS, KEY_FSM_WAIT_PRESS);
        */
}

/**
* @brief读取指定按键对应的IO口电平值,不需要消抖,一次读取即可,该函数需要用户根据硬件自行实现
* @param按键的ID值,用户自行编码
* @retval IO口电平值,本实现中1:按键按下,0:按键按下,
*/
__weak uint8_t KEY_Read_Port(uint8_t KeyId)
{
        /*
        //根据KeyId读取指定IO口电平值
        switch(KeyId)
        {
                case KEY_ID_1:
                        return (HAL_GPIO_ReadPin(USEKEY_GPIO_Port, USEKEY_Pin));
                        break;
                default:
                        return 0;
                        break;
        }
        */
        return 0;
}

另外,本代码中最重要的,或者说与其他实现方式不同的地方在于,按键动作识别。
关键在于按键句柄结构体中的KeyState成员,其高4bit为上一个状态,低4bit为当前状态,通过对这个组合字可以识别出按键的具体动作。
定义了短按、长按、连击的组合识别码:
//按键状态组合字(8位),ls为上一个状态(高4位),cs为当前状态(低4位)
#define KEY_FULL_STATE(ls, cs)        (KEY_StateTypeDef)(((ls)<<4)|(cs))
//按键的当前状态值,取按键状态组合字的低4位
#define KEY_CURT_STATE(s)                        (KEY_StateTypeDef)((s)&0x0F)
//按键上一个状态值,取按键状态组合字的高4位
#define KEY_LAST_STATE(s)                        (KEY_StateTypeDef)(((s)&0xF0)>>4)

//按键内部计数组合字(16位),h2用于按下/释放消抖计数(高2位),l14用于长按/连击计数(低14位)
#define KEY_FULL_CNT(h2, l14)                ((((h2)&0x0003)<<14)|((l14)&0x3FFF))
//按键按下/释放消抖计数值
#define KEY_LOW_CNT(c)                        ((c)&0x3FFF)
//按键长按/连击计数值
#define KEY_HIGH_CNT(c)                        (((c)&0xC000)>>14)

//按键短按动作识别字
#define KEY_SHORT_ACTION()                KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_RELEASED)
//按键长按动作识别字
#define KEY_LONG_ACTION()                        KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_RELEASED)
//按键连击动作识别字
#define KEY_REPEAT_ACTION()                KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED)

上述代码最后形成两个文件:key_driver.c和key_driver.h
如何使用该模块:
/**
* @briefmain.c--以单个按键以例,仅把与本模块相关的代码写出,其余代码未写
* @param
* @retval
*/
//包含其他头文件

//包含按键处理模块头文件
#include "key_driver.h"

//定义一个按键句柄结构体,并进行初始化
#define KEY_ID_1 1
KEY_HandleTypeDef hkey = {KEY_ID_1, KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED), 0, KEY_FSM_WAIT_PRESS};

/*若有多个按键,可以定义按键句柄结构体数组
#define KEY_ID_1 1
#define KEY_ID_2 2
#define KEY_ID_3 3
KEY_HandleTypeDef hkey = {{KEY_ID_1, KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED), 0, KEY_FSM_WAIT_PRESS},
                                                       {KEY_ID_2, KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED), 0, KEY_FSM_WAIT_PRESS},
                                                       {KEY_ID_3, KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED), 0, KEY_FSM_WAIT_PRESS}};
*/


//用户根据硬件情况自行实现指定按键的IO电平读取,该函数将覆盖模块中的同名函数
uint8_t KEY_Read_Port(uint8_t KeyId)
{
       
        switch(KeyId)
        {
                case KEY_ID_1:
                        return (HAL_GPIO_ReadPin(USEKEY_GPIO_Port, USEKEY_Pin));
                        break;
                default:
                        return 0;
                        break;
        }
}

//main函数
int main(void)
{
        //按键端口配置,若其他代码已对其进行了配置,则可忽略
        KEY_Init();
       
        //其余代码
       
        while(1)
        {
                //其余代码
               
                //周期性调用按键扫描
                KEY_Scan(&hkey);
               
                //按键动作识别
                if(KEY_SHORT_ACTION() == KEY_Get_State(&hkey)) //短按,点亮led
                        HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
                if(KEY_LONG_ACTION() == KEY_Get_State(&hkey)) //长按,熄灭led
                        HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
                if(KEY_REPEAT_ACTION() == KEY_Get_State(&hkey)) //连击1次,led翻转1下
                        HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
               
                //扫描间隔10ms
                HAL_Delay(10);
        }
       
        return 0;
}


本按键代码模块在STM32L152 Discovery硬件上进行了验证,附源码供参考,欢迎批评指正!{:handshake:}

了无 发表于 2018-9-24 18:32:26

非常不错,有机会试一下

wlmwwx 发表于 2018-9-24 18:35:40

谢谢分享,收藏备用。

haohai 发表于 2018-9-24 18:51:10

楼主的图用什么画的,很不错,说明很清楚!

arndei 发表于 2018-9-24 18:53:24

写的很清楚,记得论坛之前也有一个类似的,程序写的也很不错

dxgdsx 发表于 2018-9-24 19:14:18

haohai 发表于 2018-9-24 18:51
楼主的图用什么画的,很不错,说明很清楚!

是用Visio画的。

shenwrt 发表于 2018-9-24 19:30:28

收藏收藏

机器人天空 发表于 2018-9-24 19:55:54

没有组合键吗

zcllom 发表于 2018-9-24 20:06:00

希望你这个模块站在前人的肩膀上,博采众长,成为集大成者。

dxgdsx 发表于 2018-9-24 20:09:49

机器人天空 发表于 2018-9-24 19:55
没有组合键吗

可以的,定义多个按键,然后对多个键进行组合判断即可(因为手头板子只有一个按键,多键组合未加验证)。

dxgdsx 发表于 2018-9-24 20:15:57

zcllom 发表于 2018-9-24 20:06
希望你这个模块站在前人的肩膀上,博采众长,成为集大成者。

惭愧惭愧,这个是自己练习用到。

makesoft 发表于 2018-9-24 20:27:39

很多场合不能等到按键释放再响应,那样人机交互感受不好。

andy93762 发表于 2018-9-24 20:50:48

非常不错,学习下,了解下

elecfun 发表于 2018-9-24 21:02:44

看起来不错,多谢分享

dxgdsx 发表于 2018-9-24 21:07:03

makesoft 发表于 2018-9-24 20:27
很多场合不能等到按键释放再响应,那样人机交互感受不好。

关于这一点,我也很疑问,个人觉得要看实际项目的需求来定。
事实上,这里的代码是可以获取每一个10ms检测时的按键上一次状态和当前状态,通过设置不同的组合状态字来进行判断,不一定要非等到按键释放以后再操作。
另外,代码只能检测过去,不可能预测未来。

就拿短按来看:


所以,下面几个动作识别字都是用来识别一个独立的操作,短按就是短按,长按就是长按。不能先认为短按,然后继续按着,就认为是长按

//按键短按动作识别字
#define KEY_SHORT_ACTION()                KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_RELEASED)
//按键长按动作识别字
#define KEY_LONG_ACTION()                        KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_RELEASED)
//按键连击动作识别字
#define KEY_REPEAT_ACTION()                KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED)

当然,说到底,还是根据实际项目需求来定。

sjf 发表于 2018-9-24 21:18:58

楼主不错,共享精神,值得肯定!

lisingch 发表于 2018-9-24 21:21:53

这个帖子得好好学习一下。

BS_good200xy 发表于 2018-9-24 21:32:13

正想好好研究一下按键。

wthzack 发表于 2018-9-24 23:52:54

多谢分享   有机会试一下

wajlh 发表于 2018-9-25 02:08:24

收藏一下

dory_m 发表于 2018-9-25 07:41:37

学习,谢谢!!!

daicp 发表于 2018-9-25 08:31:30

感谢楼主无私分享{:handshake:}

eliterxzgxu 发表于 2018-9-25 08:33:05

感谢楼主分享

337zhang 发表于 2018-9-25 08:34:26

好久没见干货了,帮顶一下

peteryzm 发表于 2018-9-25 08:52:30

mark,按键程序
感谢无私分享

AlertTao 发表于 2018-9-25 08:52:41

{:handshake:}{:handshake:}{:handshake:}{:handshake:}

zengyi703 发表于 2018-9-25 08:58:38

收藏备用

2012Ehome 发表于 2018-9-25 09:08:20


谢谢分享,收藏备用+1

mculjf 发表于 2018-9-25 09:11:47

感谢楼主分享

a105 发表于 2018-9-25 09:40:38


收藏备用

asbzhang 发表于 2018-9-25 10:01:16

很详细,不错。

dreambox 发表于 2018-9-25 10:01:43

感谢楼主分享

yangbo18416 发表于 2018-9-25 10:18:43

感谢分享,收藏备用

Aslm 发表于 2018-9-25 10:22:10

按键处理模块,实现短按、长按、连击判断,附源码与图

bbsview 发表于 2018-9-25 10:26:05

不错,值得好好的学习一下!{:handshake:}

stevensun80 发表于 2018-9-25 10:30:37

状态机流程图画的很用心,学习了

jackjiao 发表于 2018-9-25 10:32:42

之前也写过按键的模块,不过有一定的局限性,学习下

pjdu 发表于 2018-9-25 10:44:56


谢谢分享,收藏备用。

l.htlht 发表于 2018-9-25 10:52:46

谢谢分享

Excellence 发表于 2018-9-25 10:58:41

谢谢楼主无私分享。
最近用矩阵键盘,有时间试试。

Excellence 发表于 2018-9-25 10:59:02

4*5矩阵键盘,而且GPIO不是连续的,不知道楼主代码支持吗?

阿豪博士 发表于 2018-9-25 11:17:15

MRARK:按键状态机!

fsmcu 发表于 2018-9-25 11:21:12

有些应用场合很变态,比如长按,还要区分长按不同的时间,需要根据按键释放来测算按下的时间,然后进入相应的动作

yangxizhong 发表于 2018-9-25 11:22:37

学习,谢谢!!!

qinshiysb 发表于 2018-9-25 11:22:41

楼主给力,按键处理

linccfzu 发表于 2018-9-25 11:23:00

收藏学习!!

linux-0405209 发表于 2018-9-25 11:28:37

很详细,很好的资料,谢谢楼主

dxgdsx 发表于 2018-9-25 12:05:44

Excellence 发表于 2018-9-25 10:59
4*5矩阵键盘,而且GPIO不是连续的,不知道楼主代码支持吗?

暂时没有看过矩阵键盘,但是个人觉得实现思路基本差不多的。

dxgdsx 发表于 2018-9-25 12:07:02

fsmcu 发表于 2018-9-25 11:21
有些应用场合很变态,比如长按,还要区分长按不同的时间,需要根据按键释放来测算按下的时间,然后进入相应 ...

对的,所以要根据实际情况来实现。
可以在这个代码基础上增加时间常数与按键状态来拓展。

dxgdsx 发表于 2018-9-25 12:08:59

Excellence 发表于 2018-9-25 10:59
4*5矩阵键盘,而且GPIO不是连续的,不知道楼主代码支持吗?

个人觉得不管什么类型的键盘,只要根据具体硬件把KEY_Read_Port函数实现了,就可以直接使用。

yanzhiwei 发表于 2018-9-25 13:54:53

谢谢分享,收藏备用

OOXX110 发表于 2018-9-25 14:05:14


谢谢分享,收藏备用

qq335702318 发表于 2018-9-25 14:23:19

本帖最后由 qq335702318 于 2018-9-25 14:37 编辑

不错!楼主是花了心思做流程分析的!

我认为的"连击"是短时间内按键 ”按下-弹起-再次按下-再次弹起”,也就是Double-Click

另外,每个按键都占用一个结构体的思路,虽然用起来灵活方便,但不适合RAM空间极小的8位机

dxgdsx 发表于 2018-9-25 15:01:28

qq335702318 发表于 2018-9-25 14:23
不错!楼主是花了心思做流程分析的!

我认为的"连击"是短时间内按键 ”按下-弹起-再次按下-再次弹起”,也 ...

“双击”确实如你所说,但是“连击”我确实没怎么搞明白定义。
比如一些设备上,先长按按键,屏幕上对应的数字闪烁(意味着进入修改状态),继续按着按键,该数字每隔一定时间加1。

结构体的使用,确实对于资源极小的单片机系统不够友好。

oaixuw 发表于 2018-9-25 15:13:01

很好,先收藏了,谢谢!

1785345205 发表于 2018-9-25 15:13:15

谢谢分享

Pjm2008 发表于 2018-9-25 15:27:52

很好,先收藏

雨中的风铃 发表于 2018-9-25 15:32:02

谢谢分享!!!!!!!!!

LK9286 发表于 2018-9-25 15:35:12

感谢,收藏学习

pisgah 发表于 2018-9-25 15:35:47

多谢楼主的分享和详细的图文解说

_yuming 发表于 2018-9-25 18:58:27

收藏,备用。。。虽然目前还用不到。

fuu 发表于 2018-9-25 22:44:47

思路挺好,支持

cc1987 发表于 2018-9-26 08:31:58

{:smile:}谢谢分享

yuguoliang 发表于 2018-9-26 08:33:54

谢谢分享,收藏备用。

a312835782 发表于 2018-9-26 08:37:19

看起来很麻烦

iqxt88 发表于 2018-9-26 08:46:51

不错思路清晰,谢谢分享。

dxgdsx 发表于 2018-9-26 08:47:27

a312835782 发表于 2018-9-26 08:37
看起来很麻烦

之前我看傻孩子老师工作室发布的通用按键模块,源码里面多个状态机loop,然后又是双队列,觉得麻烦,所以精简了一下,只剩下一个状态机loop。队列直接舍去。

当然如果只是读取按键的话,确实没必要这样写。但是毕竟还是想把按键这块做成一个相对独立的模块,所以稍微写的多一点。

你觉得上面代码还有哪部分可以精简一下?{:handshake:}

Jmhh247 发表于 2018-9-26 10:26:20

dxgdsx 发表于 2018-9-26 08:47
之前我看傻孩子老师工作室发布的通用按键模块,源码里面多个状态机loop,然后又是双队列,觉得麻烦,所以 ...

不能一味的精简。

即然都在前人的基础上重写成了“模块”,就把队列加上吧,一步到位。

wushifeng 发表于 2018-9-26 10:27:01

MARK            

dxgdsx 发表于 2018-9-26 10:31:22

Jmhh247 发表于 2018-9-26 10:26
不能一味的精简。

即然都在前人的基础上重写成了“模块”,就把队列加上吧,一步到位。 ...

队列并不是刚需。

jack_yu 发表于 2018-9-26 13:23:06

谢谢楼主分享。学习了

xingjianpeng 发表于 2018-9-26 13:54:46


谢谢楼主分享。学习了

TigerFish 发表于 2018-9-27 08:31:44

这种资料真正有用,感谢

Gorgon_Meducer 发表于 2018-9-27 23:06:47

本帖最后由 Gorgon_Meducer 于 2018-9-27 23:37 编辑

dxgdsx 发表于 2018-9-26 10:31
队列并不是刚需。

首先,非常,非常,非常开心楼主认真的阅读了代码,并根据自己的理解做出了自己的实现,甚至花费宝贵的时间发到我的板块来分享。非常感谢。

其次,关于队列的问题,我想说:“等我书出来,你就知道打脸的感觉了。” 卖个关子。
为了避免被人带节奏催书的问题,这里先放一点原理图,聪明的人应该可以看出使用两个队列的其中一个原因(是的还有别的原因):







结论:通过替换数据处理的前端(Frontend)可以在不改写后续处理和APP的情况下,实现按键模块对不同类型键盘的支持(这是好处之一)。

dxgdsx 发表于 2018-9-28 08:58:51

Gorgon_Meducer 发表于 2018-9-27 23:06
首先,非常,非常,非常开心楼主认真的阅读了代码,并根据自己的理解做出了自己的实现,甚至花费宝贵的时 ...

终于看到大神前来指导了!激动,感谢感谢!

既然逮到大神了,我就抓住机会请教下:

我觉得双FIFO的存在,最好的作用是:用户从FIFO中获取的结果是已经处理好的最终结果,不需要用户进行再次判断。
而我写的里面,用户得到的结果是:上一次扫描到的按键状态 与 本次扫描到的按键状态 的组合状态,这个组合状态字自带了很多有用信息,用它来判断“短按”、“长按”、“连击”的操作是可以的。
但是如果要判断更多动作,可能需要用户二次处理。
所以,我这个严格来讲不能算是独立的模块,只能算是一段可供参考的代码。{:lol:}

ilikemcu 发表于 2018-9-28 09:43:25

makesoft 发表于 2018-9-24 20:27
很多场合不能等到按键释放再响应,那样人机交互感受不好。

有短按键和长按键同时使用的时候,只能取消短按键的这个功能了,也就是短按键必须等待按键释放才能响应,但是长按键可以在时间到达后立即响应,而无需等待按键释放。

在按键数量极其有限的情况下,楼主的方法是很有效律的,我的设计里用2个按键,也是实现了类似的长按,短按,连击等等功能,不过由于按键功能是逐步添加的,整个按键的处理代码都是分散加载的。

makesoft 发表于 2018-9-28 10:48:23

Gorgon_Meducer 发表于 2018-9-27 23:06
首先,非常,非常,非常开心楼主认真的阅读了代码,并根据自己的理解做出了自己的实现,甚至花费宝贵的时 ...

一直按键处理使用类似方法,最大的好处是做好按键识别和按键使用分离,增加了系统的可靠性和灵活性。

lrzxc 发表于 2018-9-28 11:17:10

嗯,收藏了

Gorgon_Meducer 发表于 2018-9-28 21:20:38

dxgdsx 发表于 2018-9-28 08:58
终于看到大神前来指导了!激动,感谢感谢!

既然逮到大神了,我就抓住机会请教下:


我注意到了你在提供给客户的内容中加入了状态信息,但是这里有个问题,就是你没有办法
携带时间信息。而时间信息是很多用户行为识别的关键。另外,如果你仔细看我提供的图,
你会明白,这里队列存在的目的主要是两点:隔离和接口去耦。

我的理解是,队列先进先出的特性已经实际上保留了事件之间的前后状态,所以不必在一个
按键report中包含前后的相邻状态。同样,如果真的考虑应用可能需要这些信息,只保存一级
恐怕是不够的……最后用户还是要根据实际情况去队列里挖掘,而这个挖掘的过程还是离不开
使用状态机。

为了解决这个问题,我的处理方法实际上是,在保留各原始的按键事件的前提下,通过逐级
追加数据处理,根据已有的案件事件来进行模式识别,提取出更有用的按键事件,并追加到
事件流里面——比如,双击的处理就是根据单击的事件配合时间信息来实现的。

这里我想强调的是,逐级处理信息,由简单到抽象,这正是人类大脑进行信息处理的方式。
比如,视网膜第一级,简单的对光线和颜色敏感,第二级神经网络将第一集的点信息处理成
线条信息,第三极则对将点和线的信息处理成运动的矢量信息。你可以看出这里明显的逐级
处理,逐级追加有用信息,逐级抽象的过程。

我觉得你现在最大的问题是,太专注于按键扫描本身了。从技术的角度说需要适当扩大一点
思考的范围。

dxgdsx 发表于 2018-9-29 09:50:39

Gorgon_Meducer 发表于 2018-9-28 21:20
我注意到了你在提供给客户的内容中加入了状态信息,但是这里有个问题,就是你没有办法
携带时间信息。而 ...

真的非常受教,感谢!

逐级增加信息,逐级进行处理,确实是人类大脑认知、处理事务最有效的方式。大脑虽然厉害,但是一下子面对复杂事务却很难有效处理,经过分层逐级处理,可以将复杂问题分解成若干简单问题,然后逐步处理,最后解决复杂问题。
太受教了!

hubinghuandi 发表于 2018-9-29 11:58:11


学习,谢谢!!!

gongngei 发表于 2018-9-29 12:05:36

学习了,谢谢

bitter_rain 发表于 2018-9-29 14:22:45

不错,赞赞赞!

liaihua1997 发表于 2018-9-29 16:11:37

这个程序长按的时候会有响应短按的时间窗口吗?

dxgdsx 发表于 2018-9-29 16:13:07

liaihua1997 发表于 2018-9-29 16:11
这个程序长按的时候会有响应短按的时间窗口吗?

不会。
但是可以很容易实现这个功能。

zd0305 发表于 2018-10-15 10:27:26

值得学习!!!!!!!好贴

sandeant 发表于 2018-10-16 14:49:57

好贴!!!!!!!!

a673261839 发表于 2018-11-1 10:05:15


学习,谢谢!!!

HJMB 发表于 2018-11-2 08:37:40

好帖子,非常实用!谢谢!

minier 发表于 2018-11-2 09:40:06

嗯!借助实践触发和状态机,二合一就既有FSM思想好理解,又有时间控制,实现不同的击键要求

四川李工 发表于 2018-11-2 10:07:25

mark、以下!!!!!!!!!

TKZXJ 发表于 2019-1-23 12:08:27

收藏一下

jackjiao 发表于 2019-1-23 12:16:34

也写过一个类似的key queue驱动,学习学习

bsz84 发表于 2019-1-23 12:44:38

不错,值得学习一下!

zbx6020 发表于 2019-1-23 12:46:28

不错,值得学习一下!

d314361768 发表于 2019-1-23 12:55:44

收藏下!!!

zhongsandaoren 发表于 2019-1-23 13:23:53

按键处理程序 Mark

片羽之神 发表于 2019-6-6 22:20:47

mark~~有机会研究一下~

lizuqing 发表于 2019-6-7 09:35:31

按键处理程序 Mark

waymcu 发表于 2019-6-7 17:00:12


按键处理程序 Mark
页: [1] 2
查看完整版本: 按键处理模块,实现短按、长按、连击判断,附源码与图...