[update] 重构代码,实现连击功能

- 实现无限连击功能
- 简化示例程序,增加组合按键演示
- 整理代码
- 按键结构体增加 id 属性,用于唯一区分按键
- 按键结构体增加 max_combos_click_solt 属性,用于处理连击

Signed-off-by: zhaojuntao <d2014zjt@163.com>
This commit is contained in:
zhaojuntao 2019-12-26 17:55:12 +08:00
parent 7b996ae55b
commit d19ecb024c
4 changed files with 443 additions and 364 deletions

190
README.md
View File

@ -1,23 +1,39 @@
# FlexibleButton
FlexibleButton 是一个基于 C 语言的小巧灵活的按键处理库。该按键库解耦了具体的按键硬件结构理论上支持轻触按键与自锁按键并可以无限扩展按键数量。另外FlexibleButton 使用扫描的方式一次性读取所有所有的按键状态,然后通过事件回调机制上报按键事件
FlexibleButton 是一个基于标准 C 语言的小巧灵活的按键处理库,支持单击、连击、短按、长按、自动消抖,可以自由设置组合按键,可用于中断和低功耗场景
该按键库使用 C 语言编写,驱动与应用程序解耦,便于灵活应用,比如用户可以方便地在应用层增加按键中断、处理按键功耗、定义按键事件处理方式,而无需修改 FlexibleButton 库中的代码。核心的按键扫描代码仅有三行,没错,就是经典的 **三行按键扫描算法**。使用 C 语言标准库 API 编写,也使得该按键库可以无缝兼容任意的处理器平台,并且支持任意 OS 和 non-OS裸机程)。
该按键库解耦了具体的按键硬件结构理论上支持轻触按键与自锁按键并可以无限扩展按键数量。另外FlexibleButton 使用扫描的方式一次性读取所有所有的按键状态,然后通过事件回调机制上报按键事件。核心的按键扫描代码仅有三行,没错,就是经典的 **三行按键扫描算法**。使用 C 语言标准库 API 编写,也使得该按键库可以无缝兼容任意的处理器平台,并且支持任意 OS 和 non-OS裸机程)。
## 获取方式
## 获取
### Git 方式
```SHELL
git clone https://github.com/murphyzhao/FlexibleButton.git
```
## 使用与测试
### RT-Thread menuconfig 方式
FlexibleButton 库中提供了一个测试例程 [`./flexible_button_demo.c`](./flexible_button_demo.c),该例程基于 RT-Thread OS 进行测试,硬件平台选择了 RT-Thread IoT Board v2.2 开发板。当然你可以选择使用其他的 OS或者使用裸机测试只需要移除 OS 相关的特性即可。
```
RT-Thread online packages --->
miscellaneous packages --->
[*] FlexibleButton: Small and flexible button driver --->
[*] Enable flexible button demo
version (latest) --->
```
配置完成后,输入 `pkgs --update` 下载软件包。
## 快速体验
FlexibleButton 库中提供了一个测试例程 [`./flexible_button_demo.c`](./flexible_button_demo.c),该例程基于 RT-Thread OS 进行测试,硬件平台选择了 *RT-Thread IoT Board Pandora v2.51* 开发板。当然你可以选择使用其他的 OS或者使用裸机测试只需要移除 OS 相关的特性即可。
如果你使用自己的硬件平台,只需要将 FlexibleButton 库源码和例程加入你既有的工程下即可。
## DEMO 程序说明
该示例程序可以直接在 RT-Thread [`stm32l475-atk-pandora`](https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32l475-atk-pandora) BSP 中运行,可以在该 BSP 目录下,使用 menuconfig 获取本软件包。
### 确定用户按键
```C
@ -54,96 +70,83 @@ int flex_button_main(void)
INIT_APP_EXPORT(flex_button_main);
```
如上所示,首先使用 `user_button_init();` 初始化用户按键硬件,该步骤将用户按键绑定到 FlexibleButton 库。然后,使用 RT-Thread 的 `INIT_APP_EXPORT` 接口导出为上电自动初始化,创建了一个 “flex_btn” 名字的按键扫描线程,线程里扫描检查按键事件。
如上代码所示,首先使用 `user_button_init();` 初始化用户按键硬件,该步骤将用户按键绑定到 FlexibleButton 库。然后,使用 RT-Thread 的 `INIT_APP_EXPORT` 接口导出为上电自动初始化,创建了一个 “flex_btn” 名字的按键扫描线程,线程里扫描检查按键事件。
### 按键初始化代码
`user_button_init();` 初始化代码如下所示:
```C
```
static void user_button_init(void)
{
int i;
/* 初始化按键数据结构 */
rt_memset(&user_button[0], 0x0, sizeof(user_button));
user_button[USER_BUTTON_0].usr_button_read = button_key0_read;
user_button[USER_BUTTON_0].cb = (flex_button_response_callback)btn_0_cb;
user_button[USER_BUTTON_1].usr_button_read = button_key1_read;
user_button[USER_BUTTON_1].cb = (flex_button_response_callback)btn_1_cb;
user_button[USER_BUTTON_2].usr_button_read = button_key2_read;
user_button[USER_BUTTON_3].usr_button_read = button_keywkup_read;
/* 初始化 IoT Board 按键引脚,使用 rt-thread PIN 设备框架 */
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT); /* 设置 GPIO 为输入模式 */
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT); /* 设置 GPIO 为输入模式 */
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT); /* 设置 GPIO 为输入模式 */
rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT);/* 设置 GPIO 为输入模式 */
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); /* 设置 GPIO 为上拉输入模式 */
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP); /* 设置 GPIO 为上拉输入模式 */
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLUP); /* 设置 GPIO 为上拉输入模式 */
rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT_PULLDOWN); /* 设置 GPIO 为下拉输入模式 */
/* 核心的按键配置
* pressed_logic_level设置按键按下的逻辑电平
* click_start_tick设置触发按键按下事件的起始 tick用于消抖
* short_press_start_tick设置短按事件触发的起始 tick
* long_press_start_tick设置长按事件触发的起始 tick
* long_hold_start_tick设置长按保持事件触发的起始 tick
*/
for (i = 0; i < USER_BUTTON_MAX; i ++)
{
user_button[i].id = i;
user_button[i].usr_button_read = common_btn_read;
user_button[i].cb = common_btn_evt_cb;
user_button[i].pressed_logic_level = 0;
user_button[i].click_start_tick = 20;
user_button[i].short_press_start_tick = 100;
user_button[i].long_press_start_tick = 200;
user_button[i].long_hold_start_tick = 300;
user_button[i].short_press_start_tick = FLEX_MS_TO_SCAN_CNT(1500);
user_button[i].long_press_start_tick = FLEX_MS_TO_SCAN_CNT(3000);
user_button[i].long_hold_start_tick = FLEX_MS_TO_SCAN_CNT(4500);
if (i == USER_BUTTON_3)
{
user_button[USER_BUTTON_3].pressed_logic_level = 1;
}
flex_button_register(&user_button[i]);
}
}
```
`user_button_init();` 主要用于初始化按键硬件,设置按键长按和短按的 tick 数RT-Thread RT_TICK_PER_SECOND 配置为 1000 时,默认一个 tick 为 1ms然后注册按键到 FlexibleButton 库。
核心的配置如下:
|配置项|说明|
| :---- | :----|
| id | 按键编号 |
| usr_button_read | 设置按键读值回调函数 |
| cb | 设置按键事件回调函数 |
| pressed_logic_level | 设置按键按下时的逻辑电平 |
| short_press_start_tick | 短按起始 tick使用 FLEX_MS_TO_SCAN_CNT 宏转化为扫描次数 |
| long_press_start_tick | 长按起始 tick使用 FLEX_MS_TO_SCAN_CNT 宏转化为扫描次数 |
| long_hold_start_tick | 超长按起始 tick使用 FLEX_MS_TO_SCAN_CNT 宏转化为扫描次数 |
注意short_press_start_tick、long_press_start_tick 和 long_hold_start_tick 必须使用 `FLEX_MS_TO_SCAN_CNT` 将毫秒时间转化为扫描次数。
`user_button[i].short_press_start_tick = FLEX_MS_TO_SCAN_CNT(1500);` 表示按键按下开始计时1500 ms 后按键依旧是按下状态的话,就断定为短按开始。
### 事件处理代码
```C
static void btn_0_cb(flex_button_t *btn)
static void common_btn_evt_cb(void *arg)
{
rt_kprintf("btn_0_cb\n");
switch (btn->event)
flex_button_t *btn = (flex_button_t *)arg;
rt_kprintf("id: [%d - %s] event: [%d - %30s] combos: %d\n",
btn->id, enum_btn_id_string[btn->id],
btn->event, enum_event_string[btn->event],
btn->click_cnt);
if (flex_button_event_read(&user_button[USER_BUTTON_0]) == flex_button_event_read(&user_button[USER_BUTTON_1]) == FLEX_BTN_PRESS_CLICK)
{
case FLEX_BTN_PRESS_DOWN:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOWN]\n");
break;
case FLEX_BTN_PRESS_CLICK:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_CLICK]\n");
break;
case FLEX_BTN_PRESS_DOUBLE_CLICK:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOUBLE_CLICK]\n");
break;
case FLEX_BTN_PRESS_SHORT_START:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_START]\n");
break;
case FLEX_BTN_PRESS_SHORT_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_START:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_START]\n");
break;
case FLEX_BTN_PRESS_LONG_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD_UP]\n");
break;
rt_kprintf("[combination]: button 0 and button 1\n");
}
}
```
示例代码中,将所有的按键事件回调均绑定到 `common_btn_evt_cb` 函数,在该函数中打印了按键 ID 和按键事件,以及按键连击次数,并演示了如何使用组合按键。
## FlexibleButton 代码说明
### 按键事件定义
@ -156,6 +159,7 @@ typedef enum
FLEX_BTN_PRESS_DOWN = 0, // 按下事件
FLEX_BTN_PRESS_CLICK, // 单击事件
FLEX_BTN_PRESS_DOUBLE_CLICK, // 双击事件
FLEX_BTN_PRESS_REPEAT_CLICK, // 连击事件,使用 flex_button_t 中的 click_cnt 断定连击次数
FLEX_BTN_PRESS_SHORT_START, // 短按开始事件
FLEX_BTN_PRESS_SHORT_UP, // 短按抬起事件
FLEX_BTN_PRESS_LONG_START, // 长按开始事件
@ -167,42 +171,58 @@ typedef enum
} flex_button_event_t;
```
其中 `FLEX_BTN_PRESS_LONG_HOLD` 事件可以用来实现长按累加的应用场景。
### 按键数据结构
```
typedef struct flex_button
{
uint8_t pressed_logic_level : 1;
uint8_t event : 4;
uint8_t status : 3;
struct flex_button* next;
uint8_t (*usr_button_read)(void *);
flex_button_response_callback cb;
uint16_t scan_cnt;
uint16_t click_cnt;
uint16_t max_combos_click_solt;
uint16_t debounce_tick;
uint16_t click_start_tick;
uint16_t short_press_start_tick;
uint16_t long_press_start_tick;
uint16_t long_hold_start_tick;
uint8_t (*usr_button_read)(void);
flex_button_response_callback cb;
struct flex_button* next;
uint8_t id;
uint8_t pressed_logic_level : 1;
uint8_t event : 4;
uint8_t status : 3;
} flex_button_t;
```
| 序号 | 数据成员 | 是否需要用户初始化 | 说明 |
| :----: | :---- | :----: | :---- |
| 1 | pressed_logic_level | 是 | 设置按键按下的逻辑电平。1高电平为按下状态0低电平为按下状态 |
| 2 | event | 否 | 用于记录当前按键的按键事件 |
| 3 | status | 否 | 用于记录当前按键的状态,按下、抬起、长按等 |
| 4 | scan_cnt | 否 | 用于记录扫描次数 |
| 5 | click_cnt | 否 | 用于记录单击次数,用于判断双击事件 |
| 6 | debounce_tick | 是 | 消抖时间,默认为 0可以不配置依靠扫描间隙进行消抖 |
| 7 | click_start_tick | 是 | 设置触发按键按下事件的起始 tick有消抖效果 |
| 1 | next | 否 | 按键库使用单向链表串起所有的按键 |
| 2 | usr_button_read | 是 | 用户设备的按键引脚电平读取函数,**重要** |
| 3 | cb | 是 | 设置按键事件回调,用于应用层对按键事件的分类处理 |
| 4 | scan_cnt | 否 | 用于记录扫描次数,按键按下是开始从零计数 |
| 5 | click_cnt | 否 | 记录单击次数,用于判定单击、连击 |
| 6 | max_combos_click_solt | 是 | 连击间隙,用于判定是否结束连击计数,有默认值 `MAX_COMBOS_CLICK_SOLT` |
| 7 | debounce_tick | 否 | 消抖时间,暂未使用,依靠扫描间隙进行消抖 |
| 8 | short_press_start_tick | 是 | 设置短按事件触发的起始 tick |
| 9 | long_press_start_tick | 是 | 设置长按事件触发的起始 tick |
| 10 | long_hold_start_tick | 是 | 设置长按保持事件触发的起始 tick |
| 11 | usr_button_read | 是 | 用户设备的按键引脚电平读取函数,**重要** |
| 12 | cb | 是 | 设置按键事件回调,用于应用层对按键事件的分类处理 |
| 13 | next | 否 | 按键库使用单向链表串起所有的按键配置 |
| 10 | long_hold_start_tick | 是 | 设置长按保持事件触发的起始 tick |
| 11 | id | 是 | 当多个按键使用同一个回调函数时,用于断定属于哪个按键 |
| 12 | pressed_logic_level | 是 | 设置按键按下的逻辑电平。1标识按键按下的时候为高电平0标识按键按下的时候未低电平**重要** |
| 13 | event | 否 | 用于记录当前按键事件 |
| 14 | status | 否 | 用于记录当前按键的状态,用于内部状态机 |
注意,在使用 `max_combos_click_solt``debounce_tick``short_press_start_tick``long_press_start_tick``long_hold_start_tick` 的时候,注意需要使用宏 `**FLEX_MS_TO_SCAN_CNT(ms)**` 将毫秒值转换为扫描次数。因为按键库基于扫描次数运转。示例如下:
```
user_button[1].short_press_start_tick = FLEX_MS_TO_SCAN_CNT(1500); // 1500 毫秒
```
上述代码表示表示按键按下后开始计时1500ms 的时候,按键依旧按下,则断定为短按开始,并上报 `FLEX_BTN_PRESS_SHORT_START` 事件。
### 按键注册接口
@ -222,12 +242,22 @@ flex_button_event_t flex_button_event_read(flex_button_t* button);
### 按键扫描接口
按键扫描的核心函数,需要放到应用程序中定时扫描间隔 5-20ms 即可
按键扫描的核心函数,需要放到应用程序中定时扫描,扫描间隔建议 20 毫秒
```C
void flex_button_scan(void);
```
## 注意事项
- 阻塞问题
因为按键事件回调函数以及按键键值读取函数是在按键扫描的过程中执行的,因此请不要在这类函数中使用阻塞接口,不要进行延时操作。
- 按键扫描函数栈需求
按键扫描函数本身对栈的需求小于 300 字节,但是按键事件回调函数和按键键值读取函数都是在按键扫描函数的上下文中执行的,请格外关心按键事件回调函数与按键键值读取函数对栈空间的需求。
## 其它
### 关于低功耗
@ -255,11 +285,11 @@ void flex_button_scan(void);
### 关于组合按键
该按键库仅做了底层的按键扫描处理,一次扫描可以确定所有的按键状态,并上报对应的按键事件,如果需要支持组合按键,请再封一层,根据按键库返回的事件封装需要的组合按键。
该按键库仅做了底层的按键扫描处理,一次扫描可以确定所有的按键状态,并上报对应的按键事件,如果需要支持组合按键,请再封一层,根据按键库返回的事件封装需要的组合按键。[示例程序](./flexible_button_demo.c)提供了简单的实现。
### 关于矩阵键盘
不管你的矩阵键盘是通过什么通信方式获取按键状态的,只要你将读取按键状态的函数对接到 Flexible_button 数据结构中的 `uint8_t (*usr_button_read)(void);` 函数上即可。
不管你的矩阵键盘是通过什么通信方式获取按键状态的,只要你将读取按键状态的函数对接到 Flexible_button 数据结构中的 `uint8_t (*usr_button_read)(void*);` 函数上即可。
> 参考 [issue 2](https://github.com/murphyzhao/FlexibleButton/issues/2) 中的讨论。

View File

@ -23,23 +23,65 @@
* Change logs:
* Date Author Notes
* 2018-09-29 MurphyZhao First add
* 2019-08-02 MurphyZhao murphyzhao
* 2019-08-02 MurphyZhao Migrate code to github.com/murphyzhao account
* 2019-12-26 MurphyZhao Refactor code and implement combos
*
*/
#include "flexible_button.h"
#include <string.h>
#include <stdio.h>
#ifndef NULL
#define NULL 0
#endif
#define EVENT_SET_AND_EXEC_CB(btn, evt) \
do \
{ \
btn->event = evt; \
if(btn->cb) \
btn->cb((flex_button_t*)btn); \
} while(0);
/**
* BTN_IS_PRESSED
*
* 1: is pressed
* 0: is not pressed
*/
#define BTN_IS_PRESSED(i) (g_btn_status_reg & (1 << i))
enum FLEX_BTN_STAGE
{
FLEX_BTN_STAGE_DEFAULT = 0,
FLEX_BTN_STAGE_DOWN = 1,
FLEX_BTN_STAGE_COMBOS = 2
};
typedef uint32_t btn_type_t;
static flex_button_t *btn_head = NULL;
#define EVENT_CB_EXECUTOR(button) if(button->cb) button->cb((flex_button_t*)button)
#define MAX_BUTTON_CNT 16
/**
* g_logic_level
*
* The logic level of the button pressed,
* Each bit represents a button.
*
* First registered button, the logic level of the button pressed is
* at the low bit of g_logic_level.
*/
btn_type_t g_logic_level = (btn_type_t)0;
/**
* g_btn_status_reg
*
* The status register of all button, each bit records the pressing state of a button.
*
* First registered button, the pressing state of the button is
* at the low bit of g_btn_status_reg.
*/
btn_type_t g_btn_status_reg = (btn_type_t)0;
static uint16_t trg = 0;
static uint16_t cont = 0;
static uint16_t keydata = 0xFFFF;
static uint16_t key_rst_data = 0xFFFF;
static uint8_t button_cnt = 0;
/**
@ -52,7 +94,7 @@ int8_t flex_button_register(flex_button_t *button)
{
flex_button_t *curr = btn_head;
if (!button || (button_cnt > MAX_BUTTON_CNT))
if (!button || (button_cnt > sizeof(btn_type_t) * 8))
{
return -1;
}
@ -66,14 +108,25 @@ int8_t flex_button_register(flex_button_t *button)
curr = curr->next;
}
/**
* First registered button is at the end of the 'linked list'.
* btn_head points to the head of the 'linked list'.
*/
button->next = btn_head;
button->status = 0;
button->status = FLEX_BTN_STAGE_DEFAULT;
button->event = FLEX_BTN_PRESS_NONE;
button->scan_cnt = 0;
button->click_cnt = 0;
button->max_combos_click_solt = MAX_COMBOS_CLICK_SOLT;
btn_head = button;
key_rst_data = key_rst_data << 1;
/**
* First registered button, the logic level of the button pressed is
* at the low bit of g_logic_level.
*/
g_logic_level |= (button->pressed_logic_level << button_cnt);
button_cnt ++;
return button_cnt;
}
@ -85,24 +138,20 @@ int8_t flex_button_register(flex_button_t *button)
*/
static void flex_button_read(void)
{
uint8_t i;
flex_button_t* target;
uint16_t read_data = 0;
keydata = key_rst_data;
int8_t i = 0;
for(target = btn_head, i = 0;
/* The button that was registered first, the button value is in the low position of raw_data */
btn_type_t raw_data = 0;
for(target = btn_head, i = button_cnt - 1;
(target != NULL) && (target->usr_button_read != NULL);
target = target->next, i ++)
target = target->next, i--)
{
keydata = keydata |
(target->pressed_logic_level == 1 ?
((!(target->usr_button_read)()) << i) :
((target->usr_button_read)() << i));
raw_data = raw_data | ((target->usr_button_read)(target) << i);
}
read_data = keydata^0xFFFF;
trg = read_data & (read_data ^ cont);
cont = read_data;
g_btn_status_reg = (~raw_data) ^ g_logic_level;
}
/**
@ -114,144 +163,126 @@ static void flex_button_read(void)
*/
static void flex_button_process(void)
{
int8_t i = 0;
uint8_t i;
flex_button_t* target;
for (target = btn_head, i = 0; target != NULL; target = target->next, i ++)
{
if (target->status > 0)
if (target->status > FLEX_BTN_STAGE_DEFAULT)
{
target->scan_cnt ++;
if (target->scan_cnt >= ((1 << (sizeof(target->scan_cnt) * 8)) - 1))
{
target->scan_cnt = target->long_hold_start_tick;
}
}
switch (target->status)
{
case 0: /* is default */
if (trg & (1 << i)) /* is pressed */
case FLEX_BTN_STAGE_DEFAULT: // stage: default(button up)
if (BTN_IS_PRESSED(i)) // is pressed
{
target->scan_cnt = 0;
target->click_cnt = 0;
EVENT_SET_AND_EXEC_CB(target, FLEX_BTN_PRESS_DOWN);
/* swtich to button down stage */
target->status = FLEX_BTN_STAGE_DOWN;
}
else
{
target->event = FLEX_BTN_PRESS_NONE;
}
break;
case FLEX_BTN_STAGE_DOWN: // stage: button down
if (BTN_IS_PRESSED(i)) // is pressed
{
if (target->click_cnt > 0) // combos
{
target->scan_cnt = 0;
target->click_cnt = 0;
target->status = 1;
target->event = FLEX_BTN_PRESS_DOWN;
EVENT_CB_EXECUTOR(target);
if (target->scan_cnt > target->max_combos_click_solt)
{
EVENT_SET_AND_EXEC_CB(target,
target->click_cnt < FLEX_BTN_PRESS_REPEAT_CLICK ?
target->click_cnt :
FLEX_BTN_PRESS_REPEAT_CLICK);
/* swtich to button down stage */
target->status = FLEX_BTN_STAGE_DOWN;
target->scan_cnt = 0;
target->click_cnt = 0;
}
}
else if (target->scan_cnt >= target->long_hold_start_tick)
{
if (target->event != FLEX_BTN_PRESS_LONG_HOLD)
{
EVENT_SET_AND_EXEC_CB(target, FLEX_BTN_PRESS_LONG_HOLD);
}
}
else if (target->scan_cnt >= target->long_press_start_tick)
{
if (target->event != FLEX_BTN_PRESS_LONG_START)
{
EVENT_SET_AND_EXEC_CB(target, FLEX_BTN_PRESS_LONG_START);
}
}
else if (target->scan_cnt >= target->short_press_start_tick)
{
if (target->event != FLEX_BTN_PRESS_SHORT_START)
{
EVENT_SET_AND_EXEC_CB(target, FLEX_BTN_PRESS_SHORT_START);
}
}
}
else // is up
{
if (target->scan_cnt >= target->long_hold_start_tick)
{
EVENT_SET_AND_EXEC_CB(target, FLEX_BTN_PRESS_LONG_HOLD_UP);
target->status = FLEX_BTN_STAGE_DEFAULT;
}
else if (target->scan_cnt >= target->long_press_start_tick)
{
EVENT_SET_AND_EXEC_CB(target, FLEX_BTN_PRESS_LONG_UP);
target->status = FLEX_BTN_STAGE_DEFAULT;
}
else if (target->scan_cnt >= target->short_press_start_tick)
{
EVENT_SET_AND_EXEC_CB(target, FLEX_BTN_PRESS_SHORT_UP);
target->status = FLEX_BTN_STAGE_DEFAULT;
}
else
{
target->event = FLEX_BTN_PRESS_NONE;
/* swtich to combos stage */
target->status = FLEX_BTN_STAGE_COMBOS;
target->click_cnt ++;
}
break;
}
break;
case 1: /* is pressed */
if (!(cont & (1 << i))) /* is up */
case FLEX_BTN_STAGE_COMBOS: // stage: combos
if (BTN_IS_PRESSED(i)) // is pressed
{
/* swtich to button down stage */
target->status = FLEX_BTN_STAGE_DOWN;
target->scan_cnt = 0;
}
else
{
if (target->scan_cnt > target->max_combos_click_solt)
{
target->status = 2;
}
else if ((target->scan_cnt >= target->short_press_start_tick) &&
(target->scan_cnt < target->long_press_start_tick))
{
target->status = 4;
target->event = FLEX_BTN_PRESS_SHORT_START;
EVENT_CB_EXECUTOR(target);
}
break;
EVENT_SET_AND_EXEC_CB(target,
target->click_cnt < FLEX_BTN_PRESS_REPEAT_CLICK ?
target->click_cnt :
FLEX_BTN_PRESS_REPEAT_CLICK);
case 2: /* is up */
if ((target->scan_cnt < target->click_start_tick))
{
target->click_cnt++; // 1
if (target->click_cnt == 1)
{
target->status = 3; /* double click check */
}
else
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_DOUBLE_CLICK;
EVENT_CB_EXECUTOR(target);
}
/* swtich to default stage */
target->status = FLEX_BTN_STAGE_DEFAULT;
}
else if ((target->scan_cnt >= target->click_start_tick) &&
(target->scan_cnt < target->short_press_start_tick))
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_CLICK;
EVENT_CB_EXECUTOR(target);
}
else if ((target->scan_cnt >= target->short_press_start_tick) &&
(target->scan_cnt < target->long_press_start_tick))
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_SHORT_UP;
EVENT_CB_EXECUTOR(target);
}
else if ((target->scan_cnt >= target->long_press_start_tick) &&
(target->scan_cnt < target->long_hold_start_tick))
{
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_LONG_UP;
EVENT_CB_EXECUTOR(target);
}
else if (target->scan_cnt >= target->long_hold_start_tick)
{
/* long press hold up, not deal */
target->click_cnt = 0;
target->status = 0;
target->event = FLEX_BTN_PRESS_LONG_HOLD_UP;
EVENT_CB_EXECUTOR(target);
}
break;
case 3: /* double click check */
if (trg & (1 << i))
{
target->click_cnt++;
target->status = 2;
target->scan_cnt --;
}
else if (target->scan_cnt >= target->click_start_tick)
{
target->status = 2;
}
break;
case 4: /* is short pressed */
if (!(cont & (1 << i))) /* is up */
{
target->status = 2;
}
else if ((target->scan_cnt >= target->long_press_start_tick) &&
(target->scan_cnt < target->long_hold_start_tick))
{
target->status = 5;
target->event = FLEX_BTN_PRESS_LONG_START;
EVENT_CB_EXECUTOR(target);
}
break;
case 5: /* is long pressed */
if (!(cont & (1 << i))) /* is up */
{
target->status = 2;
}
else if (target->scan_cnt >= target->long_hold_start_tick)
{
target->status = 6;
target->event = FLEX_BTN_PRESS_LONG_HOLD;
EVENT_CB_EXECUTOR(target);
}
break;
case 6: /* is long pressed */
if (!(cont & (1 << i))) /* is up */
{
target->status = 2;
}
break;
}
break;
}
}
}
@ -284,4 +315,3 @@ void flex_button_scan(void)
flex_button_read();
flex_button_process();
}

