前言

设备只有一个按键,需要稳定可靠的多种按键事件检测,实现更多功能。

一、设计思路

采用状态机消抖检测按键来组合创建一个非常健壮且可靠的按键处理模块。

1. 分层结构

  • 底层 (GPIO + 消抖):使用 read_key_debounced() 提供稳定、无抖动的物理电平信号。
  • 中层 (状态机):基于稳定的电平信号,运行状态机来识别高级事件(单击、双击、长按等)。
  • 顶层 (应用):获取中层产生的事件,执行具体任务。

2. 数据流

GPIO Pin -> read_key_debounced() -> 稳定状态 -> Key_Scan() -> KeyEvent -> main()

3. 时序

  • 定时器中断(如5ms)调用 read_key_debounced()。
  • 主循环调用 Key_Scan() 处理状态机。

二、代码

  1. 按键检测头文件:key.h
#ifndef __KEY_H
#define __KEY_H
#include "stdint.h"
// 按键物理状态(由read_key_debounced提供,稳定状态)
#define KEY_STATE_PRESSED      0
#define KEY_STATE_RELEASED     1

// 按键检测结果(最终返回给用户的事件)
typedef enum {
    KEY_EVENT_NONE = 0,				//无操作
    KEY_EVENT_SINGLE_CLICK,			//单击
    KEY_EVENT_DOUBLE_CLICK,			//双击
    KEY_EVENT_TRIPLE_CLICK,			//三击
    KEY_EVENT_LONG_PRESS,			//长按
} KeyEvent_TypeDef;

// 初始化按键GPIO和状态机
void Key_Init(void);
// 按键扫描函数,需周期调用 (如5ms)
KeyEvent_TypeDef Key_Scan(void);

#endif
  1. 按键检测模块:key.c
#include "key.h"
/******************** 用户配置区域 ********************/
// 1. 时间参数 (单位: 毫秒 ms)
#define DEBOUNCE_TIME_MS       20   // 消抖时间
#define SHORT_PRESS_TIME_MS    1000 // 短按最长时间
#define LONG_PRESS_TIME_MS     2000 // 长按时间
#define CLICK_INTERVAL_MS      400  // 连击最大间隔
/****************************************************/

// 声明外部函数:获取经过消抖后的稳定按键状态
// 这个函数需要在别处定义(例如在定时器中断中更新)
extern uint8_t read_key_debounced(void);

// 按键状态机状态
typedef enum {
    KEY_STATE_IDLE = 0,				//空闲状态
    KEY_STATE_PRESS_DETECTED,		//按键按下状态
    KEY_STATE_WAIT_RELEASE,			//等待释放状态
    KEY_STATE_WAIT_CLICK,			//释放后等待连击状态
} KeyState_TypeDef;

// 按键控制块结构体
typedef struct {
    KeyState_TypeDef State;
    uint32_t PressStartTime; // 按下开始的时间戳
    uint32_t ReleaseTime;    // 释放开始的时间戳
    uint8_t ClickCount;		 // 点击次数
} Key_CB_TypeDef;

static Key_CB_TypeDef s_key = {0};
static uint32_t s_current_tick = 0; // 当前时间,需要在外部更新(如SysTick)

// 提供给外部调用的函数,用于更新当前时间戳
// 例如,可以在SysTick_Handler中调用 Key_TickInc()
void Key_TickInc(void) {
    s_current_tick++;
}

