AhFeiButton
是一个用于小型嵌入式平台的按键处理库。采用了类似MVC的架构:驱动层, 逻辑层,应用层。理论上支持普通按键与自锁按键,并可以无限扩展按键数量, 支持的事件有:单击,双击,短按,长按,多次点按,按下,松开。另外,AhFeiButton使用扫描的方式一次性读取所有所有的按键状态,然后通过事件回调机制上报按键事件。最强大的一点是可以为每个button做个性化配置,比如说,button1按下多长时间为短按,多长时间为长按,间隔多长时间为双击,每个button单独配置,灵活性强,能适应强复杂度场景,如果按照平常写法,多几个有特色的按键,你的if else 和标志位就是满天飞了,逻辑还不好维护。
该按键库使用 C 语言编写,驱动与应用程序解耦,便于灵活应用,比如用户可以方便地在应用层增加按键中断、处理按键功耗、定义按键事件处理方式,而无需修改API源码。支持32位平台,以及所有的OS平台,示例为STM32G474+RT-Thread+IO设备驱动.
原文链接
代码讲解
代码中已经写了足够的注释,这里就不再赘述,请看代码。
AhFeiButton.c
/*********************************************************************************
*Copyright(C) -
*FileName: ahfei_button.c
*Author: 我不是阿沸
*Version: 1.1
*Date: 2023.08.28
*Description: 主要用于复杂的按键操作场景,比如说一个按键需要实现加加减减菜单确认等多种功能,都可以用这个库
*Others:
*History:
1.Date:2023/08/29
Author:我不是阿沸
Modification:修改双击相关处理流程。
2.其他修改
*Copyright Notice:本文为CSDN博主「我不是阿沸」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
*Original link:https://blog.csdn.net/qq_53828053/article/details/132497784
**********************************************************************************/
#include "ahfeiible_button.h"
#include <string.h>
#include <stdio.h>
static ahfei_button_t *btn_head = NULL;
#define EVENT_CB_EXECUTOR(button) if(button->cb) button->cb((ahfei_button_t*)button)
#define MAX_BUTTON_CNT 16
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;
/**
* @brief 按键注册
*
* @param flex_button_t 结构体实例的地址
* @return 返回一个按键索引
*/
int8_t ahfei_button_register(ahfei_button_t *button)
{
ahfei_button_t *curr = btn_head;
if (!button || (button_cnt > MAX_BUTTON_CNT))
{
return -1;
}
while (curr)
{
if(curr == button)
{
return -1;
}
curr = curr->next;
}
button->next = btn_head;
button->status = 0;
button->event = AHFEI_BTN_PRESS_NONE;
button->scan_cnt = 0;
button->click_cnt = 0;
btn_head = button;
key_rst_data = key_rst_data << 1;
button_cnt ++;
return button_cnt;
}
/**
* @brief 按键读取
*
* @param void
* @return none
*/
static void ahfei_button_read(void)
{
ahfei_button_t* target;
uint16_t read_data = 0;
keydata = key_rst_data;
int8_t i = 0;
for(target = btn_head, i = 0;
(target != NULL) && (target->usr_button_read != NULL);
target = target->next, i ++)
{
keydata = keydata |
(target->pressed_logic_level == 1 ?
((!(target->usr_button_read)()) << i) :
((target->usr_button_read)() << i));
}
read_data = keydata^0xFFFF;
trg = read_data & (read_data ^ cont);
cont = read_data;
}
/**
* @brief 按键扫描
* @param void
* @return None
*/
static void ahfei_button_process(void)
{
int8_t i = 0;
ahfei_button_t* target;
for (target = btn_head, i = 0; target != NULL; target = target->next, i ++)
{
if (target->status > 0)
{
target->scan_cnt ++;
}
switch (target->status)
{
case 0:
if (trg & (1 << i))
{
target->scan_cnt = 0;
target->click_cnt = 0;
target->status = 1;
target->event = AHFEI_BTN_PRESS_DOWN;
EVENT_CB_EXECUTOR(target);
}
else
{
target->event = AHFEI_BTN_PRESS_NONE;
}
break;
case 1:
if (!(cont & (1 << i)))
{
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 = AHFEI_BTN_PRESS_SHORT_START;
EVENT_CB_EXECUTOR(target);
}
break;
case 2:
if ((target->scan_cnt < target->click_start_tick))
{
target->click_cnt++; // 1
if (target->click_cnt == 1)
{
target->status = 3;
}
else
{
target->click_cnt = 0;
target->status = 0;
target->event = AHFEI_BTN_PRESS_DOUBLE_CLICK;
EVENT_CB_EXECUTOR(target);
}
}
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 = AHFEI_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 = AHFEI_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 = AHFEI_BTN_PRESS_LONG_UP;
EVENT_CB_EXECUTOR(target);
}
else if (target->scan_cnt >= target->long_hold_start_tick)
{
target->click_cnt = 0;
target->status = 0;
target->event = AHFEI_BTN_PRESS_LONG_HOLD_UP;
EVENT_CB_EXECUTOR(target);
}
break;
case 3:
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:
if (!(cont & (1 << i)))
{
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 = AHFEI_BTN_PRESS_LONG_START;
EVENT_CB_EXECUTOR(target);
}
break;
case 5:
if (!(cont & (1 << i)))
{
target->status = 2;
}
else if (target->scan_cnt >= target->long_hold_start_tick)
{
target->status = 6;
target->event = AHFEI_BTN_PRESS_LONG_HOLD;
EVENT_CB_EXECUTOR(target);
}
break;
case 6:
if (!(cont & (1 << i)))
{
target->status = 2;
}
break;
}
}
}
/**
* @brief 获取指定按键的事件。
* @param 需要获取的按键.
* @return 返回按键事件
*/
ahfei_button_event_t ahfei_button_event_read(ahfei_button_t* button)
{
return (ahfei_button_event_t)(button->event);
}
/**
* @brief 按键扫描任务。
* @param void
* @return none
*/
void ahfei_button_scan(void)
{
ahfei_button_read();
ahfei_button_process();
}
AhFeiButton.h
/*********************************************************************************
*Copyright(C) -
*FileName: ahfei_button.c
*Author: 我不是阿沸
*Version: 1.1
*Date: 2023.08.28
*Description: 主要用于复杂的按键操作场景,比如说一个按键需要实现加加减减菜单确认等多种功能,都可以用这个库
*Others:
*History:
1.Date:2023/08/29
Author:我不是阿沸
Modification:修改双击相关处理流程。
2.其他修改
*Copyright Notice:本文为CSDN博主「我不是阿沸」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
*Original link:https://blog.csdn.net/qq_53828053/article/details/132497784
**********************************************************************************/
#ifndef __AHFEIIBLE_BUTTON_H__
#define __AHFEIIBLE_BUTTON_H__
#include "stdint.h"
#include "string.h"
typedef void (*ahfei_button_response_callback)(void*);
typedef enum
{
AHFEI_BTN_PRESS_DOWN = 0, // 按下事件
AHFEI_BTN_PRESS_CLICK, // 单击事件
AHFEI_BTN_PRESS_DOUBLE_CLICK, // 双击事件
AHFEI_BTN_PRESS_SHORT_START, // 短按开始事件
AHFEI_BTN_PRESS_SHORT_UP, // 短按抬起事件
AHFEI_BTN_PRESS_LONG_START, // 长按开始事件
AHFEI_BTN_PRESS_LONG_UP, // 长按抬起事件
AHFEI_BTN_PRESS_LONG_HOLD, // 长按保持事件
AHFEI_BTN_PRESS_LONG_HOLD_UP, // 长按保持的抬起事件
AHFEI_BTN_PRESS_MAX, //最大
AHFEI_BTN_PRESS_NONE, //
} ahfei_button_event_t;
/**
* ahfei_button_t
*
* @brief 按钮数据结构,需要在扫描前使用init的成员。
* @member pressed_logic_level: 设置按键按下的逻辑电平。1:高电平为按下状态;0:低电平为按下状态
* Must be inited by 'ahfei_button_register' API before start button scan.
* @member debounce_tick: 消抖时间,默认为 0,可以不配置,依靠扫描间隙进行消抖
* @member click_start_tick: 设置触发按键按下事件的起始 tick,有消抖效果
* @member short_press_start_tick: 设置短按事件触发的起始 tick
* @member long_press_start_tick: 设置长按事件触发的起始 tick
* @member long_hold_start_tick: 设置长按保持事件触发的起始 tick
* @member usr_button_read: 用户设备的按键引脚电平读取函数
* @member cb: 设置按键事件回调,用于应用层对按键事件的分类处理
* 如果使用“ahfei_button_event_read”api,您不需要初始化“cb”成员。
* @member next : 按键库使用单向链表串起所有的按键配置
*/
typedef struct ahfei_button
{
uint8_t pressed_logic_level : 1; /* need user to init */
/**
* @event
* The event of button in ahfei_button_evnt_t enum list.
* Automatically initialized to the default value ahfei_BTN_PRESS_NONE
* by 'ahfei_button_register' API.
*/
uint8_t event : 4;
/**
* @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 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);
ahfei_button_response_callback cb;
struct ahfei_button* next;
} ahfei_button_t;
#ifdef __cplusplus
extern "C" {
#endif
int8_t ahfei_button_register(ahfei_button_t * button);
ahfei_button_event_t ahfei_button_event_read(ahfei_button_t * button);
void ahfei_button_scan(void);
#ifdef __cplusplus
}
#endif
#endif /* __ahfeiIBLE_BUTTON_H__ */
demo
#include "button.h"
#include "pin.h"
#define PIN_1 GET_PIN(B, 5)
typedef enum
{
USER_BUTTON_0 = 0,
USER_BUTTON_MAX
} user_button_t;
static ahfei_button_t user_button[USER_BUTTON_MAX];
static void btn_0_cb(ahfei_button_t *btn)
{
rt_kprintf("btn_0_cb\n");
switch (btn->event)
{
case AHFEI_BTN_PRESS_DOWN:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_DOWN]\n");
break;
case AHFEI_BTN_PRESS_CLICK:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_CLICK]\n");
break;
case AHFEI_BTN_PRESS_DOUBLE_CLICK:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_DOUBLE_CLICK]\n");
break;
case AHFEI_BTN_PRESS_SHORT_START:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_SHORT_START]\n");
break;
case AHFEI_BTN_PRESS_SHORT_UP:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_SHORT_UP]\n");
break;
case AHFEI_BTN_PRESS_LONG_START:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_LONG_START]\n");
break;
case AHFEI_BTN_PRESS_LONG_UP:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_LONG_UP]\n");
break;
case AHFEI_BTN_PRESS_LONG_HOLD:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_LONG_HOLD]\n");
break;
case AHFEI_BTN_PRESS_LONG_HOLD_UP:
rt_kprintf("btn_0_cb [AHFEI_BTN_PRESS_LONG_HOLD_UP]\n");
break;
}
}
static uint8_t button_key0_read(void)
{
return rt_pin_read(PIN_1);
}
static void button_scan(void *arg)
{
while(1)
{
ahfei_button_scan();
rt_thread_mdelay(20);
}
}
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 = (ahfei_button_response_callback)btn_0_cb;
rt_pin_mode(PIN_1, PIN_MODE_INPUT); /* set KEY pin mode to input */
for (i = 0; i < USER_BUTTON_MAX; i ++)
{
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;
ahfei_button_register(&user_button[i]);
}
}
int ahfei_button_main(void)
{
rt_thread_t tid = RT_NULL;
user_button_init();
/* Create background ticks thread */
tid = rt_thread_create("ahfei_btn", button_scan, RT_NULL, 1024, 10, 10);
if(tid != RT_NULL)
{
rt_thread_startup(tid);
}
return 0;
}