搜索
bottom↓
回复: 122

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

  [复制链接]

出0入0汤圆

发表于 2018-9-24 17:29:32 | 显示全部楼层 |阅读模式
本帖最后由 dxgdsx 于 2018-9-24 19:41 编辑

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

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

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

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


设计了一个按键扫描处理状态机,共有5个状态编码:
  1. //按键扫描状态机状态编码
  2. typedef enum
  3. {
  4.         //按键状态机:“按键等待被按下”处理
  5.         KEY_FSM_WAIT_PRESS = 0,
  6.         //按键状态机:“按键短按”处理
  7.         KEY_FSM_SHORT_PRESS,
  8.         //按键状态机:“按键长按”处理
  9.         KEY_FSM_LONG_PRESS,
  10.         //按键状态机:“按键连击”处理
  11.         KEY_FSM_REPEAT_PRESS,
  12.         //按键状态机:“按键释放”处理
  13.         KEY_FSM_RELEASE
  14. }KEY_FsmTypeDef;
复制代码


定义按键的不同,共有6个状态编码:
  1. //按键状态编码
  2. typedef enum
  3. {
  4.         //按键状态:释放状态
  5.         KEY_RELEASED = 0,
  6.         //按键状态:短按状态
  7.         KEY_SHORT_PRESSED,
  8.         //按键状态:等待长按状态
  9.         KEY_LONG_PRESS_WAIT,
  10.         //按键状态:长按状态
  11.         KEY_LONG_PRESSED,
  12.         //按键状态:等待连击状态
  13.         KEY_REPEAT_PRESS_WAIT,
  14.         //按键状态:一次连击状态
  15.         KEY_REPEAT_PRESSED
  16. }KEY_StateTypeDef;
复制代码


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


声明一些函数:
  1. //按键端口读取函数声明,需要用户根据硬件情况自行实现
  2. uint8_t KEY_Read_Port(uint8_t KeyId);
  3. //按键初始化函数声明
  4. void KEY_Init(void);
  5. //按键扫描函数声明
  6. void KEY_Scan(KEY_HandleTypeDef* keyHandle);
  7. //按键状态获取函数声明
  8. KEY_StateTypeDef KEY_Get_State(KEY_HandleTypeDef* keyHandle);
复制代码


