按键驱动状态机实现

1,给按键状态分为4种,空闲,按下,确认按下,松开,确认松开(空闲)

状态说明

  • IDLE(空闲) :无按键动作,等待有效电平(通常为 0,即按下接地)。

  • PRESS_SHAKE(按下防抖):首次检测到有效电平,启动定时器等待电平稳定。

  • PRESSED(确认按下):电平稳定,执行按下回调(可在此区分短按/长按)。

  • RELEASE_SHAKE(松开防抖):检测到无效电平,启动定时器等待电平稳定。

  • CONFIRM_RELEASE(确认松开):电平稳定,执行松开回调,返回 IDLE(即"确认松开(空闲)")。

2. 轮询模式与中断模式应用场景

触发方式 实现方式 优点 缺点 典型场景
轮询 在系统任务中循环调用 Key_Scan(),周期 10~50ms。 逻辑简单,无中断干扰,便于调试。 占用 CPU 资源,响应有延迟(取决于轮询周期)。 系统已有 20ms 的 RTOS 心跳节拍;简单控制产品(如家电面板)。
中断 配置 EXTI 边沿触发(上升沿/下降沿),在 ISR 中调用 Key_Scan() 响应极快,CPU 空闲时可进入休眠(低功耗)。 需处理抖动(中断可能多次触发),需注意 ISR 中避免耗时操作。 电池供电设备(需唤醒 CPU);需要快速响应按键(如音量键)。

2.1 结构体定义

复制代码
#include "los_timer.h"
#include "los_task.h"
#include "stm32f4xx_hal.h"

/*---------- 按键硬件配置 ----------*/
#define KEY_PORT        GPIOA
#define KEY_PIN         GPIO_PIN_0
#define KEY_PRESS_LEVEL GPIO_PIN_RESET  // 低电平有效(按下为0)
#define KEY_RELEASE_LEVEL GPIO_PIN_SET // 高电平有效(松开为1)

/*---------- 按键状态枚举 ----------*/
typedef enum {
    KEY_IDLE = 0,
    KEY_PRESS_SHAKE,
    KEY_PRESSED,
    KEY_RELEASE_SHAKE,
    KEY_CONFIRM_RELEASE
} Key_State_t;

/*---------- 按键结构体 ----------*/
typedef struct {
    Key_State_t state;
    UINT32 timer_id;          // LiteOS 定时器 ID
    GPIO_TypeDef *port;
    uint16_t pin;
    uint32_t debounce_ms;     // 防抖延时 (ms)
    void (*on_press)(void);   // 按下回调函数指针
    void (*on_release)(void); // 松开回调函数指针
} Key_Handle_t;

static Key_Handle_t g_key = {0};

2.2 GPIO 初始化(输入模式 + 上拉)

复制代码
void Key_GPIO_Init(void) {
    GPIO_InitTypeDef gpio_init = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    gpio_init.Pin = KEY_PIN;
    gpio_init.Mode = GPIO_MODE_INPUT;
    gpio_init.Pull = GPIO_PULLUP;      // 内部上拉,确保按键未按下时读取高电平
    gpio_init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(KEY_PORT, &gpio_init);
}

2.3 按键状态机核心逻辑(定时器回调)

复制代码
/*---------- 按键定时器回调(防抖处理) ----------*/
VOID Key_Timer_Callback(UINT32 arg) {
    GPIO_PinState level = HAL_GPIO_ReadPin(g_key.port, g_key.pin);
    
    switch (g_key.state) {
        case KEY_PRESS_SHAKE:
            if (level == KEY_PRESS_LEVEL) {
                // 电平稳定,确认按下
                g_key.state = KEY_PRESSED;
                if (g_key.on_press) g_key.on_press();
            } else {
                // 抖动,返回空闲
                g_key.state = KEY_IDLE;
            }
            break;
            
        case KEY_RELEASE_SHAKE:
            if (level == KEY_RELEASE_LEVEL) {
                // 电平稳定,确认松开
                g_key.state = KEY_CONFIRM_RELEASE;
                if (g_key.on_release) g_key.on_release();
                g_key.state = KEY_IDLE;  // 回归空闲
            } else {
                // 抖动,回到按下状态
                g_key.state = KEY_PRESSED;
            }
            break;
            
        default:
            break;
    }
}

