发帖
3 0 0

【学习笔记】AI-M61-32S 按键处理

putin
论坛元老

23

主题

132

回帖

3748

积分

论坛元老

积分
3748
小安派&M61教程合集 45 3 前天 16:39
[i=s] 本帖最后由 putin 于 2025-9-20 16:39 编辑 [/i]

一、基本架构

image.png

最后实现的结果为:单击、双击、长按、一起按(一起目前有个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) {
        // 这里不会被执行
    }
}

四、结果

image.png

──── 0人觉得很赞 ────

使用道具 举报

前天 22:22
点赞😀
学习学习!
点赞
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 29801 个
  • 话题数: 43429 篇