| 
 | 
 
路线栈欢迎您!
您需要 登录 才可以下载或查看,没有帐号?立即注册 
 
 
 
x
 
一、前言 
 
一直很少用到按键,基本识别一下按键是否按压,可以看 常用单片机的按键处理程序 ,就不需要特别设计,能用够用就行。现在某个项目中,使用一个按键,与机子进行多种互动;自然地,就需要识别单击、双击、长按等。 
 
构想的形态 
 
初步梳理需求,并预测未来的场景,构建如下形态: 
 
 
1.它要识别出独立按键的不同操作,即事件 
2.自动完成按键的状态转移 
3.也需要有组合键功能 
4.必须处理好优先级,不能说三击触发了,双击也能触发 
 
总的要求 
 
1.对应低性能的MCU,实现有限的按键识别 
2.具备组合键功能 
3.状态与事件处理分离 
4.使用便捷,移植性高 
 
二、设计实现 
 
按键结构设计 
 
考虑到按键的管理,使用单向链表串起所有按键:独立按键链表、组合键链表 
 
尽可能让二者前半部分保持一致,如下: 
 
 
独立按键扫描的数据结构 
 
主要有电平、滤波、连击部分、阈值部分,满足扫描需求: 
 
- typedef struct
 
 - {
 
 -   uint8_t    level       : 1;    /* 当前电平 */
 
 -   uint8_t    valid_level : 1;    /* 有效电平,按键激活判断 */
 
 -   uint8_t    reserve     : 2;    /* 保留 */
 
 -   uint8_t    click_cnt   : 4;    /* 连击次数 */
 
 -   uint8_t    filter_cnt;         /* 电平滤波计数 */
 
 -   uint8_t    filter_limit;       /* 电平滤波阈值 */
 
 -   uint16_t   tick;               /* 按键扫描计时 */
 
 -   uint16_t   double_click_limit; /* 双击时间的阈值 */
 
 -   uint16_t   long_limit;         /* 长按的阈值 */
 
 -   uint16_t   long_long_limit;    /* 超长按的阈值 */
 
 -   uint8_t  (*get_level)(void);   /* 电平获取,函数指针 */
 
 - }key_scan_t;
 
  复制代码 
部分按键初始化配置的API 
 
1.按键初始化 
2.事件回调函数注册 
3.组合键关联独立按键 
 
- /* 注册一个独立按键 */
 
 - void ry_key_reg(ry_key_t *key,
 
 -     uint8_t valid_level,        /* 有效电平,按键激活判断 */
 
 -     uint8_t filter,             /* 电平滤波阈值 */
 
 -     uint8_t double_click_limit, /* 双击时间的阈值 */
 
 -     uint8_t long_limit,         /* 长按的阈值 */
 
 -     uint8_t long_long_limit,    /* 超长按的阈值 */
 
 -     uint8_t (*get_level)(void))
 
 - /* 组合键的注册函数 */
 
 - void ry_key_compound_reg(ry_key_compound_t *key, callback cbk)
 
 - /* 组合键添加关联的独立按键SN号,以SN大小插入 */
 
 - void ry_key_compound_insert_key_sn(ry_key_compound_t *key, uint8_t sn)
 
 - /* 注册按键的回调函数 */
 
 - #define RY_KEY_CALLBACK_CFG(key, event, cbk)
 
  复制代码 
独立按键的状态机 
 
总的结构: 
 
 
  
代码实现: 
 