View File

@ -23,7 +23,8 @@
* Change logs:
* Date Author Notes
* 2018-09-29 MurphyZhao First add
* 2019-08-02 MurphyZhao murphyzhao
* 2019-08-02 MurphyZhao Migrate code to github.com/murphyzhao account
* 2019-12-26 MurphyZhao Refactor code and implement combos
*
*/
@ -31,7 +32,12 @@
#define __FLEXIBLE_BUTTON_H__
#include "stdint.h"
#include "string.h"
#define FLEX_BTN_SCAN_FREQ_HZ 50 // How often flex_button_scan () is called
#define FLEX_MS_TO_SCAN_CNT(ms) (ms / (1000 / FLEX_BTN_SCAN_FREQ_HZ))
/* Combos slot, default 300ms */
#define MAX_COMBOS_CLICK_SOLT (FLEX_MS_TO_SCAN_CNT(300))
typedef void (*flex_button_response_callback)(void*);
@ -40,6 +46,7 @@ typedef enum
FLEX_BTN_PRESS_DOWN = 0,
FLEX_BTN_PRESS_CLICK,
FLEX_BTN_PRESS_DOUBLE_CLICK,
FLEX_BTN_PRESS_REPEAT_CLICK,
FLEX_BTN_PRESS_SHORT_START,
FLEX_BTN_PRESS_SHORT_UP,
FLEX_BTN_PRESS_LONG_START,
@ -56,55 +63,82 @@ typedef enum
* @brief Button data structure
* Below are members that need to user init before scan.
*
* @member pressed_logic_level: Logic level when the button is pressed.
* Must be inited by 'flex_button_register' API
* before start button scan.
* @member debounce_tick: The time of button debounce.
* The value is number of button scan cycles.
* @member click_start_tick: The time of start click.
* The value is number of button scan cycles.
* @member short_press_start_tick: The time of short press start tick.
* The value is number of button scan cycles.
* @member long_press_start_tick: The time of long press start tick.
* The value is number of button scan cycles.
* @member long_hold_start_tick: The time of hold press start tick.
* The value is number of button scan cycles.
* @member usr_button_read: Read the logic level value of specified button.
* @member cb: Button event callback function.
* If use 'flex_button_event_read' api,
* you don't need to initialize the 'cb' member.
* @member next : Next button struct
* @member next
* Internal use.
* One-way linked list, pointing to the next button.
*
* @member usr_button_read
* User function is used to read button vaule.
*
* @member cb
* Button event callback function.
*
* @member scan_cnt
* Internal use, user read-only.
* Number of scans, counted when the button is pressed, plus one per scan cycle.
*
* @member click_cnt
* Internal use, user read-only.
* Number of button clicks
*
* @member max_combos_click_solt
* Combo slot. Default 'MAX_COMBOS_CLICK_SOLT'.
* Need to use FLEX_MS_TO_SCAN_CNT to convert milliseconds into scan cnts.
*
* @member debounce_tick
* Debounce. Not used yet.
* Need to use FLEX_MS_TO_SCAN_CNT to convert milliseconds into scan cnts.
*
* @member short_press_start_tick
* Short press start time. Requires user configuration.
* Need to use FLEX_MS_TO_SCAN_CNT to convert milliseconds into scan cnts.
*
* @member long_press_start_tick
* Long press start time. Requires user configuration.
* Need to use FLEX_MS_TO_SCAN_CNT to convert milliseconds into scan cnts.
*
* @member long_hold_start_tick
* Long hold press start time. Requires user configuration.
*
* @member id
* Button id. Requires user configuration.
* When multiple buttons use the same button callback function,
* they are used to distinguish the buttons.
* Each button id must be unique.
*
* @member pressed_logic_level
* Requires user configuration.
* The logic level of the button pressed, each bit represents a button.
*
* @member event
* Internal use, users can call 'flex_button_event_read' to get current button event.
* Used to record the current button event.
*
* @member status
* Internal use, user unavailable.
* Used to record the current state of buttons.
*
*/
typedef struct flex_button
{
uint8_t pressed_logic_level : 1; /* need user to init */
struct flex_button* next;
/**
* @event
* The event of button in flex_button_evnt_t enum list.
* Automatically initialized to the default value FLEX_BTN_PRESS_NONE
* by 'flex_button_register' API.
*/
uint8_t event : 4;
uint8_t (*usr_button_read)(void *);
flex_button_response_callback cb;
/**
* @status
* Used to record the status of the button
* Automatically initialized to the default value 0.
*/
uint8_t status : 3;
uint16_t scan_cnt; /* default 0. Used to record the number of key scans */
uint16_t click_cnt; /* default 0. Used to record the number of key click */
uint16_t scan_cnt;
uint16_t click_cnt;
uint16_t max_combos_click_solt;
uint16_t debounce_tick;
uint16_t click_start_tick;
uint16_t short_press_start_tick;
uint16_t long_press_start_tick;
uint16_t long_hold_start_tick;
uint8_t (*usr_button_read)(void);
flex_button_response_callback cb;
struct flex_button* next;
uint8_t id;
uint8_t pressed_logic_level : 1;
uint8_t event : 4;
uint8_t status : 3;
} flex_button_t;
#ifdef __cplusplus

