按钮(按键)抖动是单片机开发中常见的硬件问题,本质是机械触点接触瞬间的物理弹跳导致的电信号不稳定。消除抖动(防抖)是确保按键状态检测准确的关键,下面从原理到实现详细讲解。

一、按钮抖动的原理:为什么需要防抖?

机械按钮的触点由金属弹片组成,按下或松开瞬间,弹片会因弹性发生多次快速接触与分离(持续时间通常为5~20ms),表现为单片机检测到的电平信号在高/低电平之间快速跳变:

  • 未按下时:按钮断开,通常通过上拉电阻(内部或外部)使输入引脚为高电平(1);
  • 按下瞬间:触点弹跳,电平在1和0之间快速切换(抖动阶段);
  • 稳定按下:触点完全接触,电平稳定为低电平(0);
  • 松开瞬间:同样出现弹跳,电平从0跳变回1的过程中抖动。

若不处理,单片机会误将抖动中的“多次跳变”识别为“多次按键操作”(比如一次按下被当成多次触发),导致逻辑错误。

二、防抖的核心思路:“等待稳定”或“过滤毛刺”

防抖的本质是忽略抖动阶段的不稳定信号,只识别稳定状态,常用两种方案:

1. 硬件防抖:通过电路消除抖动(适合批量生产)

通过RC滤波电路或专用防抖芯片,从硬件层面过滤抖动信号,优点是不占用CPU资源,缺点是增加硬件成本。

(1)RC滤波电路(最常用)
  • 原理:利用电容充放电特性平滑抖动信号,电阻限制电流。
  • 电路:按钮两端并联一个100nF电容(C),串联一个1kΩ电阻(R)到地,单片机引脚接在电阻与电容之间。
    • 按下时,电容快速放电,电平稳定为低;
    • 抖动时,电容充放电速度跟不上抖动频率,电平保持稳定。
(2)专用防抖芯片

如74HC14(施密特触发器),通过滞回特性将抖动信号整形为稳定的高低电平,适合对稳定性要求高的场景。

2. 软件防抖:通过程序逻辑消除抖动(灵活低成本)

硬件防抖有额外成本,实际开发中更常用软件防抖,核心是检测到电平变化后,等待一段时间(大于抖动时间,通常10~20ms),再确认按键状态

软件防抖的关键逻辑:
  1. 检测到按键状态变化(如从高到低);
  2. 启动定时器或延时,等待抖动结束(如10ms);
  3. 再次检测按键状态,若与第一次变化一致,则确认有效(如确实按下)。

三、软件防抖的经典实现方法(附代码)

以“按下按键时电平从高变低”为例(上拉输入模式),介绍3种常用软件防抖方法:

方法1:延时函数防抖(简单直观,适合裸机单任务)

通过delay()函数等待抖动结束,再读取状态,适合对实时性要求不高的场景(如流水灯控制)。

原理:
  • 检测到按键引脚为低电平时(可能是抖动),延时10ms;
  • 延时后再次检测,若仍为低电平,则确认按键按下。
示例代码(STM32 HAL库):
#include "stm32f1xx_hal.h"#define KEY_PIN GPIO_PIN_0
#define KEY_PORT GPIOA// 按键扫描函数(返回1表示按下,0表示未按下)
uint8_t Key_Scan(void) {static uint8_t key_state = 0; // 按键状态(0:未按下,1:按下)// 第一次检测到按键按下(低电平)if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) {HAL_Delay(10); // 延时10ms,等待抖动结束// 再次检测,确认按下if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) {key_state = 1;return 1; // 按键有效按下}} else {// 按键松开,状态复位key_state = 0;}return 0;
}// 主函数中调用
int main(void) {HAL_Init();// 初始化按键引脚为上拉输入GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = KEY_PIN;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP; // 内部上拉,未按下时为高电平HAL_GPIO_Init(KEY_PORT, &GPIO_InitStruct);while (1) {if (Key_Scan() == 1) {// 执行按键操作(如点亮LED)HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5); // 假设LED接PB5}}
}
缺点:

HAL_Delay(10)会阻塞CPU,期间无法执行其他任务,不适合多任务或实时性要求高的场景(如电机控制)。

方法2:定时器中断防抖(无阻塞,推荐)

利用定时器定时触发中断(如1ms一次),在中断中记录按键状态变化的时间,通过时间差判断是否稳定,避免阻塞主程序。

原理:
  • 定时器每1ms进入一次中断,更新“按键按下的持续时间”;
  • 当持续时间超过10ms时,确认按键稳定按下。
