STM32之按键长短按设计【一键多用】

在嵌入式开发中,按键的短按长按功能是提升交互效率的关键。本文将详解如何在STM32中通过状态机和时间戳实现按键的“一键多用”,并提供完整的代码和设计思路。


一、设计原理

1. 核心需求

  • 消抖处理:消除机械按键的抖动(20ms)。
  • 长短按区分:按下时间 < 1s 为短按,≥1s 为长按。
  • 事件触发:通过标志位分离按键检测与事件处理。

2. 状态机设计

定义三种按键状态:

typedef enum {
    BUTTON_STATE_RELEASED,     // 松开状态
    BUTTON_STATE_PRESSED,      // 按下状态
    BUTTON_STATE_DEBOUNCING    // 消抖状态
} ButtonState;

二、代码实现

  1. 按键结构体与宏定义(key.h)
#define DEBOUNCE_TIME 20      // 消抖时间20ms
#define LONG_PRESS_TIME 1000  // 长按阈值1000ms

typedef struct {
    ButtonState state;         // 当前状态
    uint32_t buttonTime;       // 状态切换时间戳
    uint32_t presstime;        // 按下时刻时间戳
    uint8_t pressedFlag;       // 短按标志
    uint8_t longPressFlag;     // 长按标志
    GPIO_TypeDef* gpioPort;    // GPIO端口(如GPIOA)
    uint16_t gpioPin;          // GPIO引脚(如GPIO_PIN_0)
} Button;
  1. 中断回调函数(key.c)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == key1.gpioPin) {
        if (key1.state == BUTTON_STATE_RELEASED) {
            // 按下:进入消抖状态,记录时间戳
            key1.state = BUTTON_STATE_DEBOUNCING;
            key1.buttonTime = HAL_GetTick(); //记录按下时刻,用于消抖计时
            key1.presstime = key1.buttonTime;  // 记录按下时刻,用于长按计时
        } else if (key1.state == BUTTON_STATE_PRESSED) {
            // 松开:进入消抖状态,记录时间戳
            key1.state = BUTTON_STATE_DEBOUNCING;
            key1.buttonTime = HAL_GetTick(); //记录松开时刻,用于消抖计时
        }
    }
    // 其他按键类似...
}
  1. 按键处理函数(核心逻辑)
void Button_Process(Button* button) {
    // 1. 消抖处理
    if (button->state == BUTTON_STATE_DEBOUNCING) {
        if (HAL_GetTick() - button->buttonTime >= DEBOUNCE_TIME) {
            // 读取引脚电平确认状态
            if (HAL_GPIO_ReadPin(button->gpioPort, button->gpioPin) == GPIO_PIN_RESET) {
                button->state = BUTTON_STATE_PRESSED;  // 稳定按下
            } else {
                button->state = BUTTON_STATE_RELEASED; // 稳定松开
                // 计算按下时长,设置标志位
                if (HAL_GetTick() - button->presstime < LONG_PRESS_TIME) {
                    button->pressedFlag = 1;    // 短按
                } else {
                    button->longPressFlag = 1;  // 长按
                }
            }
        }
    }

    // 2. 处理短按事件
    if (button->pressedFlag) {
        button->pressedFlag = 0;
        printf("KEY Short Press!\n");
    }

    // 3. 处理长按事件
    if (button->longPressFlag) {
        button->longPressFlag = 0;
        printf("KEY Long Press!\n");
    }
}

三、流程图

在这里插入图片描述

四、使用示例

  1. 初始化按键
Button key1 = {BUTTON_STATE_RELEASED, 0, 0, 0, 0, KEY_GPIO_Port, KEY_Pin};
  1. 主循环调用
while (1) {
    Button_Process(&key1);  // 处理按键
    HAL_Delay(10);          // 适当延时
}

五、优化与扩展

  1. 多按键支持
    定义多个Button实例,独立管理每个按键。
  2. 回调函数
    可将事件处理替换为函数指针,增强灵活性。
  3. 参数可调
    通过宏定义修改DEBOUNCE_TIME和LONG_PRESS_TIME。
    通过本文的设计,STM32的按键可实现精准的长短按检测,代码清晰且易于扩展。实际应用中,可根据需求调整阈值和事件处理逻辑。

完整程序

key.h文件:

#ifndef __KEY_H__
#define __KEY_H__

#include "main.h"