/*---------- 启动防抖定时器 ----------*/
static void Key_StartTimer(void) {
    if (g_key.timer_id != 0xFFFFFFFF) {
        LOS_TimerDelete(g_key.timer_id);
    }
    // 创建单次定时器,防抖延时
    LOS_TimerCreate(&g_key.timer_id, LOS_HW_MODE_SINGLE, 
                    Key_Timer_Callback, NULL, g_key.debounce_ms);
    LOS_TimerStart(g_key.timer_id);
}

2.4 外部触发接口(中断调用)

复制代码
/*---------- 外部检测触发(由定时器轮询或中断调用) ----------*/
void Key_Scan(void) {
    GPIO_PinState level = HAL_GPIO_ReadPin(g_key.port, g_key.pin);
    
    switch (g_key.state) {
        case KEY_IDLE:
            if (level == KEY_PRESS_LEVEL) {
                g_key.state = KEY_PRESS_SHAKE;
                Key_StartTimer();  // 启动防抖
            }
            break;
            
        case KEY_PRESSED:
            if (level == KEY_RELEASE_LEVEL) {
                g_key.state = KEY_RELEASE_SHAKE;
                Key_StartTimer();  // 启动松开防抖
            }
            break;
            
        default:
            // 防抖中,不做任何事(等待定时器回调)
            break;
    }
}

2.5 用户回调与初始化

复制代码
/*---------- 用户回调函数(举例) ----------*/
void User_Key_Press(void) {
    printf("按键确认按下!\n");
    // 可在此触发任务信号量或事件
}

void User_Key_Release(void) {
    printf("按键确认松开!\n");
}

void Key_Init(void) {
    Key_GPIO_Init();
    
    g_key.port = KEY_PORT;
    g_key.pin = KEY_PIN;
    g_key.state = KEY_IDLE;
    g_key.debounce_ms = 30;  // 30ms 防抖
    g_key.on_press = User_Key_Press;
    g_key.on_release = User_Key_Release;
    g_key.timer_id = 0xFFFFFFFF;
}

3 中断模式具体实现(EXTI 示例)

复制代码
/*---------- 中断初始化(以 PA0 为例) ----------*/
void Key_EXTI_Init(void) {
    GPIO_InitTypeDef gpio_init = {0};
    EXTI_HandleTypeDef exti_init = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    
    // 1. GPIO 复用为 EXTI
    gpio_init.Pin = KEY_PIN;
    gpio_init.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发(按下瞬间)
    gpio_init.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY_PORT, &gpio_init);
    
    // 2. 配置中断优先级
    HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

/*---------- 中断服务函数(在中断向量表中调用) ----------*/
void EXTI0_IRQHandler(void) {
    // 清除中断标志(必须!)
    if (__HAL_GPIO_EXTI_GET_IT(KEY_PIN) != RESET) {
        __HAL_GPIO_EXTI_CLEAR_IT(KEY_PIN);
    }
    
    // 调用按键扫描(仅做状态切换和启动定时器,无耗时操作)
    Key_Scan();
}

/*---------- 回调函数(在中断中调用,需快速处理) ----------*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == KEY_PIN) {
        // 关键:在中断中仅触发检测,不直接运行定时器创建(耗时)
        Key_Scan();  // 该函数内会启动软件定时器,将后续防抖交给定时器
    }
}

#include "los_hwi.h"

/*---------- 中断初始化(LiteOS 方式) ----------*/
void Key_Interrupt_Init(void) {
    UINT32 ret;
    HWI_PRIOR_T hwiPrio = 3;
    HWI_MODE_T mode = 0;
    HWI_ARG_T arg = 0;
    
    // 1. 配置 GPIO 为中断输入模式(下降沿触发)
    GPIO_InitTypeDef gpio_init = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    
    gpio_init.Pin = KEY_PIN;
    gpio_init.Mode = GPIO_MODE_IT_FALLING;   // 下降沿触发
    gpio_init.Pull = GPIO_PULLUP;
    gpio_init.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(KEY_PORT, &gpio_init);
    
    // 2. 注册中断处理函数到 LiteOS 内核
    //    参数:中断号、优先级、模式、回调函数、参数
    ret = LOS_HwiCreate(KEY_IRQn, hwiPrio, mode, 
                        (HWI_PROC_FUNC)EXTI0_IRQHandler, arg);
    if (ret != LOS_OK) {
        printf("Key interrupt create failed! ret=%u\n", ret);
        return;
    }
    
    // 3. 使能中断(LiteOS 已自动使能,但可显式调用)
    // LOS_HwiEnable(KEY_IRQn);
    
    printf("Key interrupt registered successfully!\n");
}