示例代码(STM32 HAL库):
#include "stm32f1xx_hal.h"#define KEY_PIN GPIO_PIN_0
#define KEY_PORT GPIOAuint8_t key_pressed = 0; // 按键有效按下标志(1:有效)
uint16_t key_press_time = 0; // 按键按下的持续时间(ms)// 定时器中断回调函数(1ms触发一次)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {if (htim->Instance == TIM2) { // 假设用TIM2// 检测按键是否按下(低电平)if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) {key_press_time++; // 持续按下,时间累加// 持续时间超过10ms,确认有效if (key_press_time >= 10) {key_pressed = 1;key_press_time = 10; // 防止溢出}} else {// 按键松开,复位key_press_time = 0;key_pressed = 0;}}
}// 主函数中初始化定时器和按键
int main(void) {HAL_Init();// 初始化按键引脚(上拉输入)GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = KEY_PIN;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP;HAL_GPIO_Init(KEY_PORT, &GPIO_InitStruct);// 初始化定时器(1ms中断)TIM_HandleTypeDef htim2;htim2.Instance = TIM2;htim2.Init.Prescaler = 7200 - 1; // 72MHz / 7200 = 10kHzhtim2.Init.CounterMode = TIM_COUNTERMODE_UP;htim2.Init.Period = 10 - 1; // 10kHz / 10 = 1kHz → 1msHAL_TIM_Base_Init(&htim2);HAL_TIM_Base_Start_IT(&htim2); // 启动定时器中断while (1) {if (key_pressed == 1) {// 执行按键操作HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);key_pressed = 0; // 清除标志(避免重复触发)}// 主循环可执行其他任务(无阻塞)}
}
优点:
  • 定时器中断独立运行,不阻塞主程序,适合多任务场景;
  • 时间控制精确,可灵活调整防抖阈值(如10ms/20ms)。

方法3:状态机防抖(处理复杂按键逻辑,如长按/短按)

通过状态机管理按键的“空闲→按下→稳定→松开”等状态,不仅能防抖,还能识别长按、短按、双击等复杂操作。

状态定义:
  • KEY_IDLE:空闲状态(未按下);
  • KEY_PRESS_DETECT:检测到按下(可能抖动);
  • KEY_PRESSED:稳定按下状态;
  • KEY_RELEASE_DETECT:检测到松开(可能抖动)。
