You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32 Nucleo开发中C语言Delay函数的替代方案(按键长按检测)

替代Delay函数实现STM32 Nucleo按键长按检测的方案

在STM32开发中,Delay函数会阻塞主程序,导致其他任务无法执行,对于按键长按这类需要同时处理多逻辑的场景,以下几种非阻塞方案更实用:

1. 定时器中断+状态机方案

通过定时器定期扫描按键状态,配合状态机跟踪按键的空闲、按下、长按全生命周期,完全非阻塞,适合多按键或复杂按键逻辑场景。

实现步骤:

  • 配置定时器(如10ms触发一次中断)作为按键扫描的时间基准。
  • 定义按键状态枚举,清晰区分不同阶段。
  • 在定时器中断回调中读取引脚状态,更新状态机并计数。
  • 主循环根据状态机输出执行长按动作。

代码示例(HAL库):

#include "stm32f4xx_hal.h"

// 按键引脚定义
#define KEY_PIN GPIO_PIN_0
#define KEY_GPIO_PORT GPIOA

// 按键状态枚举
typedef enum {
    KEY_STATE_IDLE,      // 空闲状态
    KEY_STATE_PRESSED,   // 已按下(待确认稳定)
    KEY_STATE_LONG_PRESS // 长按触发状态
} KeyState;

KeyState keyState = KEY_STATE_IDLE;
uint32_t pressCounter = 0;
#define LONG_PRESS_THRESHOLD 50 // 10ms中断×50=500ms长按阈值

// 定时器中断回调(假设使用TIM2,需提前配置)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        GPIO_PinState keyPinState = HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_PIN);
        
        switch (keyState) {
            case KEY_STATE_IDLE:
                if (keyPinState == GPIO_PIN_RESET) { // 按键按下(下拉输入,按下为低)
                    keyState = KEY_STATE_PRESSED;
                    pressCounter = 0;
                }
                break;
            case KEY_STATE_PRESSED:
                if (keyPinState == GPIO_PIN_RESET) {
                    pressCounter++;
                    if (pressCounter >= LONG_PRESS_THRESHOLD) {
                        keyState = KEY_STATE_LONG_PRESS;
                    }
                } else {
                    keyState = KEY_STATE_IDLE; // 短按松开,回到空闲
                }
                break;
            case KEY_STATE_LONG_PRESS:
                if (keyPinState == GPIO_PIN_SET) {
                    keyState = KEY_STATE_IDLE; // 长按后松开,重置状态
                }
                break;
        }
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init(); // 初始化定时器并开启中断
    
    HAL_TIM_Base_Start_IT(&htim2);
    
    while (1) {
        if (keyState == KEY_STATE_LONG_PRESS) {
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); // 执行长按动作(点亮LED)
            keyState = KEY_STATE_PRESSED; // 避免重复触发
        } else {
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
        }
        
        // 主循环其他任务:串口通信、传感器读取等
        // ...
    }
}

2. SysTick毫秒计时方案

利用HAL库自带的HAL_GetTick()(基于SysTick定时器,返回系统运行毫秒数),在主循环中轮询按键状态并计算时间差,无需额外配置定时器,适合简单场景。

代码示例:

#include "stm32f4xx_hal.h"

#define KEY_PIN GPIO_PIN_0
#define KEY_GPIO_PORT GPIOA
#define LONG_PRESS_DURATION 500 // 长按阈值500ms

uint32_t pressStartTime = 0;
uint8_t isKeyPressed = 0;

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    
    while (1) {
        GPIO_PinState keyPinState = HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_PIN);
        
        if (keyPinState == GPIO_PIN_RESET && !isKeyPressed) {
            pressStartTime = HAL_GetTick(); // 记录按键按下起始时间
            isKeyPressed = 1;
        } else if (keyPinState == GPIO_PIN_RESET && isKeyPressed) {
            if (HAL_GetTick() - pressStartTime >= LONG_PRESS_DURATION) {
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); // 执行长按动作
                while (HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_PIN) == GPIO_PIN_RESET); // 等待按键松开
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
                isKeyPressed = 0;
            }
        } else if (keyPinState == GPIO_PIN_SET && isKeyPressed) {
            isKeyPressed = 0; // 短按松开,重置状态
        }
        
        // 主循环其他任务
        // ...
    }
}

3. 外部中断+软件计时方案

通过外部中断捕捉按键按下瞬间,记录起始时间,主循环中检查按键是否持续按下并计算时间差,响应速度快,适合对实时性要求高的场景。

代码示例:

#include "stm32f4xx_hal.h"

#define KEY_PIN GPIO_PIN_0
#define KEY_GPIO_PORT GPIOA
#define LONG_PRESS_DURATION 500

uint32_t pressStartTime = 0;
uint8_t keyDownFlag = 0;

// 外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == KEY_PIN) {
        HAL_Delay(10); // 短延时消抖(仅用于确认按键状态,不阻塞主逻辑)
        if (HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_PIN) == GPIO_PIN_RESET) {
            pressStartTime = HAL_GetTick();
            keyDownFlag = 1;
        }
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init(); // 配置按键引脚为下降沿触发外部中断
    
    while (1) {
        if (keyDownFlag) {
            if (HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_PIN) == GPIO_PIN_RESET) {
                if (HAL_GetTick() - pressStartTime >= LONG_PRESS_DURATION) {
                    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); // 执行长按动作
                    keyDownFlag = 0;
                }
            } else {
                keyDownFlag = 0; // 未达长按就松开,重置状态
                HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
            }
        }
        
        // 主循环其他任务
        // ...
    }
}

注意事项:

  • 所有方案需处理按键消抖,可通过短延时或多次采样消除机械按键的抖动干扰。
  • 多按键场景建议用结构体封装每个按键的状态、计时变量,避免全局变量混乱。
  • 定时器中断方案需合理设置优先级,避免影响系统关键中断。

内容的提问来源于stack exchange,提问作者Classy Fied

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 08:52:36