一次手指动作,为何产生多个电平变化
如果直接检测“GPIO是LOW就加一”,按住按钮的几百毫秒内loop会执行许多次;即使只检测下降沿,机械触点在接触的瞬间仍可能短暂断开再接通。一种错误来自持续电平,另一种来自触点抖动,需要分别处理。
本篇把原始电平、候选电平、已接受的稳定电平分开:原始值一变就重启30毫秒计时,只有候选值连续稳定足够久才更新稳定状态;稳定状态从HIGH变LOW时产生一次press。最终作品是按钮切灯器,长按只切换一次。
- 做到这里,你应该看到
- 能说明“低电平”与“一次稳定按下事件”为什么不是同一件事。
参考:[1] Arduino-ESP32 3.3.12:GPIO API与模式[3] Arduino:millis()返回类型与回绕
上拉让松开的按钮也有明确状态
保留P01的GPIO4 LED电路。按钮使用常开触点,GPIO5(J1-5)接一侧,另一侧接GND,软件配置INPUT_PULLUP。松开时由内部上拉保持HIGH;按下时触点把输入接地,读到LOW。
常见四脚轻触按钮每组两脚内部已经相连。如果选了同一组的两脚,输入会始终接地;如果把电源与地接到内部相连的两脚,还会形成短路。先断电用通断挡确认两组,按下时才导通的组合才能作为按钮两侧。
本篇外接灯固定为Kingbright WP7113ID(规格书V.14A),使用330Ω、±5%、额定0.25W电阻;如果沿用F03的1kΩ电阻,也可保留,亮度更低,程序不变。更换电阻必须先断电。
| 节点 | 连接 | 预期电平 |
|---|---|---|
| GPIO5(J1-5) | 按钮的一组触点 | INPUT_PULLUP |
| 按钮另一组触点 | GND | 按下时输入LOW |
| GPIO4 | 330Ω→LED阳极,阴极GND | 按下沿切换 |
参考:[1] Arduino-ESP32 3.3.12:GPIO API与模式[2] 乐鑫:ESP32-S3-DevKitC-1 v1.1,排针与RGB修订[4] Kingbright WP7113ID规格书 V.14A:极性、10mA条件压降与额定值
用稳定窗口过滤抖动,释放也要经过同一规则
30毫秒是这份初学示例的初始阈值,不是所有按钮的抖动上限。候选值每次变化都更新changedAt,因此抖动结束前不会提前接受。松开也要稳定30毫秒才更新状态,避免在一次动作里反复接受按下和松开。
上电时以当前输入初始化candidate和stable,避免刚启动就把一个已按住的按钮当成新事件。测试前要先松开;计数只记录运行过程中新的稳定下降沿。很短的触碰可能被30毫秒规则过滤,这是定义的输入范围,应明确记录。
参考:[1] Arduino-ESP32 3.3.12:GPIO API与模式[3] Arduino:millis()返回类型与回绕
完整代码:只有稳定下降沿改变LED
程序没有delay,也没有在每次扫描时打印,避免大量日志影响读入。press日志只在有效按下时增加,release日志用于确认松开确实被接受。单看LED亮灭不容易发现重复两次切换,计数日志是必要的第二条证据。
// SPDX-License-Identifier: MIT
#include <Arduino.h>
constexpr uint8_t LED_PIN = 4, BUTTON_PIN = 5;
constexpr uint32_t DEBOUNCE_MS = 30;
bool candidate, stable, lamp = false;
uint32_t changedAt = 0, pressCount = 0;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
pinMode(BUTTON_PIN, INPUT_PULLUP);
candidate = stable = digitalRead(BUTTON_PIN);
changedAt = millis();
Serial.println("P02 ready; release button before 30-press test");
}
void loop() {
const uint32_t now = millis();
const bool raw = digitalRead(BUTTON_PIN);
if (raw != candidate) {
candidate = raw;
changedAt = now;
}
if (candidate != stable && uint32_t(now - changedAt) >= DEBOUNCE_MS) {
stable = candidate;
if (stable == LOW) {
lamp = !lamp;
digitalWrite(LED_PIN, lamp ? HIGH : LOW);
Serial.printf("press=%lu t=%lu led=%u\n", (unsigned long)++pressCount,
(unsigned long)now, lamp);
} else {
Serial.printf("release t=%lu\n", (unsigned long)now);
}
}
}
参考:[1] Arduino-ESP32 3.3.12:GPIO API与模式[3] Arduino:millis()返回类型与回绕
用30次短按与一次长按检验结果
先按P01命令方式编译本篇目录并上传,打开115200串口,保持按钮松开再RESET。本篇下载包含与正文一致的源码、锁定profile和README。验收要求来自要解决的“重复事件”,不是看见灯能亮就结束。
- 记录初始LED灭、计数0。每次按住约0.2秒再松开约0.2秒,做30次,用纸或表格标记物理次数与press编号。
- 每次动作应只有一条新press,最后编号30;30次切换后LED应回到灭。保存实际串口日志。
- 再按住2秒,应只出现press=31,松开只出现release;LED不在长按中反复改变。
- 如果一次动作重复计数,先排接线,再保留原始电平/逻辑分析仪波形,根据实际抖动调整阈值,重新完整做30次。
- 做到这里,你应该看到
- 30次的实际事件数等于30,长按只计一次,且保存了实板记录。本站尚未执行该硬件检查。
从“总是HIGH/LOW”开始定位
总是LOW:断电检查按钮是否接了同组脚、GPIO5是否直接碰地、按钮是否机械卡住。总是HIGH:查缺共地、导线未入孔或选错脚组。先量状态,不要先加更长delay。
日志增加但灯不切换:回到P01独立测试GPIO4。按一次出现多条press:确认没有另一段代码在累计,并检查是否在上电/松开后完整经过稳定窗口。长线和较强干扰可能需要合适的外部电路,单纯把阈值无限增大会让操作迟钝。
RESET会清空本例计数和灯状态。恢复原始30毫秒代码并重复验收,得到可信基线后,再进入多任务计时。
资料来源与版本说明
资料与固定版本源码核对;构建、仿真与实板为分开的验证维度。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。