按键为什么会乱跳:一次按下为什么变成很多次
LED 亮起来之后我又插上了两个轻触按键。A 按键接 PB11B 按键接 PB1两个按键的另一端都接 GND。A 按一下数值加一B 按一下数值减一三颗外接 LED 把这个数值显示成三位二进制。第一次测试问题立刻出现了手指只按了一下数字却往前跳了一长串。松开按键数字才停住。我先问自己“是不是按键坏了”然后回头检查代码。代码看起来很规矩检测到按下就一直等到松手松手以后再给出一个结果。按下、等待、输出顺序没有少任何一步if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)Bit_RESET){while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)Bit_RESET){}value;}按下时进入if按住时卡在while松手后才执行value。乍一看一次按键应该只加一次逻辑几乎挑不出毛病。但实际运行还是会多次触发究竟是为什么呢按住一次程序为什么能数很多次接线是A 按键一端接 PB11B 按键一端接 PB1两个按键的另一端接 GNDPB11、PB1 都配成内部上拉输入PC13、PC14、PC15 接三颗外接 LED分别显示 bit0、bit1、bit2。接线沿用这三颗灯。手册第 20 页注明PC13~PC15作输出时不能作为电流源驱动 LED而且同一时间只能有一个作输出。按手册看这个接法是出规格的——这里不改接线只把限制写出来。先把输入初始化好voidKey_Init(void){GPIO_InitTypeDef gpio;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);gpio.GPIO_PinGPIO_Pin_11|GPIO_Pin_1;gpio.GPIO_ModeGPIO_Mode_IPU;GPIO_Init(GPIOB,gpio);}GPIO_Mode_IPU 里的 IPU 是上拉输入。松开按键时PB11 通过内部上拉保持高电平按下按键时PB11 被按键接到 GND读到低电平。uint8_tKey_A_IsPressed(void){returnGPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)Bit_RESET;}uint8_tKey_B_IsPressed(void){returnGPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)Bit_RESET;}这些函数只回答一个问题“现在是不是按下状态”按住不动它们会连续返回 1。如果主循环这样写while(1){if(Key_A_IsPressed()){count;}}手指按住几十毫秒主循环已经转了成千上万圈count 也就增加了那么多。为什么一定要上拉如果 PB11 既没有接到高电平也没有接到低电平松开按键后它就悬在空中读到的电平没有依据。这就是浮空输入。把模式临时改成gpio.GPIO_ModeGPIO_Mode_IN_FLOATING;然后断开按键连续读取 PB11。LED 可能自己闪计数可能自己变手指靠近时读数还会变化。你没有按它芯片却收到了一个没有固定答案的输入。内部上拉的作用很直接没人按键时给 PB11 一个默认高电平。按键按下后GND 的连接比内部上拉更强把它拉成低电平。也可以使用外部上拉电阻。内部上拉省了一颗电阻接线更简单外部电阻的阻值和电气特性更容易控制。对于这个入门按键内部上拉已经够用。一次按下和当前按着不是一回事边沿检测可以把“松开到按下”抓出来。A、B 各自保存一份上一次状态uint8_tKey_A_DetectPressEdge(void){staticuint8_twas_pressed0;uint8_tis_pressedKey_A_IsPressed();uint8_tevent;event(uint8_t)(is_pressed!was_pressed);was_pressedis_pressed;returnevent;}uint8_tKey_B_DetectPressEdge(void){staticuint8_twas_pressed0;uint8_tis_pressedKey_B_IsPressed();uint8_tevent;event(uint8_t)(is_pressed!was_pressed);was_pressedis_pressed;returnevent;}主循环不再对每个低电平都计数而是只在状态从 0 变成 1 的那一轮得到事件。三颗灯这一侧用一个LED_ShowBinary()它把这个数的低三位分别写到PC13、PC14、PC15上按键这边只负责决定什么时候该调它while(1){if(Key_A_DetectPressEdge()){value(uint8_t)((value1U)0x07U);LED_ShowBinary(value);}if(Key_B_DetectPressEdge()){value(uint8_t)((value7U)0x07U);LED_ShowBinary(value);}}三颗灯那边一位对一根引脚voidLED_ShowBinary(uint8_tvalue){if(value0x01U){GPIO_SetBits(GPIOC,GPIO_Pin_13);}else{GPIO_ResetBits(GPIOC,GPIO_Pin_13);}if(value0x02U){GPIO_SetBits(GPIOC,GPIO_Pin_14);}else{GPIO_ResetBits(GPIOC,GPIO_Pin_14);}if(value0x04U){GPIO_SetBits(GPIOC,GPIO_Pin_15);}else{GPIO_ResetBits(GPIOC,GPIO_Pin_15);}}但这还没有解决轻触按键本身的另一个问题。金属触点碰到一起时不会像理想开关那样干净地从高变低。它可能在几毫秒内弹来弹去高、低、高、低最后才稳定下来。边沿检测会把这些抖动也当成好几次按下。先用最容易看懂的办法确认一次按下发现按下后先等一小段时间再读一次确认仍然按着就等到松手最后只返回一次事件staticvoidKey_WaitShort(void){volatileuint32_ti;for(i0;i72000U;i){}}uint8_tKey_A_GetPressEvent(void){if(Key_A_IsPressed()){Key_WaitShort();if(Key_A_IsPressed()){while(Key_A_IsPressed()){}Key_WaitShort();return1U;}}return0U;}下载后按一次 A 或 B三颗 LED 的二进制数只改变一次。这个版本把触点刚碰到时的抖动过滤掉了也把按住期间的重复读取压成了一个事件。按键没有“按了一次”这个电平。它只给出一根线的高低变化一次、两次和很多次是程序在这些变化里整理出来的事件。最后橘猫说按住按键时输入电平会在一段时间内持续有效如果主循环每转一圈就把有效电平当成一次操作计数自然会飞快增加。触点抖动又会在刚按下和刚松开时制造额外的高低变化。等待一小段时间再确认、再等待松手是最容易看懂的阻塞式处理能把一次按压压成一次返回值但也会占住主循环。更精确的等待和非阻塞消抖要在稳定的时间基准建立后再做。最后蜘蛛说原来不止我的手会抖按钮也会啊。