其中,关键函数是KEY_Scan函数,事实上其它几个函数无关紧要,存在更多只是为了形式上的完整
KEY_Scan函数就是一个状态机处理函数,源代码如下:
  1. /**
  2.   * @brief  单个按键处理函数,需要被周期性调用,建议10ms扫描一次
  3.   * @param  指定单个按键的句柄
  4.   * @retval None
  5.   */
  6. void KEY_Scan(KEY_HandleTypeDef* keyHandle)
  7. {
  8.         //读取按键端口电平
  9.         uint8_t IsKeyDown = KEY_Read_Port(keyHandle->KeyId);
  10.        
  11.         //获取状态机的当前状态
  12.         switch(keyHandle->KeyFsmState)
  13.         {
  14.                 case KEY_FSM_WAIT_PRESS:
  15.                         if(IsKeyDown) //检测到按键按下
  16.                         {
  17.                                 keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
  18.                                 //按键按下消抖:连续几次检测到按键按下,可以确认按键按下
  19.                                 if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_PRESS_DB_CNT)
  20.                                 {
  21.                                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  22.                                         keyHandle->KeyFsmState = KEY_FSM_SHORT_PRESS;
  23.                                         keyHandle->KeyState = KEY_FULL_STATE(KEY_RELEASED, KEY_SHORT_PRESSED);
  24.                                 }
  25.                         }
  26.                         else
  27.                                 keyHandle->KeyCnt = 0; //检测到按键释放,复位计数器
  28.                         break;
  29.                
  30.                 case KEY_FSM_SHORT_PRESS:
  31.                         keyHandle->KeyState = KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_LONG_PRESS_WAIT);
  32.                         if(!IsKeyDown)  //检测到按键释放
  33.                         {
  34.                                 keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
  35.                                 //按键释放消抖:连续几次检测到按键释放,可以确认按键释放
  36.                                 if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_RELEASE_DB_CNT)
  37.                                 {
  38.                                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  39.                                         keyHandle->KeyFsmState = KEY_FSM_RELEASE;
  40.                                         keyHandle->KeyState = KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_RELEASED);
  41.                                 }
  42.                         }
  43.                         else
  44.                         {
  45.                                 keyHandle->KeyCnt = KEY_FULL_CNT(0, KEY_LOW_CNT(keyHandle->KeyCnt)+1);
  46.                                 if(KEY_LOW_CNT(keyHandle->KeyCnt) >= KEY_LONG_PRESS_CNT) //可以确认按键长按
  47.                                 {
  48.                                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  49.                                         keyHandle->KeyFsmState = KEY_FSM_LONG_PRESS;
  50.                                         keyHandle->KeyState = KEY_FULL_STATE(KEY_LONG_PRESS_WAIT, KEY_LONG_PRESSED);
  51.                                 }
  52.                         }
  53.                         break;
  54.                
  55.                 case KEY_FSM_LONG_PRESS:
  56.                         keyHandle->KeyState = KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_REPEAT_PRESS_WAIT);
  57.                         if(!IsKeyDown)  //检测到按键释放
  58.                         {
  59.                                 keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
  60.                                 //按键释放消抖:连续几次检测到按键释放,可以确认按键释放
  61.                                 if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_RELEASE_DB_CNT)
  62.                                 {
  63.                                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  64.                                         keyHandle->KeyFsmState = KEY_FSM_RELEASE;
  65.                                         keyHandle->KeyState = KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_RELEASED);
  66.                                 }
  67.                         }
  68.                         else
  69.                         {
  70.                                 keyHandle->KeyCnt = KEY_FULL_CNT(0, KEY_LOW_CNT(keyHandle->KeyCnt)+1);
  71.                                 if(KEY_LOW_CNT(keyHandle->KeyCnt) >= KEY_REPEAT_PRESS_CNT) //可以确认按键连击
  72.                                 {
  73.                                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  74.                                         keyHandle->KeyFsmState = KEY_FSM_REPEAT_PRESS;
  75.                                         keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED);
  76.                                 }
  77.                         }
  78.                         break;
  79.                
  80.                 case KEY_FSM_REPEAT_PRESS:
  81.                         keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESSED, KEY_REPEAT_PRESS_WAIT);
  82.                         if(!IsKeyDown)  //检测到按键释放
  83.                         {
  84.                                 keyHandle->KeyCnt = KEY_FULL_CNT(KEY_HIGH_CNT(keyHandle->KeyCnt)+1, 0);
  85.                                 //按键释放消抖:连续几次检测到按键释放,可以确认按键释放
  86.                                 if(KEY_HIGH_CNT(keyHandle->KeyCnt) >= KEY_RELEASE_DB_CNT)
  87.                                 {
  88.                                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  89.                                         keyHandle->KeyFsmState = KEY_FSM_RELEASE;
  90.                                         keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_RELEASED);
  91.                                 }
  92.                         }
  93.                         else
  94.                         {
  95.                                 keyHandle->KeyCnt = KEY_FULL_CNT(0, KEY_LOW_CNT(keyHandle->KeyCnt)+1);
  96.                                 if(KEY_LOW_CNT(keyHandle->KeyCnt) >= KEY_REPEAT_PRESS_CNT) //可以确认按键连击
  97.                                 {
  98.                                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  99.                                         keyHandle->KeyFsmState = KEY_FSM_REPEAT_PRESS;
  100.                                         keyHandle->KeyState = KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED);
  101.                                 }
  102.                         }
  103.                         break;
  104.                
  105.                 case KEY_FSM_RELEASE:
  106.                         keyHandle->KeyCnt = 0; //复位计数器,状态跳转
  107.                         keyHandle->KeyFsmState = KEY_FSM_WAIT_PRESS;
  108.                         keyHandle->KeyState = KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED);
  109.                         break;

  110.                 default:
  111.                         break;
  112.         }
  113. }
