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

独立按键、组合键

图片

介绍该按键驱动的结构、使用方法

由于手上无板子,源码未验证

github.com/L231/ry_key

一些无关紧要的

之前一直很少用到按键,基本识别一下按键是否按压,就没了,所以,就不需要特别设计,能用够用就行。

这不,机会来了,某个项目中,使用一个按键,与机子进行多种互动。自然地,就需要识别单击、双击、长按等。一时间看着代码,难以下手。

构想的形态

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

图片

  • 它要识别出独立按键的不同操作,即事件

  • 自动完成按键的状态转移

  • 也需要有组合键功能

  • 必须处理好优先级,不能说三击触发了,双击也能触发

总的要求

  • 对应低性能的MCU,实现有限的按键识别

  • 具备组合键功能

  • 状态与事件处理分离

  • 使用便捷,移植性高

按键结构设计

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

  • 独立按键链表

  • 组合键链表

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

图片独立按键扫描的数据结构

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

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
  • 按键初始化

  • 事件回调函数注册

  • 组合键关联独立按键

/* 注册一个独立按键 */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;}

按键扫描的机制

  • 先扫描所有按键,再执行事件处理

  • 某个时刻,只允许一个事件,多了全部无效

图片

应用举例

使用场景

场景说明:

  • 一个电源按键,长按

  • 功能按键,单击

  • 组合键为“电源键” + “功能键”

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

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();  }}
  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
当然可以!下面是一个简单的嵌入式C代码示例,实现了按键驱动的状态机,并支持按、短按和点击等相关按键功能: ```c #include <stdio.h> // 定义按键状态枚举 typedef enum { IDLE_STATE, SHORT_PRESS_STATE, LONG_PRESS_STATE } ButtonState; // 定义按键枚举 typedef enum { SHORT_PRESS_DURATION = 10, // 单位为ms LONG_PRESS_DURATION = 1000 } ButtonDuration; // 按键驱动函数 void buttonDriver(int buttonStatus) { static ButtonState state = IDLE_STATE; static int pressDuration = 0; switch(state) { case IDLE_STATE: if(buttonStatus == 1) { pressDuration = 0; state = SHORT_PRESS_STATE; } break; case SHORT_PRESS_STATE: if(buttonStatus == 0) { state = IDLE_STATE; if(pressDuration < SHORT_PRESS_DURATION) { printf("Short press\n"); } } else { pressDuration++; if(pressDuration >= LONG_PRESS_DURATION) { state = LONG_PRESS_STATE; printf("Long press\n"); } } break; case LONG_PRESS_STATE: if(buttonStatus == 0) { state = IDLE_STATE; } break; default: break; } } int main() { // 模拟按键状态,1表示按下,0表示松开 int buttonStatus[] = {1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0}; // 模拟按键状态变化 for(int i = 0; i < sizeof(buttonStatus) / sizeof(buttonStatus[0]); i++) { buttonDriver(buttonStatus[i]); } return 0; } ``` 以上代码中,`buttonDriver` 函数用于处理按键状态变化。根据当前状态,它会判断按键是否被短按、按或点击,并打印相应的消息。 在 `main` 函数中,我们模拟了按键状态的变化,并调用 `buttonDriver` 函数进行处理。你可以根据实际需求修改代码中的按键状态数组,以测试不同的按键行为。 请注意,这只是一个简单的示例代码,实际的按键驱动可能需要更多的功能和细节处理。我希望这个示例能帮助你入门嵌入式C编程中的按键驱动开发。如果你有任何问题,请随时提问!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值