#define BUTTON_PIN GPIO_PIN_0
#define BUTTON_GPIO_PORT GPIOA
#define DEBOUNCE_TIME 20  // 消抖时间,单位ms
#define LONG_PRESS_TIME 1000  // 长按时间阈值,单位ms


typedef enum {
    BUTTON_STATE_RELEASED,
    BUTTON_STATE_PRESSED,
    BUTTON_STATE_DEBOUNCING
} ButtonState;

typedef struct {
    ButtonState state;          // 按键状态
    uint32_t buttonTime;        // 按键按下或松开的时间戳
    uint32_t presstime;         // 按键按下的时间戳
    uint8_t pressedFlag;        // 单次触发标志
    uint8_t longPressFlag;      // 长按触发标志
    GPIO_TypeDef* gpioPort;     // 按键 GPIO 端口
    uint16_t gpioPin;           // 按键 GPIO 引脚
} Button;

extern Button key1, key2;

void Button_Process(Button* button);

#endif

key.c文件:

#include "key.h"
#include "stdio.h"

ButtonState buttonState = BUTTON_STATE_RELEASED;
uint32_t buttonTime = 0, presstime = 0;
uint8_t buttonPressedFlag = 0;
uint8_t buttonLongPressFlag = 0;

Button key1 = {BUTTON_STATE_RELEASED, 0, 0, 0, 0, KEY_GPIO_Port, KEY_Pin};
Button key2 = {BUTTON_STATE_RELEASED, 0, 0, 0, 0, KEY0_GPIO_Port, KEY0_Pin};

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 
{
    if (GPIO_Pin == key1.gpioPin) 
	{
        if (key1.state == BUTTON_STATE_RELEASED) 
		{
            // 按键按下,进入消抖状态
            key1.state = BUTTON_STATE_DEBOUNCING;
            key1.buttonTime = HAL_GetTick();
            key1.presstime = key1.buttonTime;
        } 
		else if (key1.state == BUTTON_STATE_PRESSED) 
		{
            // 按键松开,进入消抖状态
            key1.state = BUTTON_STATE_DEBOUNCING;
            key1.buttonTime = HAL_GetTick();
        }
    } 
	else if (GPIO_Pin == key2.gpioPin) 
	{
        if (key2.state == BUTTON_STATE_RELEASED) 
		{
            // 按键按下,进入消抖状态
            key2.state = BUTTON_STATE_DEBOUNCING;
            key2.buttonTime = HAL_GetTick();
            key2.presstime = key2.buttonTime;
        } 
		else if (key2.state == BUTTON_STATE_PRESSED) 
		{
            // 按键松开,进入消抖状态
            key2.state = BUTTON_STATE_DEBOUNCING;
            key2.buttonTime = HAL_GetTick();
        }
    }
}

void Button_Process(Button* button) 
{
    if (button->state == BUTTON_STATE_DEBOUNCING) 
	{
        // 消抖处理
        if (HAL_GetTick() - button->buttonTime >= DEBOUNCE_TIME) 
		{
            if (HAL_GPIO_ReadPin(button->gpioPort, button->gpioPin) == GPIO_PIN_RESET) 
			{
                // 按键稳定按下
                button->state = BUTTON_STATE_PRESSED;
            } 
			else 
			{
                // 按键稳定松开
                button->state = BUTTON_STATE_RELEASED;
                // 判断触发类型
                if (HAL_GetTick() - button->presstime < LONG_PRESS_TIME) 
				{
                    button->pressedFlag = 1;  // 单次触发
                } 
				else 
				{
                    button->longPressFlag = 1;  // 长按触发
                }
            }
        }
    }

    if (button->pressedFlag) 
	{
        button->pressedFlag = 0;
        // 处理单次触发事件
        if (button->gpioPin == KEY_Pin) 
		{
            printf("KEY Pressed!\n");
        } 
		else if (button->gpioPin == KEY0_Pin) 
		{
            printf("KEY0 Pressed!\n");
        }
    }

    if (button->longPressFlag) 
	{
        button->longPressFlag = 0;
        // 处理长按触发事件
        if (button->gpioPin == KEY_Pin) 
		{
            printf("KEY Long Pressed!\n");
        } 
		else if (button->gpioPin == KEY0_Pin) 
		{
            printf("KEY0 Long Pressed!\n");
        }
    }
}

Logo

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

更多推荐