复制代码


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


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

  16. /**
  17.   * @brief  读取指定按键对应的IO口电平值,不需要消抖,一次读取即可,该函数需要用户根据硬件自行实现
  18.   * @param  按键的ID值,用户自行编码
  19.   * @retval IO口电平值,本实现中1:按键按下,0:按键按下,
  20.   */
  21. __weak uint8_t KEY_Read_Port(uint8_t KeyId)
  22. {
  23.         /*
  24.         //根据KeyId读取指定IO口电平值
  25.         switch(KeyId)
  26.         {
  27.                 case KEY_ID_1:
  28.                         return (HAL_GPIO_ReadPin(USEKEY_GPIO_Port, USEKEY_Pin));
  29.                         break;
  30.                 default:
  31.                         return 0;
  32.                         break;
  33.         }
  34.         */
  35.         return 0;
  36. }
复制代码


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

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

  13. //按键短按动作识别字
  14. #define KEY_SHORT_ACTION()                KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_RELEASED)
  15. //按键长按动作识别字
  16. #define KEY_LONG_ACTION()                        KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_RELEASED)
  17. //按键连击动作识别字
  18. #define KEY_REPEAT_ACTION()                KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED)
复制代码


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

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

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

  12. /*若有多个按键,可以定义按键句柄结构体数组
  13. #define KEY_ID_1 1
  14. #define KEY_ID_2 2
  15. #define KEY_ID_3 3
  16. KEY_HandleTypeDef hkey[3] = {{KEY_ID_1, KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED), 0, KEY_FSM_WAIT_PRESS},
  17.                                                          {KEY_ID_2, KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED), 0, KEY_FSM_WAIT_PRESS},
  18.                                                          {KEY_ID_3, KEY_FULL_STATE(KEY_RELEASED, KEY_RELEASED), 0, KEY_FSM_WAIT_PRESS}};
  19. */


  20. //用户根据硬件情况自行实现指定按键的IO电平读取,该函数将覆盖模块中的同名函数
  21. uint8_t KEY_Read_Port(uint8_t KeyId)
  22. {
  23.        
  24.         switch(KeyId)
  25.         {
  26.                 case KEY_ID_1:
  27.                         return (HAL_GPIO_ReadPin(USEKEY_GPIO_Port, USEKEY_Pin));
  28.                         break;
  29.                 default:
  30.                         return 0;
  31.                         break;
  32.         }
  33. }

  34. //main函数
  35. int main(void)
  36. {
  37.         //按键端口配置,若其他代码已对其进行了配置,则可忽略
  38.         KEY_Init();
  39.        
  40.         //其余代码
  41.        
  42.         while(1)
  43.         {
  44.                 //其余代码
  45.                
  46.                 //周期性调用按键扫描
  47.                 KEY_Scan(&hkey);
  48.                
  49.                 //按键动作识别
  50.                 if(KEY_SHORT_ACTION() == KEY_Get_State(&hkey)) //短按,点亮led
  51.                         HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
  52.                 if(KEY_LONG_ACTION() == KEY_Get_State(&hkey)) //长按,熄灭led
  53.                         HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
  54.                 if(KEY_REPEAT_ACTION() == KEY_Get_State(&hkey)) //连击1次,led翻转1下
  55.                         HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
  56.                
  57.                 //扫描间隔10ms
  58.                 HAL_Delay(10);
  59.         }
  60.        
  61.         return 0;
  62. }
复制代码


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入8汤圆

发表于 2018-9-24 18:32:26 来自手机 | 显示全部楼层
非常不错,有机会试一下

出0入0汤圆

发表于 2018-9-24 18:35:40 | 显示全部楼层
谢谢分享,收藏备用。

出0入0汤圆

发表于 2018-9-24 18:51:10 | 显示全部楼层
楼主的图用什么画的,很不错,说明很清楚!

出0入0汤圆

发表于 2018-9-24 18:53:24 | 显示全部楼层
写的很清楚,记得论坛之前也有一个类似的,程序写的也很不错

出0入0汤圆

 楼主| 发表于 2018-9-24 19:14:18 | 显示全部楼层
haohai 发表于 2018-9-24 18:51
楼主的图用什么画的,很不错,说明很清楚!

是用Visio画的。

出0入0汤圆

