搜索
bottom↓
回复: 42

全功能按键处理软件模块(KEYIF)新版

  [复制链接]

出870入263汤圆

发表于 2012-5-26 15:00:39 | 显示全部楼层 |阅读模式
旧版KEYIF是比较耗费内存资源的,新版在这个问题上给予很大优化,优化后每按键仅需10bits!
请到作者博客进行学习,有详细的代码和分析!
http://www.cnblogs.com/embeddedtech/archive/2012/05/26/2519160.html

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

知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)

出870入263汤圆

 楼主| 发表于 2015-4-25 19:54:23 | 显示全部楼层
再次上传源代码,以便大家下载:

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2012-5-26 15:16:42 | 显示全部楼层
有违反坛规之嫌,还是贴过来吧。

出870入263汤圆

 楼主| 发表于 2012-5-26 15:41:59 | 显示全部楼层
本帖最后由 armstrong 于 2012-5-26 15:43 编辑

在单片机开发中,由于资源受限而没有平台的支持,每次开发都要重写很多代码,应用的千奇百怪的需求更是加剧了这种困难。解决问题的办法是,总结常见的需求,分析它,得出即高效有通用的解决方案。
今天我就来为大家提供一种按键的解决方案,它易用,高效,节省资源!
先给出这个按键模块解决方案的全部代码,稍后再来分析。
keyif.h内容:
   1:  #ifndef__KEY_IF_H__
   2:  #define__KEY_IF_H__
   3:  ////////////////////////////////////////////////////////////////////////////////
   4:  typedefunsigned char   u8_t;
   5:   
   6:  #defineKEY_NUM_MAX     (8)
   7:   
   8:  #defineKEY_STA_BEGIN   (0)
   9:  #defineKEY_STA_KEEP    (1)
  10:  #defineKEY_STA_END     (2)
  11:   
  12:  #defineKEY_PERIOD_MS   (25)
  13:   
  14:  voidkif_Init(void);
  15:  voidkif_TickHook(void);
  16:   
  17:  /*
  18:      kindex 按键索引,从0开始,而小于KEY_NUM_MAX。
  19:      返回值:按键被按下返回非零,按键被抬起返回零。
  20:  */
  21:  externu8_t KeyRead(u8_t kindex);
  22:  /*
  23:      kindex 按键索引,从0开始,而小于KEY_NUM_MAX。
  24:      ksta  按键状态,取值为KEY_STA_系列宏。
  25:      ktick 按键计时,以KEY_PERIOD_MS时间为计数单位。
  26:      返回值:如果本次按键操作已经处理妥当,就返回非零。
  27:  */
  28:  externu8_t KeyProc(u8_t kindex, u8_t ksta, u8_t ktick);
  29:   
  30:  ////////////////////////////////////////////////////////////////////////////////
  31:  #endif/* __KEY_IF_H__ */
  32:   
