• 注册 / 登录
  • 切换到窄版
  • 查看: 1799|回复: 0

    嵌入式C按键驱动,支持连击、长按、组合键

    [复制链接]

    676

    主题

    690

    帖子

    6808

    积分

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    6808
    发表于 2023-8-13 21:32:36 | 显示全部楼层 |阅读模式

    路线栈欢迎您!

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

    x
    一、前言

    一直很少用到按键,基本识别一下按键是否按压,可以看 常用单片机的按键处理程序 ,就不需要特别设计,能用够用就行。现在某个项目中,使用一个按键,与机子进行多种互动;自然地,就需要识别单击、双击、长按等。

    构想的形态

    初步梳理需求,并预测未来的场景,构建如下形态:

    2.png

    1.它要识别出独立按键的不同操作,即事件
    2.自动完成按键的状态转移
    3.也需要有组合键功能
    4.必须处理好优先级,不能说三击触发了,双击也能触发

    总的要求

    1.对应低性能的MCU,实现有限的按键识别
    2.具备组合键功能
    3.状态与事件处理分离
    4.使用便捷,移植性高

    二、设计实现

    按键结构设计

    考虑到按键的管理,使用单向链表串起所有按键:独立按键链表、组合键链表

    尽可能让二者前半部分保持一致,如下:

    3.png

    独立按键扫描的数据结构

    主要有电平、滤波、连击部分、阈值部分,满足扫描需求:

    1. typedef struct
    2. {
    3.   uint8_t    level       : 1;    /* 当前电平 */
    4.   uint8_t    valid_level : 1;    /* 有效电平,按键激活判断 */
    5.   uint8_t    reserve     : 2;    /* 保留 */
    6.   uint8_t    click_cnt   : 4;    /* 连击次数 */
    7.   uint8_t    filter_cnt;         /* 电平滤波计数 */
    8.   uint8_t    filter_limit;       /* 电平滤波阈值 */
    9.   uint16_t   tick;               /* 按键扫描计时 */
    10.   uint16_t   double_click_limit; /* 双击时间的阈值 */
    11.   uint16_t   long_limit;         /* 长按的阈值 */
    12.   uint16_t   long_long_limit;    /* 超长按的阈值 */
    13.   uint8_t  (*get_level)(void);   /* 电平获取,函数指针 */
    14. }key_scan_t;
    复制代码

    部分按键初始化配置的API

    1.按键初始化
    2.事件回调函数注册
    3.组合键关联独立按键

    1. /* 注册一个独立按键 */
    2. void ry_key_reg(ry_key_t *key,
    3.     uint8_t valid_level,        /* 有效电平,按键激活判断 */
    4.     uint8_t filter,             /* 电平滤波阈值 */
    5.     uint8_t double_click_limit, /* 双击时间的阈值 */
    6.     uint8_t long_limit,         /* 长按的阈值 */
    7.     uint8_t long_long_limit,    /* 超长按的阈值 */
    8.     uint8_t (*get_level)(void))
    9. /* 组合键的注册函数 */
    10. void ry_key_compound_reg(ry_key_compound_t *key, callback cbk)
    11. /* 组合键添加关联的独立按键SN号,以SN大小插入 */
    12. void ry_key_compound_insert_key_sn(ry_key_compound_t *key, uint8_t sn)
    13. /* 注册按键的回调函数 */
    14. #define RY_KEY_CALLBACK_CFG(key, event, cbk)
    复制代码

    独立按键的状态机

    总的结构:

    1.png

    代码实现:

    1. uint8_t ry_key_state_machine(ry_key_t *key)
    2. {
    3.   __key_level_scan(key);
    4.   key->scan.tick++;

    5.   switch(key->status)
    6.   {
    7.   case KEY_IDLE_STATUS :
    8.     if(key->scan.level == key->scan.valid_level)
    9.     {
    10.       __key_event_mark(key, KEY_DOWN_EVENT);
    11.       key->scan.tick      = 0;
    12.       key->scan.click_cnt = 0;
    13.     }
    14.     break;
    15.   case KEY_DOWN_STATUS :
    16.     if(key->scan.level != key->scan.valid_level)
    17.     {
    18.       __key_event_mark(key, KEY_UP_EVENT);
    19.       key->scan.click_cnt++;
    20.     }
    21.     else if(key->scan.tick > key->scan.long_limit)
    22.       __key_event_mark(key, KEY_LONG_PRESS_EVENT);
    23.     break;
    24.   case KEY_UP_STATUS :
    25.     if(key->scan.level == key->scan.valid_level)
    26.     {
    27.       __key_event_mark(key, KEY_DOWN_STATUS);
    28.       key->scan.tick = 0;
    29.     }
    30.     /* 从按键按下开始计时,若时间超过连击时间阈值,则认为连击结束 */
    31.     else if(key->scan.tick > key->scan.double_click_limit)
    32.     {
    33.       if(1 == key->scan.click_cnt)
    34.         __key_event_mark(key, KEY_SINGLE_CLICK_EVENT);
    35.       else if(2 == key->scan.click_cnt)
    36.         __key_event_mark(key, KEY_DOUBLE_CLICK_EVENT);
    37.       else if(3 == key->scan.click_cnt)
    38.         __key_event_mark(key, KEY_THREE_CLICK_EVENT);
    39.       else
    40.         key->status = KEY_IDLE_STATUS;
    41.     }
    42.     break;
    43.   case KEY_LONG_PRESS_STATUS :
    44.     if(key->scan.tick > key->scan.long_long_limit)
    45.       __key_event_mark(key, KEY_LONG_LONG_PRESS_EVENT);
    46.     else if(key->scan.level != key->scan.valid_level)
    47.       key->status     = KEY_IDLE_STATUS;
    48.     break;
    49.   default :
    50.     key->status         = KEY_IDLE_STATUS;
    51.     break;
    52.   }
    53.   return key->event;
    54. }
    复制代码

    按键扫描的机制

    1.先扫描所有按键,再执行事件处理
    2.某个时刻,只允许一个事件,多了全部无效

    5.png

    三、应用举例

    使用场景

    场景说明:

    1.一个电源按键,长按
    2.功能按键,单击
    3.组合键为“电源键” + “功能键”

    定义几个按键,并准备电平读取函数:

    1. static ry_key_t __keyPower;              /* 电源按键 */
    2. static ry_key_t __keyCtr;                /* 控制按键 */
    3. static ry_key_compound_t __compoundKey1; /* 组合键 */

    4. extern uint8_t key_power_get_level(void);
    5. extern uint8_t key_ctr_get_level(void);
    复制代码

    初始化按键

    初始化如下:

    1. void user_key_init(void)
    2. {
    3.   ry_key_reg(&__keyPower, 1, 5, 50, 300, 900, key_power_get_level);
    4.   ry_key_reg(&__keyCtr,   1, 5, 50, 300, 900, key_ctr_get_level);
    5.   ry_key_compound_reg(&__compoundKey1, compound_key1_callback);
    6.   /* 配置事件的回调函数 */
    7.   RY_KEY_CALLBACK_CFG(__keyPower, KEY_LONG_PRESS_EVENT, key_power_long_press_callback);
    8.   RY_KEY_CALLBACK_CFG(__keyCtr, KEY_SINGLE_CLICK_EVENT, key_ctr_single_click_callback);
    9.   /* 组合键关联对应的独立按键 */
    10.   ry_key_compound_insert_key_sn(&__compoundKey1, __keyPower.sn);
    11.   ry_key_compound_insert_key_sn(&__compoundKey1, __keyCtr.sn);
    12. }
    复制代码

    准备几个回调函数:

    1. void key_power_long_press_callback(ry_key_t *key)
    2. {
    3.   printf("key_power_long_press_callback");
    4. }
    5. void key_ctr_single_click_callback(ry_key_t *key)
    6. {
    7.   printf("key_ctr_single_click_callback");
    8. }
    9. void compound_key1_callback(ry_key_t *key)
    10. {
    11.   printf("compound_key1_callback");
    12. }
    复制代码

    主函数

    这样定时扫描按键,就会自动运行我们定义的事件回调函数。

    1. int main(void)
    2. {
    3.   //system_init();
    4.   user_key_init();
    5.   while(1)
    6.   {
    7.     /* 配置定时器,定时扫描按键效果更好 */
    8.     ry_key_scan();
    9.   }
    10. }
    复制代码

    key按键驱动.rar

    81.73 KB, 下载次数: 6

    手上无板子,源码未验证

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    小黑屋|路丝栈 ( 粤ICP备2021053448号 )

    GMT+8, 2024-12-22 14:43 , Processed in 0.044946 second(s), 22 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

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