按键消抖还在 delay?难怪你按一下,STM32 像抽风一样连触发
2026/6/5 9:51:16 网站建设 项目流程

你是不是也遇到过这种情况?

明明只按了一下按键,LED 却闪了好几次。

菜单本来只想往下走一格,结果一下跳了两三格。

更烦的是,项目里想做“短按切换模式,长按进入设置”,结果短按也触发,长按也触发,程序像抽风一样。

很多初学者第一反应就是:

那我 delay 一下不就好了?

比如这样写:

if(KEY==0){delay_ms(20);if(KEY==0){LED=!LED;}}

这段代码看起来没问题。

点灯实验里也确实能用。

但我想说一句:按键消抖不是简单 delay 一下。


01 按键为什么会抖?

机械按键不是理想开关。

你手指按下去的一瞬间,触点不是立刻稳定接通,而是会在很短时间内来回抖几下。

也就是说,单片机看到的电平可能是这样的:

1 1 0 1 0 0 1 0 0 0

你以为自己只按了一次。

但单片机可能以为你按了好几次。

尤其是 STM32 这种主频很高的芯片,几毫秒时间里,它已经循环检测很多次 GPIO 了。

所以,按键乱触发,不一定是板子坏了。

很多时候,是代码把“电平抖动”当成了“有效按键”。


02 delay 消抖最大的问题是什么?

delay 消抖不是完全不能用。

如果你只是写个点灯实验,问题不大。

但一到真实项目里,它就容易出事。

比如你的程序还要做这些事情:

串口接收数据 OLED 刷新显示 ADC 采样 PWM 输出 FreeRTOS 任务调度 通信协议解析

这时候你按一下按键,程序直接卡在 delay_ms(20)。

如果用户连续按几次,系统就会一卡一卡。

串口数据可能丢。

屏幕刷新可能变慢。

通信响应可能超时。

所以 delay 消抖的问题不是“不准”,而是太粗暴。

项目里不怕代码简单,就怕代码简单到把系统堵住。


03 更靠谱的办法:定时扫描

比较推荐的做法是:

每隔固定时间扫描一次按键,比如 10ms 扫描一次。

不是一读到按下就马上处理。

而是连续几次都读到按下,才确认按键真的按下了。

比如:

#defineKEY_DOWN_LEVEL0#defineKEY_SCAN_TIME10#defineKEY_FILTER_CNT3uint8_tkey_cnt=0;uint8_tkey_state=1;uint8_tkey_event=0;voidKey_Scan(void){if(KEY_READ()==KEY_DOWN_LEVEL){if(key_cnt<KEY_FILTER_CNT){key_cnt++;}if(key_cnt>=KEY_FILTER_CNT&&key_state==1){key_state=0;key_event=1;// 按键按下事件}}else{key_cnt=0;key_state=1;}}

这段代码的意思很简单。

如果连续 3 次都检测到按下,才认为按键真的按下。

如果 10ms 扫描一次,连续 3 次就是大约 30ms。

这样大部分机械抖动就被过滤掉了。

关键是:这个过程不需要 delay 卡住程序。

你可以把Key_Scan()放到定时器中断里,也可以放到 10ms 周期任务里。

主循环只处理事件:

if(key_event){key_event=0;LED=!LED;}

这样写,程序就清爽很多。


04 短按和长按,别写乱了

很多人短按、长按分不清,是因为逻辑写错了。

最常见的错误是:

按下瞬间就触发短按,按久了再触发长按。

这样当然会冲突。

因为刚按下的时候,你根本不知道用户是想短按,还是想长按。

正确思路应该是:

按下后开始计时 超过一定时间,触发长按 如果没超过长按时间就松开,触发短按

比如:

#defineLONG_PRESS_TIME80// 80 * 10ms = 800msuint16_tpress_time=0;uint8_tshort_event=0;uint8_tlong_event=0;uint8_tlong_flag=0;voidKey_Process(void){if(key_state==0)// 已确认按下{if(press_time<LONG_PRESS_TIME){press_time++;}elseif(long_flag==0){long_flag=1;long_event=1;// 长按事件}}else{if(press_time>0&&press_time<LONG_PRESS_TIME){short_event=1;// 松开时判断短按}press_time=0;long_flag=0;}}

这里有个重点:

短按一般在松开时判断,不是在按下时判断。

这句话很重要。

很多项目里按键逻辑乱,就是因为短按触发太早。


05 项目里建议这样分层

不要在业务代码里到处读 GPIO。

比如菜单程序里,不要这样写:

if(KEY1==0){menu++;}

这样后面一旦要加消抖、长按、组合键,代码会越来越乱。

更好的方式是:

底层:负责读取 GPIO 按键模块:负责消抖、短按、长按 业务逻辑:只处理按键事件

比如主程序只关心:

if(short_event){short_event=0;menu++;}if(long_event){long_event=0;enter_setting_mode();}

这样代码就舒服多了。

菜单不用关心按键有没有抖。

业务不用关心低电平有效还是高电平有效。

以后换按键、改消抖时间、加长按重复触发,也不会把整个工程改乱。


06 最后记住一句话

按键消抖看起来是小问题。

但它考验的是嵌入式编程思维。

GPIO 读到的是电平。

用户操作才是事件。

不要一看到低电平就执行。

不要动不动就 delay 堵住程序。

真正适合项目的按键写法,应该是:

定时扫描 + 消抖计数 + 状态判断 + 事件输出

下次你的 STM32 按键又出现“按一次触发多次”“短按长按分不清”的问题,别急着怀疑硬件。

先看看代码是不是还停留在:

按下->delay->执行

很多按键翻车现场,问题就出在这里。

如果这篇文章对你有帮助,建议收藏起来,转发给正在学 STM32 的朋友。也欢迎留言说说,你项目里遇到过哪些离谱的按键问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询