|
路线栈欢迎您!
您需要 登录 才可以下载或查看,没有帐号?立即注册
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();
- }
- }
复制代码
|
|