View File

@ -23,15 +23,32 @@
* Change logs:
* Date Author Notes
* 2018-09-29 MurphyZhao First add
* 2019-08-02 MurphyZhao murphyzhao
* 2019-08-02 MurphyZhao Migrate code to github.com/murphyzhao account
*/
#include <rtthread.h>
#include "flexible_button.h"
#include <stdint.h>
#include <rtdevice.h>
#include <board.h>
#include "flexible_button.h"
#ifndef PIN_KEY0
#define PIN_KEY0 GET_PIN(D, 10)
#endif
#ifndef PIN_KEY1
#define PIN_KEY1 GET_PIN(D, 9)
#endif
#ifndef PIN_KEY2
#define PIN_KEY2 GET_PIN(D, 8)
#endif
#ifndef PIN_WK_UP
#define PIN_WK_UP GET_PIN(C, 13)
#endif
#define ENUM_TO_STR(e) (#e)
typedef enum
{
USER_BUTTON_0 = 0,
@ -41,104 +58,79 @@ typedef enum
USER_BUTTON_MAX
} user_button_t;
static char *enum_event_string[] = {
ENUM_TO_STR(FLEX_BTN_PRESS_DOWN),
ENUM_TO_STR(FLEX_BTN_PRESS_CLICK),
ENUM_TO_STR(FLEX_BTN_PRESS_DOUBLE_CLICK),
ENUM_TO_STR(FLEX_BTN_PRESS_REPEAT_CLICK),
ENUM_TO_STR(FLEX_BTN_PRESS_SHORT_START),
ENUM_TO_STR(FLEX_BTN_PRESS_SHORT_UP),
ENUM_TO_STR(FLEX_BTN_PRESS_LONG_START),
ENUM_TO_STR(FLEX_BTN_PRESS_LONG_UP),
ENUM_TO_STR(FLEX_BTN_PRESS_LONG_HOLD),
ENUM_TO_STR(FLEX_BTN_PRESS_LONG_HOLD_UP),
ENUM_TO_STR(FLEX_BTN_PRESS_MAX),
ENUM_TO_STR(FLEX_BTN_PRESS_NONE),
};
static char *enum_btn_id_string[] = {
ENUM_TO_STR(USER_BUTTON_0),
ENUM_TO_STR(USER_BUTTON_1),
ENUM_TO_STR(USER_BUTTON_2),
ENUM_TO_STR(USER_BUTTON_3),
ENUM_TO_STR(USER_BUTTON_MAX),
};
static flex_button_t user_button[USER_BUTTON_MAX];
static void btn_0_cb(flex_button_t *btn)
static uint8_t common_btn_read(void *arg)
{
rt_kprintf("btn_0_cb\n");
switch (btn->event)
uint8_t value = 0;
flex_button_t *btn = (flex_button_t *)arg;
switch (btn->id)
{
case FLEX_BTN_PRESS_DOWN:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOWN]\n");
break;
case FLEX_BTN_PRESS_CLICK:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_CLICK]\n");
break;
case FLEX_BTN_PRESS_DOUBLE_CLICK:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_DOUBLE_CLICK]\n");
break;
case FLEX_BTN_PRESS_SHORT_START:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_START]\n");
break;
case FLEX_BTN_PRESS_SHORT_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_SHORT_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_START:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_START]\n");
break;
case FLEX_BTN_PRESS_LONG_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD_UP:
rt_kprintf("btn_0_cb [FLEX_BTN_PRESS_LONG_HOLD_UP]\n");
break;
case USER_BUTTON_0:
value = rt_pin_read(PIN_KEY0);
break;
case USER_BUTTON_1:
value = rt_pin_read(PIN_KEY1);
break;
case USER_BUTTON_2:
value = rt_pin_read(PIN_KEY2);
break;
case USER_BUTTON_3:
value = rt_pin_read(PIN_WK_UP);
break;
default:
RT_ASSERT(0);
}
return value;
}
static void btn_1_cb(flex_button_t *btn)
static void common_btn_evt_cb(void *arg)
{
rt_kprintf("btn_1_cb\n");
switch (btn->event)
flex_button_t *btn = (flex_button_t *)arg;
rt_kprintf("id: [%d - %s] event: [%d - %30s] combos: %d\n",
btn->id, enum_btn_id_string[btn->id],
btn->event, enum_event_string[btn->event],
btn->click_cnt);
if (flex_button_event_read(&user_button[USER_BUTTON_0]) == flex_button_event_read(&user_button[USER_BUTTON_1]) == FLEX_BTN_PRESS_CLICK)
{
case FLEX_BTN_PRESS_DOWN:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_DOWN]\n");
break;
case FLEX_BTN_PRESS_CLICK:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_CLICK]\n");
break;
case FLEX_BTN_PRESS_DOUBLE_CLICK:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_DOUBLE_CLICK]\n");
break;
case FLEX_BTN_PRESS_SHORT_START:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_SHORT_START]\n");
break;
case FLEX_BTN_PRESS_SHORT_UP:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_SHORT_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_START:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_LONG_START]\n");
break;
case FLEX_BTN_PRESS_LONG_UP:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_LONG_UP]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_LONG_HOLD]\n");
break;
case FLEX_BTN_PRESS_LONG_HOLD_UP:
rt_kprintf("btn_1_cb [FLEX_BTN_PRESS_LONG_HOLD_UP]\n");
break;
rt_kprintf("[combination]: button 0 and button 1\n");
}
}
static uint8_t button_key0_read(void)
{
return rt_pin_read(PIN_KEY0);
}
static uint8_t button_key1_read(void)
{
return rt_pin_read(PIN_KEY1);
}
static uint8_t button_key2_read(void)
{
return rt_pin_read(PIN_KEY2);
}
static uint8_t button_keywkup_read(void)
{
return rt_pin_read(PIN_WK_UP);
}
static void button_scan(void *arg)
{
while(1)
{
flex_button_scan();
rt_thread_mdelay(20);
rt_thread_mdelay(20); // 20 ms
}
}
@ -148,15 +140,6 @@ static void user_button_init(void)
rt_memset(&user_button[0], 0x0, sizeof(user_button));
user_button[USER_BUTTON_0].usr_button_read = button_key0_read;
user_button[USER_BUTTON_0].cb = (flex_button_response_callback)btn_0_cb;
user_button[USER_BUTTON_1].usr_button_read = button_key1_read;
user_button[USER_BUTTON_1].cb = (flex_button_response_callback)btn_1_cb;
user_button[USER_BUTTON_2].usr_button_read = button_key2_read;
user_button[USER_BUTTON_3].usr_button_read = button_keywkup_read;
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); /* set KEY pin mode to input */
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP); /* set KEY pin mode to input */
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLUP); /* set KEY pin mode to input */
@ -164,11 +147,13 @@ static void user_button_init(void)
for (i = 0; i < USER_BUTTON_MAX; i ++)
{
user_button[i].id = i;
user_button[i].usr_button_read = common_btn_read;
user_button[i].cb = common_btn_evt_cb;
user_button[i].pressed_logic_level = 0;
user_button[i].click_start_tick = 20;
user_button[i].short_press_start_tick = 100;
user_button[i].long_press_start_tick = 200;
user_button[i].long_hold_start_tick = 300;
user_button[i].short_press_start_tick = FLEX_MS_TO_SCAN_CNT(1500);
user_button[i].long_press_start_tick = FLEX_MS_TO_SCAN_CNT(3000);
user_button[i].long_hold_start_tick = FLEX_MS_TO_SCAN_CNT(4500);
if (i == USER_BUTTON_3)
{