- uint8_t ry_key_state_machine(ry_key_t *key)
 
 - {
 
 -   __key_level_scan(key);
 
 -   key->scan.tick++;
 
  
-   switch(key->status)
 
 -   {
 
 -   case KEY_IDLE_STATUS :
 
 -     if(key->scan.level == key->scan.valid_level)
 
 -     {
 
 -       __key_event_mark(key, KEY_DOWN_EVENT);
 
 -       key->scan.tick      = 0;
 
 -       key->scan.click_cnt = 0;
 
 -     }
 
 -     break;
 
 -   case KEY_DOWN_STATUS :
 
 -     if(key->scan.level != key->scan.valid_level)
 
 -     {
 
 -       __key_event_mark(key, KEY_UP_EVENT);
 
 -       key->scan.click_cnt++;
 
 -     }
 
 -     else if(key->scan.tick > key->scan.long_limit)
 
 -       __key_event_mark(key, KEY_LONG_PRESS_EVENT);
 
 -     break;
 
 -   case KEY_UP_STATUS :
 
 -     if(key->scan.level == key->scan.valid_level)
 
 -     {
 
 -       __key_event_mark(key, KEY_DOWN_STATUS);
 
 -       key->scan.tick = 0;
 
 -     }
 
 -     /* 从按键按下开始计时,若时间超过连击时间阈值,则认为连击结束 */
 
 -     else if(key->scan.tick > key->scan.double_click_limit)
 
 -     {
 
 -       if(1 == key->scan.click_cnt)
 
 -         __key_event_mark(key, KEY_SINGLE_CLICK_EVENT);
 
 -       else if(2 == key->scan.click_cnt)
 
 -         __key_event_mark(key, KEY_DOUBLE_CLICK_EVENT);
 
 -       else if(3 == key->scan.click_cnt)
 
 -         __key_event_mark(key, KEY_THREE_CLICK_EVENT);
 
 -       else
 
 -         key->status = KEY_IDLE_STATUS;
 
 -     }
 
 -     break;
 
 -   case KEY_LONG_PRESS_STATUS :
 
 -     if(key->scan.tick > key->scan.long_long_limit)
 
 -       __key_event_mark(key, KEY_LONG_LONG_PRESS_EVENT);
 
 -     else if(key->scan.level != key->scan.valid_level)
 
 -       key->status     = KEY_IDLE_STATUS;
 
 -     break;
 
 -   default :
 
 -     key->status         = KEY_IDLE_STATUS;
 
 -     break;
 
 -   }
 
 -   return key->event;
 
 - }
 
  复制代码 
按键扫描的机制 
 
1.先扫描所有按键,再执行事件处理 
2.某个时刻,只允许一个事件,多了全部无效 
 
 
三、应用举例 
 
使用场景 
 
场景说明: 
 
1.一个电源按键,长按 
2.功能按键,单击 
3.组合键为“电源键” + “功能键” 
 
定义几个按键,并准备电平读取函数: 
 
- static ry_key_t __keyPower;              /* 电源按键 */
 
 - static ry_key_t __keyCtr;                /* 控制按键 */
 
 - static ry_key_compound_t __compoundKey1; /* 组合键 */
 
  
- extern uint8_t key_power_get_level(void);
 
 - extern uint8_t key_ctr_get_level(void);
 
  复制代码 
初始化按键 
 
初始化如下: 
 
- void user_key_init(void)
 
 - {
 
 -   ry_key_reg(&__keyPower, 1, 5, 50, 300, 900, key_power_get_level);
 
 -   ry_key_reg(&__keyCtr,   1, 5, 50, 300, 900, key_ctr_get_level);
 
 -   ry_key_compound_reg(&__compoundKey1, compound_key1_callback);
 
 -   /* 配置事件的回调函数 */
 
 -   RY_KEY_CALLBACK_CFG(__keyPower, KEY_LONG_PRESS_EVENT, key_power_long_press_callback);
 
 -   RY_KEY_CALLBACK_CFG(__keyCtr, KEY_SINGLE_CLICK_EVENT, key_ctr_single_click_callback);
 
 -   /* 组合键关联对应的独立按键 */
 
 -   ry_key_compound_insert_key_sn(&__compoundKey1, __keyPower.sn);
 
 -   ry_key_compound_insert_key_sn(&__compoundKey1, __keyCtr.sn);
 
 - }
 
  复制代码 
准备几个回调函数: 
 
- void key_power_long_press_callback(ry_key_t *key)
 
 - {
 
 -   printf("key_power_long_press_callback");
 
 - }
 
 - void key_ctr_single_click_callback(ry_key_t *key)
 
 - {
 
 -   printf("key_ctr_single_click_callback");
 
 - }
 
 - void compound_key1_callback(ry_key_t *key)
 
 - {
 
 -   printf("compound_key1_callback");
 
 - }
 
  复制代码 
主函数 
 
这样定时扫描按键,就会自动运行我们定义的事件回调函数。 
 
- int main(void)
 
 - {
 
 -   //system_init();
 
 -   user_key_init();
 
 -   while(1)
 
 -   {
 
 -     /* 配置定时器,定时扫描按键效果更好 */
 
 -     ry_key_scan();
 
 -   }
 
 - }
 
  复制代码 
 |   
 
 
 
 |