keyif.c内容:
   1:  #include "keyif.h"
   2:  #include <string.h>
   3:  
   4:  #if defined(KEY_NUM_MAX) && (KEY_NUM_MAX> 0)
   5:  
   6:  #if KEY_NUM_MAX > 255
   7:  #error 该模块为8位单片机优化,不支持255个以上按键。
   8:  #endif
   9:  
  10:  static u8_t kticks[KEY_NUM_MAX];            // 每个按键需要1字节计时器。
  11:  static u8_t kstats[(KEY_NUM_MAX+7)/8];      // 每个按键需要1BIT的状态标志。
  12:  static u8_t kvalid[(KEY_NUM_MAX+7)/8];      // 每个按键需要1BIT的有效标志。
  13:  ////////////////////////////////////////////////////////////////////////////////
  14:  //|          |
  15:  //| 函数名称 |: kif_Init
  16:  //| 功能描述 |:
  17:  //|          |:
  18:  //| 参数列表 |:
  19:  //|          |:
  20:  //|     |:
  21:  //|          |:
  22:  //| 备注信息 |:
  23:  //|          |:
  24:  ////////////////////////////////////////////////////////////////////////////////
  25:  void kif_Init(void)
  26:  {
  27:      memset(kticks, 0, sizeof(kticks));
  28:      memset(kstats, 0, sizeof(kstats));
  29:      memset(kvalid, 0, sizeof(kvalid));
  30:  }
  31:  
  32:  ////////////////////////////////////////////////////////////////////////////////
  33:  //|          |
  34:  //| 函数名称 |: kif_TickHook
  35:  //| 功能描述 |:
  36:  //|          |:
  37:  //| 参数列表 |:
  38:  //|          |:
  39:  //|     |:
  40:  //|          |:
  41:  //| 备注信息 |:
  42:  //|          |:
  43:  ////////////////////////////////////////////////////////////////////////////////
  44:  void kif_TickHook(void)
  45:  {
  46:      u8_t grp, msk;
  47:      u8_tnow;
  48:      u8_t i;
  49:     
  50:      grp = 0;
  51:      msk = 1;
  52:      for(i = 0; i < KEY_NUM_MAX; ++i){
  53:          now = (KeyRead(i) ? msk : 0);
  54:          if((kstats[grp] ^ now) & msk){
  55:              // 按键状态发生变化。
  56:              if(now){
  57:                  // 按键刚被按下。
  58:                  kticks = 0;
  59:                  kstats[grp] |= msk;
  60:                  kvalid[grp] |= msk;
  61:                  if(KeyProc(i, KEY_STA_BEGIN, 0)){
  62:                      kvalid[grp] &= ~msk;
  63:                  }
  64:              }
  65:              else{
  66:                  // 按键刚被抬起。
  67:                  kticks += 1;
  68:                  kstats[grp] &= ~msk;
  69:                  KeyProc(i, KEY_STA_END,kticks);
  70:              }
  71:          }
  72:          else if(now){
  73:              // 按键保持按下状态。
  74:              kticks += 1;
  75:              if(kvalid[grp] & msk){
  76:                  // 按键处于有效状态。
  77:                  if(KeyProc(i, KEY_STA_KEEP, kticks)){
  78:                      kvalid[grp] &= ~msk;
  79:                  }
  80:              }
  81:          }
  82:          // 处理用于加速执行的中间变量。
  83:          msk <<= 1;
  84:          if(msk == 0){
  85:              msk = 1;
  86:              grp++;
  87:          }
  88:      }
  89:  }
  90:  
  91:  #else /* KEY_NUM_MAX */
  92:  
  93:  void kif_Init(void){ ; }
  94:  void kif_TickHook(void){ ; }
  95:  
  96:  #endif /* KEY_NUM_MAX */
  97:  
example.c内容:
  1:  #include "keyif.h"
   2:  
   3:  // 读取按键物理状态的函数。
   4:  u8_t KeyRead(u8_t kindex)
   5:  {
   6:      switch(kindex){
   7:      case 0:    // 按键#0
   8:          if(PIN_DOWN(0)){
   9:              return 1;
  10:          }
  11:          return 0;
  12:      case 1:    // 按键#1
  13:          if(PIN_DOWN(1)){
  14:              return 1;
  15:          }
  16:          return 0;
  17:      case 2:    // 按键#2
  18:          if(PIN_DOWN(2)){
  19:              return 1;
  20:          }
  21:          return 0;
  22:      case 3:    // 按键#3
  23:          if(PIN_DOWN(3)){
  24:              return 1;
  25:          }
  26:          return 0;
  27:      }
  28:      return 0;
  29:  }
  30:  
  31:  // 按键事件处理函数。
  32:  u8_t KeyProc(u8_t kindex, u8_t ksta, u8_tktick)
  33:  {
  34:      switch(kindex){
  35:      case 0:    // 按键#0
  36:          if(ksta == KEY_STA_BEGIN){
  37:              // 按键被按下,TODO SOMETHING
  38:              
  39:              return 0;
  40:          }
  41:          else if(ksta == KEY_STA_KEEP){
  42:              // 按键被保持,TODO SOMETHING
  43:              
  44:              return 0;
  45:          }
  46:          else if(ksta == KEY_STA_END){
  47:              // 按键被松开,TODO SOMETHING
  48:              
  49:              return 0;
  50:          }
  51:          break;
  52:      case 1:    // 按键#1
  53:          if(ksta == KEY_STA_KEEP && ktick == 1000/KEY_PERIOD_MS){
  54:              // 按键按下保持了1000毫秒,TODO SOMETHING
  55:              
  56:              return 1;
  57:          }
  58:          break;
  59:      case 2:    // 按键#2
  60:          if(ksta == KEY_STA_KEEP && ktick == 1){
  61:              // 按键被按下,具备了间隔KEY_PERIOD_MS毫秒的去抖动时间,TODO SOMETHING
  62:              
  63:              return 1;
  64:          }
  65:          break;
  66:      case 3:    // 按键#3
  67:          if(ksta == KEY_STA_END){
  68:              // 响应按键松开事件,TODO SOMETHING
  69:              
  70:          }
  71:          break;
  72:      }
  73:      return 0;
  74:  }
  75:  
  76:  ////////////////////////////////////////////////////////////////////////////////
  77:  //|          |
  78:  //| 函数名称 |: main
  79:  //| 功能描述 |:
  80:  //|          |:
  81:  //| 参数列表 |:
  82:  //|          |:
  83:  //|     |:
  84:  //|          |:
  85:  //| 备注信息 |:
  86:  //|          |:
  87:  ////////////////////////////////////////////////////////////////////////////////
  88:  int main(void)
  89:  {
  90:      // 初始化KEYIF
  91:      kif_Init();
  92:     
  93:      while(1){
  94:          delay(KEY_PERIOD_MS);
  95:          // KEY_PERIOD_MS毫秒为周期调用。
  96:          kif_TickHook();
  97:      }
  98:  }
