[i=s] 本帖最后由 putin 于 2025-9-20 16:39 编辑 [/i]
一、基本架构

最后实现的结果为:单击、双击、长按、一起按(一起目前有个bug, 双击完之后虽然会双击,但是按键抬起来回再一次报单机或长按)
二、实现原理
static void key_scan(Button *button, struct bflb_device_s *gpio)
{
uint8_t key_val = bflb_gpio_read(gpio, button->pin);
uint32_t current_time = bflb_mtimer_get_time_ms();
switch(button->state) {
case KEY_STATE_IDLE:
if (key_val == 0) { // 按键按下
button->state = KEY_STATE_PRESS_DOWN;
button->press_time = current_time;
button->counter = 0;
}
break;
case KEY_STATE_PRESS_DOWN:
if (key_val == 1) { // 按键释放
if (current_time - button->press_time > DEBOUNCE_TIME * 10) { // 消抖完成
button->state = KEY_STATE_PRESS_UP;
button->release_time = current_time;
} else {
button->state = KEY_STATE_IDLE; // 抖动,忽略
}
} else if (current_time - button->press_time > LONG_PRESS_TIME * 10) {
button->state = KEY_STATE_LONG_PRESS;
button->action = KEY_ACTION_LONG_PRESS;
}
break;
case KEY_STATE_PRESS_UP:
if (current_time - button->release_time < DOUBLE_CLICK_TIME * 10) {
if (button->click_count == 0) {
// 第一次释放,等待可能的第二次按下
button->state = KEY_STATE_DOUBLE_WAIT;
} else {
// 第二次释放,确认为双击
button->action = KEY_ACTION_DOUBLE_CLICK;
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
} else {
// 超时,确认为单击
button->action = KEY_ACTION_CLICK;
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
break;
case KEY_STATE_DOUBLE_WAIT:
if (key_val == 0) { // 第二次按下
button->click_count++;
button->state = KEY_STATE_PRESS_DOWN;
button->press_time = current_time;
} else if (current_time - button->release_time > DOUBLE_CLICK_TIME * 10) {
// 超时,确认为单击
button->action = KEY_ACTION_CLICK;
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
break;
case KEY_STATE_LONG_PRESS:
if (key_val == 1) { // 长按释放
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
break;
default:
button->state = KEY_STATE_IDLE;
break;
}
}
1. 初始状态 (KEY_STATE_IDLE)
- 按键处于释放状态,等待用户按下
- 当检测到按键按下(
key_val == 0
)时,切换到 KEY_STATE_PRESS_DOWN
状态
- 记录按下时间戳,重置计数器
2. 按下状态 (KEY_STATE_PRESS_DOWN)
- 处理两种可能的情况:
- 按键释放:如果释放时间与按下时间间隔大于消抖时间,确认为有效按下,进入
KEY_STATE_PRESS_UP
状态
- 持续按下:如果按下时间超过长按阈值,进入
KEY_STATE_LONG_PRESS
状态,标记长按动作
3. 释放状态 (KEY_STATE_PRESS_UP)
- 判断释放后的时间间隔:
- 如果间隔小于双击等待时间:
- 若是第一次释放,进入
KEY_STATE_DOUBLE_WAIT
状态等待可能的第二次按下
- 若是第二次释放,确认为双击动作
- 如果超时,确认为单击动作
4. 双击等待状态 (KEY_STATE_DOUBLE_WAIT)
- 等待可能的第二次按键按下:
- 如果检测到第二次按下,增加点击计数并回到按下状态
- 如果超时未检测到第二次按下,确认为单击动作
5. 长按状态 (KEY_STATE_LONG_PRESS)
- 按键持续按下超过长按时间阈值
- 只有当按键释放时,才返回到空闲状态
三、源代码
#include "board.h"
#include "log.h"
#include "bflb_gpio.h"
#include "bflb_mtimer.h"
#include <FreeRTOS.h>
#include "semphr.h"
#include "stdio.h"
#define DBG_TAG "MAIN"
BFLOG_DEFINE_TAG(MAIN, DBG_TAG, true);
// 按键定义
#define KEY_GPIO_PIN1 18
#define KEY_GPIO_PIN2 19
// 时间常量定义 (单位: 10ms)
#define DEBOUNCE_TIME 2 // 消抖时间 (20ms)
#define CLICK_TIME 5 // 单击时间 (50ms)
#define DOUBLE_CLICK_TIME 15 // 双击间隔时间 (150ms)
#define LONG_PRESS_TIME 50 // 长按时间 (500ms)
/**
* 按键状态枚举
* 描述按键状态机的各个状态
*/
typedef enum {
KEY_STATE_IDLE = 0, // 空闲状态,等待按键按下
KEY_STATE_PRESS_DOWN, // 按键按下状态,检测消抖和长按
KEY_STATE_PRESS_UP, // 按键释放状态,判断单击或双击开始
KEY_STATE_DOUBLE_WAIT, // 等待第二次按键按下(双击检测)
KEY_STATE_LONG_PRESS, // 长按状态,等待按键释放
} KeyState;
/**
* 按键动作枚举
* 描述按键触发的具体动作类型
*/
typedef enum {
KEY_ACTION_NONE = 0, // 无动作
KEY_ACTION_CLICK, // 单击动作
KEY_ACTION_DOUBLE_CLICK, // 双击动作
KEY_ACTION_LONG_PRESS, // 长按动作
} KeyAction;
/**
* LED状态枚举
* 描述LED的显示状态
*/
typedef enum {
LED_OFF, // LED关闭状态
LED_ON, // LED常亮状态
LED_BLINKING // LED闪烁状态
} LedState;
/**
* 按键数据结构
* 存储单个按键的所有相关信息
*/
typedef struct {
uint8_t pin; // 按键对应的GPIO引脚号
KeyState state; // 当前按键状态
KeyAction action; // 触发的按键动作
uint32_t press_time; // 按键按下时间戳(ms)
uint32_t release_time;// 按键释放时间戳(ms)
uint32_t counter; // 状态计数器
uint8_t click_count; // 连击次数计数(用于检测双击)
} Button;
/**
* 按键管理器结构体
* 集中管理所有按键相关数据和资源
*/
typedef struct {
Button button1; // 按键1实例
Button button2; // 按键2实例
SemaphoreHandle_t sem; // 按键操作信号量(用于线程安全)
struct bflb_device_s *gpio; // GPIO设备指针
bool both_pressed; // 组合按键按下标志
bool both_released; // 组合按键释放标志
LedState led_state; // LED当前显示状态
} KeyManager;
static KeyManager key_manager = {
.button1 = {KEY_GPIO_PIN1, KEY_STATE_IDLE, KEY_ACTION_NONE, 0, 0, 0, 0},
.button2 = {KEY_GPIO_PIN2, KEY_STATE_IDLE, KEY_ACTION_NONE, 0, 0, 0, 0},
.sem = NULL,
.gpio = NULL,
.both_pressed = false,
.both_released = false,
.led_state = LED_OFF
};
// 按键初始化函数
static void key_init(void)
{
key_manager.gpio = bflb_device_get_by_name("gpio");
// 初始化按键GPIO
bflb_gpio_init(key_manager.gpio, KEY_GPIO_PIN1, GPIO_INPUT | GPIO_PULLUP | GPIO_SMT_EN);
bflb_gpio_init(key_manager.gpio, KEY_GPIO_PIN2, GPIO_INPUT | GPIO_PULLUP | GPIO_SMT_EN);
bflb_gpio_init(key_manager.gpio, GPIO_PIN_29, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN);
// 初始化LED状态
bflb_gpio_reset(key_manager.gpio, GPIO_PIN_29);
key_manager.sem = xSemaphoreCreateBinary();
xSemaphoreGive(key_manager.sem);
}
// 按键扫描函数
static void key_scan(Button *button, struct bflb_device_s *gpio)
{
uint8_t key_val = bflb_gpio_read(gpio, button->pin);
uint32_t current_time = bflb_mtimer_get_time_ms();
switch(button->state) {
case KEY_STATE_IDLE:
if (key_val == 0) { // 按键按下
button->state = KEY_STATE_PRESS_DOWN;
button->press_time = current_time;
button->counter = 0;
}
break;
case KEY_STATE_PRESS_DOWN:
if (key_val == 1) { // 按键释放
if (current_time - button->press_time > DEBOUNCE_TIME * 10) { // 消抖完成
button->state = KEY_STATE_PRESS_UP;
button->release_time = current_time;
} else {
button->state = KEY_STATE_IDLE; // 抖动,忽略
}
} else if (current_time - button->press_time > LONG_PRESS_TIME * 10) {
button->state = KEY_STATE_LONG_PRESS;
button->action = KEY_ACTION_LONG_PRESS;
}
break;
case KEY_STATE_PRESS_UP:
if (current_time - button->release_time < DOUBLE_CLICK_TIME * 10) {
if (button->click_count == 0) {
// 第一次释放,等待可能的第二次按下
button->state = KEY_STATE_DOUBLE_WAIT;
} else {
// 第二次释放,确认为双击
button->action = KEY_ACTION_DOUBLE_CLICK;
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
} else {
// 超时,确认为单击
button->action = KEY_ACTION_CLICK;
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
break;
case KEY_STATE_DOUBLE_WAIT:
if (key_val == 0) { // 第二次按下
button->click_count++;
button->state = KEY_STATE_PRESS_DOWN;
button->press_time = current_time;
} else if (current_time - button->release_time > DOUBLE_CLICK_TIME * 10) {
// 超时,确认为单击
button->action = KEY_ACTION_CLICK;
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
break;
case KEY_STATE_LONG_PRESS:
if (key_val == 1) { // 长按释放
button->state = KEY_STATE_IDLE;
button->click_count = 0;
}
break;
default:
button->state = KEY_STATE_IDLE;
break;
}
}
// 按键扫描任务
static void key_scan_task(void *pvParameters)
{
while (1) {
xSemaphoreTake(key_manager.sem, portMAX_DELAY);
// 扫描两个按键
key_scan(&key_manager.button1, key_manager.gpio);
key_scan(&key_manager.button2, key_manager.gpio);
// 检测组合按键(两个按键同时按下)
if (bflb_gpio_read(key_manager.gpio, key_manager.button1.pin) == 0 &&
bflb_gpio_read(key_manager.gpio, key_manager.button2.pin) == 0) {
if (!key_manager.both_pressed) {
LOG_I("Both keys pressed simultaneously\r\n");
key_manager.led_state = LED_OFF;
bflb_gpio_reset(key_manager.gpio, GPIO_PIN_29); // 关闭LED
key_manager.both_pressed = true;
// 重置按键状态
key_manager.button1.state = KEY_STATE_IDLE;
key_manager.button2.state = KEY_STATE_IDLE;
key_manager.button1.action = KEY_ACTION_NONE;
key_manager.button2.action = KEY_ACTION_NONE;
key_manager.button1.click_count = 0;
key_manager.button2.click_count = 0;
}
} else {
key_manager.both_pressed = false;
}
xSemaphoreGive(key_manager.sem);
vTaskDelay(10); // 10ms扫描间隔
}
}
// 按键事件处理函数
static void key_process_events(void)
{
// 按键1事件处理
if (key_manager.button1.action != KEY_ACTION_NONE) {
switch(key_manager.button1.action) {
case KEY_ACTION_CLICK:
LOG_I("Key1 single click\r\n");
key_manager.led_state = LED_ON;
bflb_gpio_set(key_manager.gpio, GPIO_PIN_29);
break;
case KEY_ACTION_DOUBLE_CLICK:
LOG_I("Key1 double click\r\n");
key_manager.led_state = LED_BLINKING;
break;
case KEY_ACTION_LONG_PRESS:
LOG_I("Key1 long press\r\n");
break;
default:
break;
}
key_manager.button1.action = KEY_ACTION_NONE;
}
// 按键2事件处理
if (key_manager.button2.action != KEY_ACTION_NONE) {
switch(key_manager.button2.action) {
case KEY_ACTION_CLICK:
LOG_I("Key2 single click\r\n");
key_manager.led_state = LED_ON;
bflb_gpio_set(key_manager.gpio, GPIO_PIN_29);
break;
case KEY_ACTION_DOUBLE_CLICK:
LOG_I("Key2 double click\r\n");
key_manager.led_state = LED_BLINKING;
break;
case KEY_ACTION_LONG_PRESS:
LOG_I("Key2 long press\r\n");
break;
default:
break;
}
key_manager.button2.action = KEY_ACTION_NONE;
}
}
// LED闪烁控制任务
static void led_control_task(void *pvParameters)
{
static uint32_t last_blink_time = 0;
bool led_on = false;
while (1) {
uint32_t current_time = bflb_mtimer_get_time_ms();
// 处理LED状态
switch(key_manager.led_state) {
case LED_OFF:
bflb_gpio_reset(key_manager.gpio, GPIO_PIN_29);
break;
case LED_ON:
bflb_gpio_set(key_manager.gpio, GPIO_PIN_29);
break;
case LED_BLINKING:
if (current_time - last_blink_time > 500) { // 500ms闪烁周期
led_on = !led_on;
if (led_on) {
bflb_gpio_set(key_manager.gpio, GPIO_PIN_29);
} else {
bflb_gpio_reset(key_manager.gpio, GPIO_PIN_29);
}
last_blink_time = current_time;
}
break;
}
vTaskDelay(10);
}
}
// 打印任务
static void print_task(void *pvParameters)
{
while (1) {
key_process_events();
printf("System running...\r\n");
vTaskDelay(100);
}
}
int main(void)
{
board_init();
key_init();
// 创建按键扫描任务
LOG_I("Starting key scan task...\r\n");
xTaskCreate(key_scan_task, "key_scan_task", 512, NULL, configMAX_PRIORITIES - 2, NULL);
// 创建LED控制任务
xTaskCreate(led_control_task, "led_control_task", 256, NULL, configMAX_PRIORITIES - 1, NULL);
// 创建打印任务
xTaskCreate(print_task, "print_task", 256, NULL, configMAX_PRIORITIES - 1, NULL);
vTaskStartScheduler();
while (1) {
// 这里不会被执行
}
}
四、结果
