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
相关产品推荐
相关产品推荐