上面的代码包括了3各文件,keyif.hkeyif.cexample.c
其中example.c是使用举例,它提供了两个必须的函数,
u8_t KeyRead(u8_tkindex);
u8_t KeyProc(u8_tkindex, u8_t ksta, u8_t ktick);
先来分析KeyRead()函数:
它是读取物理按键的底层函数,该函数被KEYIF模块调用。当按键按下时,它必须返回非零;按键抬起时,返回零即可。例如我们通常把按键引脚设计为低电平为按下状态,那么当读取对应引脚电平状态时,如果读取的引脚为低电平则返回非零,高电平就返回零。
kindex参数是用于区别物理引脚的。例如:kindex0时读取GPIO_B4kindex1时读取GPIO_A1,等等。
再来分析KeyProc()函数:
该函数是用户的按键响应函数,在该函数中用户可以加入一切按键响应代码。该函数被KEYIF模块调用。kindex参数是按键的索引,用于标识按键;ksta参数是按键的当前状态,取值为KEY_STA_BEGINKEY_STA_KEEPKEY_STA_END其中之一。BEGIN代表按键刚被按下,KEEP代表按键保持在按下状态,END代表按键被松开了。ktick参数是KEEP状态的计时器,表示按键按下状态保持了多久,单位是KEY_PERIOD_MS毫秒。
KeyProc()函数的返回值决定了本次按键操作是否还会有后续的KEEP事件,如果用户不需要后续的KEEP事件,返回非零即可。当用户告诉KEYIF不需要后续KEEP事件后,按键保持在按下状态也不会产生KEEP事件,直到下一次按键操作。无论有无KEEP事件,BEGINEND事件始终会有的,无法关闭!
example.c文件中给出了几个应用示例,其实这个按键框架能实现的操作远不止这些!用户自己根据需求可以轻易写出各种代码。
要使用KEYIF,必须在用户代码开始处调用kif_Init()函数来初始化KEYIF。然后在主循环或者时钟中断里,以KEY_PERIOD_MS毫秒为周期调用kif_TickHook()函数。

出870入263汤圆

 楼主| 发表于 2012-5-26 15:44:51 | 显示全部楼层
贴过来了,格式有点难看。我是用Windows Live Writer编辑的blog,格式很好,但贴过来就成这样了。

出0入0汤圆

发表于 2012-5-26 17:48:36 | 显示全部楼层
状态机学习

出0入0汤圆

发表于 2012-5-26 19:07:11 | 显示全部楼层
楼主不断完善按键代码啊,明天去试试看,谢谢~

出0入0汤圆

发表于 2012-5-26 19:10:30 | 显示全部楼层
你可以试一下高级模式
  1.   1:  #ifndef__KEY_IF_H__
  2.    2:  #define__KEY_IF_H__
  3.    3:  ////////////////////////////////////////////////////////////////////////////////
  4.    4:  typedefunsigned char   u8_t;
复制代码

出0入0汤圆

发表于 2012-5-26 19:12:32 | 显示全部楼层
armstrong 发表于 2012-5-26 15:41
在单片机开发中,由于资源受限而没有平台的支持,每次开发都要重写很多代码,应用的千奇百怪的需求更是加剧 ...

建议楼主把文件上传,这样别人还得自己整理,由于你加了行号,每行都要删除,有点浪费时间。

出870入263汤圆

 楼主| 发表于 2012-5-26 20:06:56 | 显示全部楼层
wuguoyan 发表于 2012-5-26 19:12
建议楼主把文件上传,这样别人还得自己整理,由于你加了行号,每行都要删除,有点浪费时间。  ...

好,我把源文件压缩包放在博客里吧。

出870入263汤圆

 楼主| 发表于 2012-5-26 20:07:37 | 显示全部楼层
90999 发表于 2012-5-26 19:10
你可以试一下高级模式

你这几行是怎么实现的?这样的格式还比较满意啊!

出0入0汤圆