发表于 2018-9-24 19:30:28 来自手机 | 显示全部楼层
收藏收藏

出0入0汤圆

发表于 2018-9-24 19:55:54 来自手机 | 显示全部楼层
没有组合键吗

出330入0汤圆

发表于 2018-9-24 20:06:00 来自手机 | 显示全部楼层
希望你这个模块站在前人的肩膀上,博采众长,成为集大成者。

出0入0汤圆

 楼主| 发表于 2018-9-24 20:09:49 | 显示全部楼层

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

出0入0汤圆

 楼主| 发表于 2018-9-24 20:15:57 | 显示全部楼层
zcllom 发表于 2018-9-24 20:06
希望你这个模块站在前人的肩膀上,博采众长,成为集大成者。

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

出10入120汤圆

发表于 2018-9-24 20:27:39 来自手机 | 显示全部楼层
很多场合不能等到按键释放再响应,那样人机交互感受不好。

出0入0汤圆

发表于 2018-9-24 20:50:48 来自手机 | 显示全部楼层
非常不错,学习下,了解下

出0入134汤圆

发表于 2018-9-24 21:02:44 | 显示全部楼层
看起来不错,多谢分享

出0入0汤圆

 楼主| 发表于 2018-9-24 21:07:03 | 显示全部楼层
makesoft 发表于 2018-9-24 20:27
很多场合不能等到按键释放再响应,那样人机交互感受不好。

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

就拿短按来看:


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

  1. //按键短按动作识别字
  2. #define KEY_SHORT_ACTION()                KEY_FULL_STATE(KEY_SHORT_PRESSED, KEY_RELEASED)
  3. //按键长按动作识别字
  4. #define KEY_LONG_ACTION()                        KEY_FULL_STATE(KEY_LONG_PRESSED, KEY_RELEASED)
  5. //按键连击动作识别字
  6. #define KEY_REPEAT_ACTION()                KEY_FULL_STATE(KEY_REPEAT_PRESS_WAIT, KEY_REPEAT_PRESSED)
复制代码


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2018-9-24 21:18:58 | 显示全部楼层
楼主不错,共享精神,值得肯定!

出0入0汤圆

发表于 2018-9-24 21:21:53 | 显示全部楼层
这个帖子得好好学习一下。

出0入4汤圆

发表于 2018-9-24 21:32:13 | 显示全部楼层
正想好好研究一下按键。

出0入0汤圆

发表于 2018-9-24 23:52:54 | 显示全部楼层
多谢分享   有机会试一下

出0入4汤圆

发表于 2018-9-25 02:08:24 | 显示全部楼层
收藏一下

出0入0汤圆

发表于 2018-9-25 07:41:37 | 显示全部楼层
学习,谢谢!!!

出0入0汤圆

发表于 2018-9-25 08:31:30 | 显示全部楼层
感谢楼主无私分享

出0入0汤圆

发表于 2018-9-25 08:33:05 来自手机 | 显示全部楼层
感谢楼主分享

出0入0汤圆

发表于 2018-9-25 08:34:26 | 显示全部楼层
好久没见干货了,帮顶一下

出250入17汤圆

发表于 2018-9-25 08:52:30 | 显示全部楼层
mark,按键程序
感谢无私分享

出0入0汤圆

发表于 2018-9-25 08:52:41 | 显示全部楼层

出0入0汤圆

发表于 2018-9-25 08:58:38 | 显示全部楼层
收藏备用

出0入0汤圆

发表于 2018-9-25 09:08:20 | 显示全部楼层

谢谢分享,收藏备用+1

出0入13汤圆

发表于 2018-9-25 09:11:47 | 显示全部楼层
感谢楼主分享

出0入0汤圆

发表于 2018-9-25 09:40:38 | 显示全部楼层

收藏备用

出0入0汤圆

发表于 2018-9-25 10:01:16 | 显示全部楼层
很详细,不错。

出0入0汤圆

发表于 2018-9-25 10:01:43 | 显示全部楼层
感谢楼主分享

出0入0汤圆

发表于 2018-9-25 10:18:43 | 显示全部楼层
感谢分享,收藏备用

出0入0汤圆