KeyEvent_TypeDef Key_Scan(void)
{
    static KeyEvent_TypeDef event = KEY_EVENT_NONE;
    uint8_t current_stable_state = read_key_debounced(); // 核心变化:使用消抖后的状态

    event = KEY_EVENT_NONE;

    switch (s_key.State)
    {
        case KEY_STATE_IDLE:
            if (current_stable_state == KEY_STATE_PRESSED) {
                // 检测到稳定的按下信号,进入按下状态
                s_key.State = KEY_STATE_PRESS_DETECTED;
                s_key.PressStartTime = s_current_tick; // 记录按下开始时间
            }
            break;

        case KEY_STATE_PRESS_DETECTED:
            // 持续监测按下状态
            if (current_stable_state == KEY_STATE_PRESSED) {
                // 检查是否达到长按时间
                if ((s_current_tick - s_key.PressStartTime) >= LONG_PRESS_TIME_MS) {
                    event = KEY_EVENT_LONG_PRESS;
                    s_key.ClickCount = 0;
                    s_key.State = KEY_STATE_IDLE;
                }
            } else {
                // 按键已稳定释放,进入等待连击状态
                s_key.State = KEY_STATE_WAIT_CLICK;
                s_key.ReleaseTime = s_current_tick;
                s_key.ClickCount++; // 增加单击计数
            }
            break;

        case KEY_STATE_WAIT_CLICK:
            // 在连击间隔内,如果再次检测到稳定按下
            if (current_stable_state == KEY_STATE_PRESSED) {
                s_key.State = KEY_STATE_PRESS_DETECTED;
                s_key.PressStartTime = s_current_tick;
            }
            // 如果超时,则根据计数决定触发哪种单击事件
            if ((s_current_tick - s_key.ReleaseTime) >= CLICK_INTERVAL_MS) {
                switch (s_key.ClickCount) {
                    case 1: event = KEY_EVENT_SINGLE_CLICK; break;
                    case 2: event = KEY_EVENT_DOUBLE_CLICK; break;
                    case 3: event = KEY_EVENT_TRIPLE_CLICK; break;
                    default: event = KEY_EVENT_NONE; break;
                }
                // 处理完事件后,重置状态机
                s_key.ClickCount = 0;
                s_key.State = KEY_STATE_IDLE;
            }
            break;

        default:
            s_key.State = KEY_STATE_IDLE;
            break;
    }

    return event;
}
  1. 按键消抖模块:key_debounce.c
#include "key.h"
#include "stdint.h"

// 提供给Key_Scan()使用的消抖后状态
uint8_t read_key_debounced(void) {
	// 消抖模块的静态变量
	static uint32_t s_last_check_tick = 0;
	static uint8_t s_stable_state = KEY_STATE_RELEASED; // 默认释放
	static uint8_t s_last_raw_state = KEY_STATE_RELEASED;

    uint8_t current_raw_state = HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN);	// 读取GPIO原始状态的函数(需要根据你的硬件实现)
    uint32_t current_tick = HAL_GetTick(); // 使用Key模块的时间

    // 状态变化检测
    if (current_raw_state != s_last_raw_state) {
        s_last_check_tick = current_tick;		// 如果状态发生变化,重置计时器并更新上次状态
        s_last_raw_state = current_raw_state;
        return s_stable_state;					//返回稳定状态(不是新状态)
    }

    // 状态稳定时间检测
    if (current_tick - s_last_check_tick > DEBOUNCE_TIME_MS) {
    	// 如果状态稳定时间超过消抖时间
        if (current_raw_state != s_stable_state) {
            s_stable_state = current_raw_state;		// 如果当前状态与稳定状态不同,更新稳定状态
        }
    }

    s_last_raw_state = current_raw_state;		// 更新上次原始状态并返回当前稳定状态
    return s_stable_state;
}
  1. main.c
#include "key.h"

int main(void) {
    // 系统初始化
    SystemInit();
    Key_Init(); // 初始化按键GPIO等

    while(1) {
        // 获取按键事件(非阻塞)
        KeyEvent_TypeDef key_event = Key_Scan();

        // 处理按键事件
        switch(key_event) {
            case KEY_EVENT_SINGLE_CLICK:
                LED_Toggle();
                break;
            case KEY_EVENT_DOUBLE_CLICK:
                Change_Mode();
                break;
            case KEY_EVENT_LONG_PRESS:
                Enter_Sleep_Mode();
                break;
            default:
                break;
        }

        // 执行其他任务
        Do_Other_Tasks();
    }
}

// SysTick中断服务函数(1ms中断一次)
void SysTick_Handler(void) {
    Key_TickInc(); // 更新时间戳
    // read_key_debounced() 的计时依赖于此 tick
}

三、结语

以上为代码框架,可以检测按键的单击双击三击和长按等按键事件。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