发表于 2012-5-26 20:29:40 | 显示全部楼层
嗯,就是不够简洁啊,原来按键程序可以写得很简洁,为什么一定要写成这样呢

出0入0汤圆

发表于 2012-5-26 20:42:46 | 显示全部楼层
armstrong 发表于 2012-5-26 20:06
好,我把源文件压缩包放在博客里吧。

我记得你之前发布的有32位的mcu和8位的mcu,现在怎么就是8bit的,是否在32位上效率也一样呢?

出0入0汤圆

发表于 2012-5-26 21:20:32 | 显示全部楼层
armstrong 发表于 2012-5-26 20:07
你这几行是怎么实现的?这样的格式还比较满意啊!


发表回复的右上角有个“ 高级模式” 或者你点引用也可以。。。。

然后在 FLASH 旁边有个 <> 标记的就是。。。。

出0入0汤圆

发表于 2012-5-26 21:38:44 | 显示全部楼层
有机会在我的项目里试下

出870入263汤圆

 楼主| 发表于 2012-5-26 21:46:34 | 显示全部楼层
wuguoyan 发表于 2012-5-26 20:42
我记得你之前发布的有32位的mcu和8位的mcu,现在怎么就是8bit的,是否在32位上效率也一样呢?
...

32位版是使用链表以及内存池实现的,按键结构体很大,耗内存,链表遍历也没数组这么快。8位版是比较合理的,如果遇到25*255=6375毫秒保持时间计数还不够用的情况,可以通过KeyProc()函数扩展,这样更灵活!
现在,每个按键只需要10bits内存,再分析kif_TickHook()函数,其按键状态的判断是如此高效!用这么小的CPU和RAM代价实现这么灵活的按键操作,很值。

出0入0汤圆

发表于 2012-5-26 21:47:09 | 显示全部楼层
坛上有一个3行的读按键的例程,配合定时器的话,那个更方便,我现在一般就用那个,修改一下,十分简单,功能强大.单次按下,连续按下,连续按下一段时间后按一定频率输出按键按下的消息.

出870入263汤圆

 楼主| 发表于 2012-5-26 21:49:18 | 显示全部楼层
ksniper 发表于 2012-5-26 21:38
有机会在我的项目里试下

试过以后如果满意别忘了回来支持一下!这样才更鼓舞共享精神。

出870入263汤圆

 楼主| 发表于 2012-5-26 21:53:11 | 显示全部楼层
huatong 发表于 2012-5-26 21:47
坛上有一个3行的读按键的例程,配合定时器的话,那个更方便,我现在一般就用那个,修改一下,十分简单,功能强大. ...

这个你不妨参看一下,你提到的这些功能,KEYIF模块全部轻易实现。如果有无法实现的操作,可以发帖子提问,我给你最简单的答案,呵呵。
不过只要你看过代码,相信你也能跟我一样扩展自如;因为KEYIF的实现方式真的太简单明了(而且高效)!

出0入0汤圆

发表于 2012-5-26 21:56:15 | 显示全部楼层
有时间看一下,不过人家那个3条代码就可以得到按键按下及连续按下的状态了,你也不妨看一下.或者有新的思路都说不定.

出870入263汤圆

 楼主| 发表于 2012-5-26 22:05:21 | 显示全部楼层
本帖最后由 armstrong 于 2012-5-26 22:14 编辑
huatong 发表于 2012-5-26 21:56
有时间看一下,不过人家那个3条代码就可以得到按键按下及连续按下的状态了,你也不妨看一下.或者有新的思路都 ...


不瞒你说,我已经看过那个帖子了。那三条代码前提是把按键引脚状态整理到一个整数内,每个位对应一个按键,然后再用当前值与上次的值异或操作得出变化的位,即得到掩码,再用掩码和当前值来做分析。再说,真在应用中那三条代码是不够的,还不如说它是由“三条代码开始的”处理方法。从软件工程角度来说,它没有分层结构,也没有模块化结构,完全跟用户代码混在一起,挺乱的。
你可已发现,KEYIF模块里已经具备该处理思想了,并且把本应该用户代码实现的很多状态机代码都一并内在实现了,使得用户代码逻辑非常清晰。用户只管写好KeyProc()函数即可,其它一概不用管。

出870入263汤圆

 楼主| 发表于 2012-5-26 22:10:28 | 显示全部楼层
dongfo 发表于 2012-5-26 20:29
嗯,就是不够简洁啊,原来按键程序可以写得很简洁,为什么一定要写成这样呢 ...