示例代码(通用C语言,可移植):
#include <stdint.h>// 按键状态枚举
typedef enum {KEY_IDLE,KEY_PRESS_DETECT,KEY_PRESSED,KEY_RELEASE_DETECT
} KeyState;#define KEY_PIN 0 // 假设按键接在GPIO0
#define READ_KEY() (gpio_read(KEY_PIN)) // 读取按键电平(0:按下,1:未按下)
#define DEBOUNCE_TIME 10 // 防抖时间(ms)KeyState key_state = KEY_IDLE;
uint16_t key_timer = 0; // 状态切换计时器
uint8_t key_short_press = 0; // 短按标志
uint8_t key_long_press = 0;  // 长按标志(如200ms)// 每1ms调用一次,更新按键状态
void Key_Process(void) {switch (key_state) {case KEY_IDLE:if (READ_KEY() == 0) { // 检测到按下key_state = KEY_PRESS_DETECT;key_timer = 0;}break;case KEY_PRESS_DETECT:key_timer++;if (key_timer >= DEBOUNCE_TIME) { // 防抖时间到if (READ_KEY() == 0) { // 确认按下key_state = KEY_PRESSED;key_timer = 0; // 重置计时器,用于检测长按} else {key_state = KEY_IDLE; // 抖动,回到空闲}}break;case KEY_PRESSED:key_timer++;if (READ_KEY() == 1) { // 检测到松开key_state = KEY_RELEASE_DETECT;if (key_timer < 200) { // 短按(<200ms)key_short_press = 1;}key_timer = 0;} else if (key_timer >= 200) { // 长按(≥200ms)key_long_press = 1;key_state = KEY_IDLE; // 长按后直接回到空闲}break;case KEY_RELEASE_DETECT:key_timer++;if (key_timer >= DEBOUNCE_TIME) { // 防抖时间到key_state = KEY_IDLE; // 确认松开,回到空闲}break;}
}// 主函数中使用
int main(void) {while (1) {Key_Process(); // 每1ms调用一次(可在定时器中断中调用)if (key_short_press) {key_short_press = 0;// 执行短按操作(如切换模式)}if (key_long_press) {key_long_press = 0;// 执行长按操作(如复位)}}
}
优点:
  • 能区分短按、长按等复杂操作,扩展性强;
  • 无阻塞,适合需要丰富交互的场景(如遥控器、智能设备)。

四、防抖方法的选择建议

  1. 简单场景(如一键控制LED):用延时函数防抖(代码简单);
  2. 多任务/实时性要求高(如同时控制电机和按键):用定时器中断防抖
  3. 复杂交互(需识别长按/双击):用状态机防抖
  4. 批量生产/硬件允许:加RC硬件防抖(减少软件负担)。

总结

按钮防抖的核心是“过滤5~20ms的抖动信号”,硬件方案适合量产,软件方案更灵活。实际开发中,推荐“硬件RC滤波+软件定时器防抖”的组合,兼顾可靠性和灵活性。掌握状态机防抖能应对绝大多数复杂按键场景,是单片机工程师的必备技能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95308.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95308.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/95308.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面经分享--小米Java一面

目录 1.Kafka和RocketMQ的区别 2.反射的作用 3.类加载的具体过程&#xff0c;双亲委派模型的机制 4.TCP的四次挥手 5.多线程的优势 6.死锁产生的原因&#xff0c;怎么解决 7.Java并发的工作原理 8.常用的git命令 9.算法题 1.leetcode 3.无重复字符的最长子串&#xff…

Python在边缘计算与物联网中的创新实践:实时数据处理与设备控制

近年来&#xff0c;Python语言的普及度持续攀升&#xff0c;尤其在人工智能、数据科学等热门领域备受青睐。然而&#xff0c;一个新兴趋势——边缘计算与物联网&#xff08;IoT&#xff09;的结合——正悄然改变技术格局。边缘计算强调在数据源头进行实时处理&#xff0c;减少云…

Spring Cloud Gateway 网关(五)

目录 一 概念引入 二 具体使用 1 首先创建一个网关模块 2 启动类 3 配置类 4 对应方法的修改 5 展示借助81端口进行转发控制 6 断言规则​编辑 三 过滤器 1 将前置的请求参数给过滤掉&#xff0c;降低繁琐程度。 2 默认过滤器 3 全局过滤器 4 自定义过滤器工厂 5…

产品经理操作手册(8)——业务需求文档(BRD)

一、BRD的定义与价值 **业务需求文档(BRD)**是产品开发前期的基础性文档&#xff0c;它将业务诉求转化为结构化的产品需求&#xff0c;是连接业务方与交付团队的桥梁。“BRD不仅是一份文档&#xff0c;而是一个对齐的过程。”BRD核心价值 统一认知&#xff1a;确保各方对业务目…

Excel表格多级下拉选项,如何制作?

之前分享过如何设置下拉选项&#xff0c;但那只是简单的一级下拉菜单&#xff0c;今天再给大家分享多级下拉菜单如何制作。也就是根据前面的下拉选项改变后面的选项。 我们现来复习一级下拉菜单&#xff0c;再接着讲多级下拉菜单 一级下拉选项 首先我们先将表格内容凑填写好…

[Sync_ai_vid] 唇形同步评判器 | 图像与视频处理器 | GPU测试

第4章&#xff1a;SyncNet唇形同步评判器 在前几章中&#xff0c;我们了解了唇形同步推理流程如何协调生成唇形同步视频&#xff0c;以及音频特征提取器(Whisper)如何为LatentSync UNet提供关键音频线索。 UNet利用这些线索巧妙调整唇部动作。但我们如何判断UNet的生成效果&a…

算法:插入排序

插入排序&#xff08;直接插入排序&#xff09; 是一种基于“插入”的排序 思路 它的核心思想是把数组分成两部分&#xff1a;一部分是有序区&#xff0c;另一部分是乱序区也就是待排序区。 每次从未排序部分“取出”一个元素&#xff0c;插入到前半部分合适的位置&#xff0c;…

MCP Go SDK

MCP Go SDK v0.3.0 Open in GitHub Codespaces &#xff08;在 GitHub Codespaces 中打开&#xff09; BREAKING CHANGES &#xff08;重大变更&#xff09; This version contains breaking changes. See the release notes for details PkgGoDev &#xff08;Go 官方包文档入…

面试问题详解十一:Qt中的线程池与 QRunnable

在 Qt 中&#xff0c;多线程的使用是开发高性能 GUI 应用的重要组成部分。为了避免频繁创建和销毁线程带来的资源消耗&#xff0c;Qt 提供了 线程池&#xff08;QThreadPool&#xff09; 和 可运行任务&#xff08;QRunnable&#xff09; 的机制&#xff0c;帮助我们更加高效地…

spring-ai-alibaba-deepresearch 学习(五)——BackgroundInvestigationNode

本篇为spring-ai-alibaba学习系列第三十一篇前面介绍 rewrite_multi_query 节点最后会根据用户上传文件标识 user_upload_file 决定下一节点现在来看一下第二个分支&#xff0c;当 user_upload_file 为 false 时&#xff0c;转入 background_investigator 节点该节点主要是负责…

ESP32S3:开发环境搭建、VSCODE 单步调试、Systemview 分析任务运行情况

目标: 实现点灯工程&#xff0c;并且可以基于 vscode 进行单步调试与 systemview 来分析任务运行情况。 环境搭建 如需在 ESP32-S3 上使用 ESP-IDF&#xff0c;请安装以下软件&#xff1a; 设置 工具链&#xff0c;用于编译 ESP32-S3 代码&#xff1b;编译构建工具 —— CMa…

linux系统学习(6.软件包管理)

目录 一、概述 1.分类 2.命名方式 3.一个软件包的组成 1. 软件包的基本定义 2. 一个软件包通常包含的部分 ① 程序文件 ② 库文件 ③ 配置文件 ④ 数据文件 / 资源文件 ⑤ 文档 / 帮助信息 ⑥ 服务脚本 / 单元文件&#xff08;如果是服务型软件&#xff09; ⑦ 包的…

数据结构青铜到王者第八话---队列(Queue)

目录 一、队列(Queue) 1、概念 2、队列的使用 3、队列模拟实现 4、循环队列 4.1数组下标循环的小技巧&#xff08;1&#xff09;下标最后再往后(offset 小于 array.length): index (index offset) % array.length 4.2如何区分空与满 4.3设计循环队列 二、双端队列 (Deq…

Windows系统之不使用第三方软件查看电脑详细配置信息

MENU使用系统信息工具&#xff08;最详细&#xff09;使用命令行查看命令提示符PowerShell&#xff08;信息更丰富&#xff09;使用DirectX诊断工具&#xff08;查看显卡和声音设备&#xff09;查看设备管理器&#xff08;查看硬件驱动&#xff09;一条命令合集&#xff08;Pow…

K8s学习笔记(一)——

一、k8s是什么一个分布式原来是主要用来管理容器的呀&#xff08;专业点叫“容器编排”&#xff09;&#xff0c;什么是管理&#xff1f;其实就是增删改查等等&#xff0c;简单来理解&#xff0c;k8s就是实现容器增删改查的呗。是开源的&#xff0c;在Linux系统下。就跟创建的s…

Zynq开发实践(FPGA之平台免费IP)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】和c语言平台提供posix api一样&#xff0c;一般fpga厂家也会提供各种各样免费的ip给客户使用。这样&#xff0c;客户就不需要自己去写每一个ip了&am…

nginx 配置文件初识全局块、events、http、server、location 的层级关系

Nginx 配置其实只有两类指令&#xff1a; 放在“某个块”里的块级指令&#xff1b;直接写在顶层的全局指令。 把全部配置想象成一个树形结构&#xff0c;根节点叫 main&#xff0c;往下依次分叉即可。下面用 1 张 ASCII 树 1 张极简示例&#xff0c;30 秒就能看懂层级关系。 层…

OCR大模型最新研究

最新OCR大模型介绍1.GPT-4o 2024.5.14 3.MinerU 2024.7.4 3.GOT-OCR 2024.9.3 4.InternVL3-78B 2025.4.11 开源 通用多模态大模型&#xff0c;OCR是它们的能力之一 因其训练数据的偏向&#xff0c;在文档理解、数学公式识别、图表分析等任务上通常是开源模型中的SOTA&a…

php电子签名

原理使用一对公钥和私钥&#xff0c;用私钥对数据进行签名&#xff0c;用公钥对签名数据进行加密&#xff0c;形成电子签名。电子签名认证&#xff0c;用私钥解密数据&#xff0c;用公钥验证签名。若加密容过长&#xff0c;则将加密内容按照固定长度分块&#xff0c;对每块进行…

鸿蒙Harmony-从零开始构建类似于安卓GreenDao的ORM数据库(三)

目录 一,插入单条数据 二,批量插入数据 三,根据条件删除数据 四,传入对象删除数据 五,删除整张表的数据 六,根据条件更新数据 前面两个章节数据库的创建以及数据库表的创建都已经完成了,下面我们再来看看数据库的增删改查如何构建。 一,插入单条数据 我们先来看一下官…