发表于 2018-9-25 10:22:10 | 显示全部楼层
按键处理模块,实现短按、长按、连击判断,附源码与图

出0入0汤圆

发表于 2018-9-25 10:26:05 | 显示全部楼层
不错,值得好好的学习一下!

出0入0汤圆

发表于 2018-9-25 10:30:37 | 显示全部楼层
状态机流程图画的很用心,学习了

出0入0汤圆

发表于 2018-9-25 10:32:42 | 显示全部楼层
之前也写过按键的模块,不过有一定的局限性,学习下

出0入0汤圆

发表于 2018-9-25 10:44:56 | 显示全部楼层

谢谢分享,收藏备用。

出0入0汤圆

发表于 2018-9-25 10:52:46 | 显示全部楼层
谢谢分享

出0入0汤圆

发表于 2018-9-25 10:58:41 | 显示全部楼层
谢谢楼主无私分享。
最近用矩阵键盘,有时间试试。

出0入0汤圆

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

出0入0汤圆

发表于 2018-9-25 11:17:15 | 显示全部楼层
MRARK:按键状态机!

出0入0汤圆

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

出0入0汤圆

发表于 2018-9-25 11:22:37 | 显示全部楼层
学习,谢谢!!!

出0入0汤圆

发表于 2018-9-25 11:22:41 | 显示全部楼层
楼主给力,按键处理

出100入976汤圆

发表于 2018-9-25 11:23:00 | 显示全部楼层
收藏学习!!

出0入0汤圆

发表于 2018-9-25 11:28:37 | 显示全部楼层
很详细,很好的资料,谢谢楼主

出0入0汤圆

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

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

出0入0汤圆

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

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

出0入0汤圆

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

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

出0入0汤圆

发表于 2018-9-25 13:54:53 | 显示全部楼层
谢谢分享,收藏备用

出0入12汤圆

发表于 2018-9-25 14:05:14 | 显示全部楼层

谢谢分享,收藏备用

出0入0汤圆

发表于 2018-9-25 14:23:19 | 显示全部楼层
本帖最后由 qq335702318 于 2018-9-25 14:37 编辑

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

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

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

出0入0汤圆

 楼主| 发表于 2018-9-25 15:01:28 | 显示全部楼层
qq335702318 发表于 2018-9-25 14:23
不错!楼主是花了心思做流程分析的!

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

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

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

出0入0汤圆

发表于 2018-9-25 15:13:01 | 显示全部楼层
很好,先收藏了,谢谢!

出180入0汤圆

发表于 2018-9-25 15:13:15 | 显示全部楼层
谢谢分享

出0入14汤圆

发表于 2018-9-25 15:27:52 | 显示全部楼层
很好,先收藏

出0入0汤圆

发表于 2018-9-25 15:32:02 | 显示全部楼层
谢谢分享!!!!!!!!!

出0入0汤圆

发表于 2018-9-25 15:35:12 | 显示全部楼层
感谢,收藏学习

出0入0汤圆

发表于 2018-9-25 15:35:47 | 显示全部楼层
多谢楼主的分享和详细的图文解说

出0入0汤圆

发表于 2018-9-25 18:58:27 | 显示全部楼层
收藏,备用。。。虽然目前还用不到。

出0入0汤圆

发表于 2018-9-25 22:44:47 | 显示全部楼层
思路挺好,支持

出0入0汤圆

发表于 2018-9-26 08:31:58 | 显示全部楼层
谢谢分享

出0入0汤圆

发表于 2018-9-26 08:33:54 | 显示全部楼层
谢谢分享,收藏备用。

出0入0汤圆

发表于 2018-9-26 08:37:19 | 显示全部楼层
看起来很麻烦

出0入0汤圆

发表于 2018-9-26 08:46:51 | 显示全部楼层
不错思路清晰,谢谢分享。

出0入0汤圆

 楼主| 发表于 2018-9-26 08:47:27 | 显示全部楼层

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

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

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

出0入8汤圆

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

不能一味的精简。

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

出0入0汤圆

发表于 2018-9-26 10:27:01 | 显示全部楼层
MARK            