我以前也这么想过,但是做的项目多了,每次按键部分都有不同的操作需求,而且应用场合多变,每次都得重新设计。现在的KEYIF,在单任务主循环、RTOS多线程、是否具备GUI等各种环境下做到通用并且能实现各种操作,就是为了这个目的啊,不妨参考一下,也许你会喜欢它。

出0入0汤圆

发表于 2012-5-26 23:45:45 | 显示全部楼层
支持楼主了
学习下

出0入0汤圆

发表于 2012-5-26 23:53:50 | 显示全部楼层
看文件名字《《keyif for 8bit MCU》》,只能用在8位机器上?32位的ARM7是否能用呢,能用的话,效率是否高呢?

出870入263汤圆

 楼主| 发表于 2012-5-27 09:41:57 | 显示全部楼层
本帖最后由 armstrong 于 2012-5-27 09:46 编辑
niba 发表于 2012-5-26 23:53
看文件名字《《keyif for 8bit MCU》》,只能用在8位机器上?32位的ARM7是否能用呢,能用的话,效率是否高 ...


KEYIF中仅用到一种数据类型,就是8位无符号整数。所以8位MCU上运行都会很有效率的,而32位系统运行一样高效!
如果你想特别优化32位系统,也是有一处可以优化的,就是把kif_TickHook()函数里的i和grp局部变量改为int型。因为它们需要经常自增,这几个局部变量会被安排在CPU寄存器里;
而int型自增在32位系统里是不必处理溢出位的。相反,8位无符号数局部变量自增反而要多耗费一条指令,用于使自增后的结果不会超出255范围。这是唯一可以优化的地方。

出0入169汤圆

发表于 2012-5-27 14:45:57 | 显示全部楼层
谢谢分享,程序写的挺好的,接口也很简单。
简单修改编译了下,共四百多字节。下次用到键盘再试验下效果。

出870入263汤圆

 楼主| 发表于 2012-5-27 20:18:43 | 显示全部楼层
lxvtag 发表于 2012-5-27 14:45
谢谢分享,程序写的挺好的,接口也很简单。
简单修改编译了下,共四百多字节。下次用到键盘再试验下效果。 ...

期待用后回来支持一下!按键操作越复杂,KEYIF越显优势。

出0入0汤圆

发表于 2012-5-27 21:16:36 | 显示全部楼层
有机会尝试一下

出0入0汤圆

发表于 2012-5-27 21:56:54 | 显示全部楼层
支持楼主,支持模块化程序思路,有时间再试试。

出0入0汤圆

发表于 2012-5-27 22:11:15 | 显示全部楼层
这个很好!!!

出0入0汤圆

发表于 2012-5-28 08:17:58 | 显示全部楼层
好好的研究一下按键思路哦,mark

出0入0汤圆

发表于 2012-5-28 09:13:39 | 显示全部楼层
顶一下!

出0入0汤圆

发表于 2012-5-28 09:33:39 | 显示全部楼层
学习了,帮顶

出0入0汤圆

发表于 2012-5-28 11:42:04 | 显示全部楼层
支持,学习

出0入0汤圆

发表于 2013-1-4 23:01:46 | 显示全部楼层
貌似和"通用程序设计"里面那个算法差不多.
kif_init()中的memset()是用来将数组清零的吧?数组被声明为static类型,其实这一步可以省略,因为静态变量默认被初始化为0.

出0入0汤圆

发表于 2013-3-1 22:04:26 | 显示全部楼层
本帖最后由 zyw19987 于 2013-3-1 22:05 编辑

和我用的很相似,最后加入队列。按下 短按确认 长按确认 长按松开
比我的好地方就是你那缩小占用资源的方法。

出0入0汤圆

发表于 2015-4-24 21:14:40 | 显示全部楼层
按键处理。mark

出0入0汤圆

发表于 2015-4-24 21:15:17 | 显示全部楼层
按键处理。mark

出0入0汤圆

发表于 2015-4-24 21:30:22 | 显示全部楼层
确实不错。简单易用。

出0入0汤圆

发表于 2015-4-24 23:05:19 | 显示全部楼层
好用的老贴子给顶也来了。。。

出0入0汤圆

发表于 2015-4-25 20:18:08 | 显示全部楼层
这个模块不怎么样,不推荐。

出870入263汤圆

 楼主| 发表于 2015-4-25 21:59:00 | 显示全部楼层
zhangshixing 发表于 2015-4-25 20:18
这个模块不怎么样,不推荐。

如果真是好东西,我是不会拿出来“分享”的!

出0入0汤圆

发表于 2015-4-25 22:39:23 | 显示全部楼层
http://www.amobbs.com/forum.php? ... =%E6%8C%89%E9%94%AE
这个更好些
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-7-23 14:15

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

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