介绍该按键驱动的结构、使用方法
由于手上无板子,源码未验证
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)
/* 注册按键的回调函数 */
独立按键的状态机
总的结构:
代码实现:
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();
}
}
-END-