出0入0汤圆

 楼主| 发表于 2018-9-26 10:31:22 | 显示全部楼层
Jmhh247 发表于 2018-9-26 10:26
不能一味的精简。

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

队列并不是刚需。

出0入0汤圆

发表于 2018-9-26 13:23:06 来自手机 | 显示全部楼层
谢谢楼主分享。学习了

出0入0汤圆

发表于 2018-9-26 13:54:46 | 显示全部楼层

谢谢楼主分享。学习了

出0入16汤圆

发表于 2018-9-27 08:31:44 | 显示全部楼层
这种资料真正有用,感谢

出0入296汤圆

发表于 2018-9-27 23:06:47 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2018-9-27 23:37 编辑
dxgdsx 发表于 2018-9-26 10:31
队列并不是刚需。


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

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







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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

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

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

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

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

出0入50汤圆

发表于 2018-9-28 09:43:25 | 显示全部楼层
makesoft 发表于 2018-9-24 20:27
很多场合不能等到按键释放再响应,那样人机交互感受不好。

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

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

出10入120汤圆

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

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

出0入0汤圆

发表于 2018-9-28 11:17:10 | 显示全部楼层
嗯,收藏了

出0入296汤圆

发表于 2018-9-28 21:20:38 | 显示全部楼层
dxgdsx 发表于 2018-9-28 08:58
终于看到大神前来指导了!激动,感谢感谢!

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

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

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

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

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

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

出0入0汤圆

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

真的非常受教,感谢!

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

出0入0汤圆

发表于 2018-9-29 11:58:11 | 显示全部楼层

学习,谢谢!!!

出0入0汤圆

发表于 2018-9-29 12:05:36 | 显示全部楼层
学习了,谢谢

出0入0汤圆

发表于 2018-9-29 14:22:45 | 显示全部楼层
不错,赞赞赞!

出0入8汤圆

发表于 2018-9-29 16:11:37 | 显示全部楼层
这个程序长按的时候会有响应短按的时间窗口吗?

出0入0汤圆

 楼主| 发表于 2018-9-29 16:13:07 | 显示全部楼层
liaihua1997 发表于 2018-9-29 16:11
这个程序长按的时候会有响应短按的时间窗口吗?

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

出0入0汤圆

发表于 2018-10-15 10:27:26 | 显示全部楼层
值得学习!!!!!!!好贴

出0入0汤圆

发表于 2018-10-16 14:49:57 | 显示全部楼层
好贴!!!!!!!!

出0入0汤圆

发表于 2018-11-1 10:05:15 | 显示全部楼层

学习,谢谢!!!

出0入0汤圆

发表于 2018-11-2 08:37:40 | 显示全部楼层
好帖子,非常实用!谢谢!

出0入0汤圆

发表于 2018-11-2 09:40:06 | 显示全部楼层
嗯!借助实践触发和状态机,二合一就既有FSM思想好理解,又有时间控制,实现不同的击键要求

出0入0汤圆

发表于 2018-11-2 10:07:25 | 显示全部楼层
mark、以下!!!!!!!!!

出0入0汤圆

发表于 2019-1-23 12:08:27 | 显示全部楼层
收藏一下

出0入0汤圆

发表于 2019-1-23 12:16:34 | 显示全部楼层
也写过一个类似的key queue驱动,学习学习

出0入0汤圆

发表于 2019-1-23 12:44:38 | 显示全部楼层
不错,值得学习一下!

出0入0汤圆

发表于 2019-1-23 12:46:28 来自手机 | 显示全部楼层
不错,值得学习一下!

出0入0汤圆

发表于 2019-1-23 12:55:44 | 显示全部楼层
收藏下!!!

出0入0汤圆

发表于 2019-1-23 13:23:53 | 显示全部楼层
按键处理程序 Mark

出0入0汤圆

发表于 2019-6-6 22:20:47 | 显示全部楼层
mark~~有机会研究一下~

出0入0汤圆

发表于 2019-6-7 09:35:31 | 显示全部楼层
按键处理程序 Mark

出0入0汤圆

发表于 2019-6-7 17:00:12 | 显示全部楼层

按键处理程序 Mark
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-6-18 11:27

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表