相关新闻

手撕Transformer④:完整Encoder编码器实战(单层+多层堆叠+全链路维度闭环)

手撕Transformer④:完整Encoder编码器实战(单层+多层堆叠+全链路维度闭环)

手撕Transformer④:完整Encoder编码器实战(单层多层堆叠全链路维度闭环)摘要:前三篇我们逐一拆解了Transformer所有基础零部件:输入层(词嵌入位置编码)、多头自注意力、残差连接、层归一化、FFN…

2026/9/24 17:45:42 阅读更多 →
【AI大模型】推理结果为空:无输出内容的问题定位流程与代码修复方案

【AI大模型】推理结果为空:无输出内容的问题定位流程与代码修复方案

【AI大模型】推理结果为空:无输出内容的问题定位流程与代码修复方案 在AI大模型私有化部署、API推理服务、本地模型调用、对话问答、代码生成、知识库问答等场景中,模型推理无输出、返回空白内容、响应为空、流式无字节输出、返回空字符串是仅次于报错闪退的高频隐性问题。该…

2026/9/24 17:45:42 阅读更多 →
java中的垃圾回收器

java中的垃圾回收器

垃圾回收器 垃圾回收集器分新生代收集器、老年代收集器、组合收集器;JDK8 默认:Parallel Scavenge Parallel Old;JDK9 开始默认 G1; 新生代 Serial(串行收集器) 单线程,STW,标记复制…

2026/9/24 17:45:42 阅读更多 →

最新新闻

Linux与Windows系统运维:参数查询与配置命令对照实战

Linux与Windows系统运维:参数查询与配置命令对照实战

很多时候,我们干的活儿并不是什么高深莫测的架构设计,反而是那些每天都在重复的“查一下参数、改一个配置”。尤其是当你的手头同时管着 Windows 服务器和 Linux 服务器时,这种“精神分裂”的感觉会特别明显:明明在 Windows 上用图…

2026/9/24 18:28:13 阅读更多 →
RabbitMQ安装详解:Windows与Docker高频坑与权限排查

RabbitMQ安装详解:Windows与Docker高频坑与权限排查

先聊点实际的。你点进这篇文章,多半是因为项目里突然要用消息队列,或者面试题刷到“RabbitMQ和Kafka怎么选”,又或者已经在Windows上装了RabbitMQ,结果服务死活起不来,管理界面也打不开。不管你是哪种情况,…

2026/9/24 18:28:13 阅读更多 →
Linux与Windows参数查询与配置:双系统实战速查手册

Linux与Windows参数查询与配置:双系统实战速查手册

1. 项目背景:为什么我要维护一份“Linux与Windows参数查询与配置”手册自从开始同时接触Linux服务器和Windows桌面环境,我就一直被同一个问题反复折磨:某个参数上次明明调通了,下次换台机器又得从头翻文档。更让人崩溃的是&#x…

2026/9/24 18:28:13 阅读更多 →
Flutter状态边界:UI树才是决定setState刷新范围的关键

Flutter状态边界:UI树才是决定setState刷新范围的关键

有人问我一个很经典的问题:setState明明调了,数据也变了,界面就是不动,到底哪里出了问题?我听完他的代码描述,第一反应不是去看状态管理库配没配好,而是反问他一句:你这段状态&#…

2026/9/24 18:28:13 阅读更多 →
基于OpenCV模板匹配的车牌识别毕业设计实战指南

基于OpenCV模板匹配的车牌识别毕业设计实战指南

简介:这是一套基于OpenCV模板匹配的车牌识别毕业设计源码,使用Python 3.8与OpenCV 4.2开发,并配有简单的GUI界面。项目面向计算机相关专业的学生或课程设计用户,主要解决从车辆图像中定位车牌、校正倾斜、判别牌照颜色、分割字符并…

2026/9/24 18:28:13 阅读更多 →
【Coze】【视频】治愈系老爷爷工作流

【Coze】【视频】治愈系老爷爷工作流

今天给大家演示一个 老爷爷语录视频自动生成工作流。该工作流通过大语言模型和图像生成模型的协作,自动完成从文本语录生成、格式化处理、配图生成,再到视频合成和音频配乐的完整流程。结合效果展示,用户只需提供简单的输入,就能得到带有温馨画面和背景音乐的成品视频,大幅…

2026/9/24 18:27:12 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →