嵌入式C | 您所在的位置:网站首页 › c语言读取键盘按键 › 嵌入式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(); }} |
CopyRight 2018-2019 实验室设备网 版权所有 |