本章要点
引脚模式决定一个引脚是「听」还是「说」。OUTPUT 让你控制外设,INPUT 让你读取外设,INPUT_PULLUP 则额外开启内部上拉电阻——引脚悬空时被拉到 3.3V 读到 HIGH,按下按键把引脚接到 GND 就读到 LOW。
机械按键按下瞬间,触点会抖动 5~20ms,程序若不处理会误判成多次按压。软件消抖的思路是:读数变化后,必须再稳定超过 50ms 才承认状态真的变了。
长按判定靠一个「是否已触发过」的标记位。不设这个标记,长按期间每隔 50ms 就会被判一次短按,现象就是 LED 疯狂翻转——这正是新手最常踩的坑。
硬件清单与原理
| 名称 | 数量 | 作用与原理 |
|---|---|---|
| 轻触按键 | 1 个 | 一对常开触点,按下导通、松开断开。机械触点有 5~20ms 抖动,代码必须做消抖。 |
| LED | 1 个 | 发光二极管,单向导电。正向压降约 1.8~2.2V,长脚为阳极、短脚为阴极。 |
| 220Ω 电阻 | 1 个 | 限流保护。把 LED 电流限制在约 (3.3-2.0)/220 ≈ 6mA,既够亮又不会烧坏。 |
| 有源蜂鸣器 | 1 个 | 内置驱动电路,通电即响,电流约 20~30mA。必须用有源款。 |
接线说明
GPIO4 → 按键 K1 一端 按键另一端 → GND
GPIO18 → [220Ω] → LED 阳极 LED 阴极 → GND
① GPIO19 → 蜂鸣器正极 蜂鸣器负极 → GND
② 通电前务必确认:所有 GND 已共地,无 5V 接入 GPIO
提示:蜂鸣器必须用「有源」款;无源蜂鸣器需要自己输出方波,现象完全不同。
实战案例 03-1 · 按键控制 LED + 蜂鸣器报警
综合练习 GPIO 的输入与输出两个方向,短按切换 LED、长按触发蜂鸣器。
短按一次按键 LED 状态翻转;长按按键,蜂鸣器连续响 3 秒后停止。
/* * ============================================================================ * 第 3 章 · 实战案例 03-1:按键控制 LED + 蜂鸣器报警(输入输出综合) * ---------------------------------------------------------------------------- * 硬件清单与元器件原理: * 1) 轻触按键 × 1 —— 一对常开触点,按下时导通、松开时断开,本质是一个「机械开关」。 * 机械触点存在约 5~20ms 的抖动(bounce),程序必须做消抖处理,否则一次按压 * 会被识别成好几次。 * 2) LED × 1 —— 二极管单向导电,正向导通电压约 1.8~2.2V,超过额定电流会烧毁。 * 串 220Ω 电阻做限流,把电流限制在约 (3.3-2.0)/220 ≈ 6mA。 * 3) 有源蜂鸣器 × 1 —— 内置驱动电路,给电就响,通电电流约 20~30mA。 * ⚠ 必须用「有源」蜂鸣器;无源蜂鸣器需要自己输出方波,本章用的是有源款。 * 4) 220Ω 电阻 × 1(LED 限流用) * * 接线方式: * 按键 K1: * GPIO4 → 按键一侧 * 按键另一侧 → GND * (程序内部开启内部上拉,按下时读到 LOW) * * LED D1: * GPIO18 → [220Ω] → LED 阳极(长脚) * LED 阴极(短脚)→ GND * * 蜂鸣器 B1(有源): * GPIO19 → 蜂鸣器正极 * 蜂鸣器负极 → GND * * 功能用途: * 综合练习 GPIO 的「输入」和「输出」两种方向: * - 短按按键 → 切换 LED 亮灭 * - 长按按键(≥1.5 秒)→ 触发蜂鸣器报警 3 秒 * * 预期现象: * 短按一次按键,LED 状态翻转;长按按键,蜂鸣器连续响 3 秒后停止。 * ============================================================================ */ #include <Arduino.h> // Arduino 核心库 /* ========================================================================== * 一、引脚定义 * ======================================================================== */ #define BUTTON_PIN 4 // 按键接 GPIO4(内部上拉,无需外部电阻) #define LED_PIN 18 // LED 接 GPIO18 #define BUZZER_PIN 19 // 有源蜂鸣器接 GPIO19 /* ========================================================================== * 二、参数与常量 * ======================================================================== */ const class="kw">unsigned long DEBOUNCE_MS = 50; // 消抖时间:抖动通常在 20ms 内,50ms 足够 const class="kw">unsigned long LONG_PRESS_MS = 1500; // 长按判定阈值:按住超过 1.5 秒算长按 const class="kw">unsigned long BUZZER_DUR_MS = 3000; // 蜂鸣器持续响的时间 // 数字逻辑的「真 / 假」用常量表达,比直接写 1 和 0 更容易读懂 const bool LED_ON = true; const bool LED_OFF = false; /* ========================================================================== * 三、状态变量 * ======================================================================== */ bool ledState = LED_OFF; // LED 当前状态 bool buzzerActive = false; // 蜂鸣器是否正在响 class="kw">unsigned long buttonPressedAt = 0; // 按键被按下的时刻 bool longPressHandled = false; // 本次按压是否已经触发过长按(避免重复触发) bool lastButtonReading = HIGH; // 上一次读到的按键电平,用于检测「按下瞬间」 class="kw">unsigned long buzzerOffAt = 0; // 蜂鸣器应该停止的时刻 class="kw">unsigned long lastDebounceChange = 0;// 按键电平最后一次「稳定变化」的时刻 /* ========================================================================== * 四、初始化 * ======================================================================== */ void setup() { Serial.begin(115200); // 启动串口 delay(200); // 等待串口芯片就绪 // 按键引脚:INPUT_PULLUP 表示「内部上拉」—— // 引脚内部接了一个拉到 3.3V 的电阻,悬空时读到的就是 HIGH; // 按键按下把引脚接到 GND,读到 LOW。这样只需要 2 根线,不用外接上拉电阻。 pinMode(BUTTON_PIN, INPUT_PULLUP); // LED 和蜂鸣器都是输出 pinMode(LED_PIN, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(LED_PIN, HIGH); // LED 初始熄灭(低电平点亮,故写 HIGH) digitalWrite(BUZZER_PIN, LOW); // 蜂鸣器初始不响(有源蜂鸣器高电平响) Serial.println(class="st">"[GPIO] 初始化完成"); Serial.println(class="st">"[GPIO] 短按按键 = 切换 LED;长按 1.5 秒 = 蜂鸣器响 3 秒"); } /* ========================================================================== * 五、自定义函数:让蜂鸣器开始响 * ======================================================================== */ void startBuzzer() { buzzerActive = true; // 标记为「正在响」 digitalWrite(BUZZER_PIN, HIGH); // 有源蜂鸣器:高电平通电发声 buzzerOffAt = millis() + BUZZER_DUR_MS; // 记录应该停止的时刻 Serial.println(class="st">"[GPIO] >>> 触发报警:蜂鸣器响 3 秒"); } /* ========================================================================== * 六、自定义函数:切换 LED 状态 * ======================================================================== */ void toggleLed() { ledState = !ledState; // 取反:true↔false digitalWrite(LED_PIN, ledState ? LOW : HIGH); // 低电平点亮 Serial.print(class="st">"[GPIO] LED 状态:"); Serial.println(ledState ? class="st">"ON" : class="st">"OFF"); } /* ========================================================================== * 七、主循环 * ======================================================================== */ void loop() { class="kw">unsigned long now = millis(); /* ---- 1. 蜂鸣器超时自动停止 ---- */ if (buzzerActive && now >= buzzerOffAt) { buzzerActive = false; digitalWrite(BUZZER_PIN, LOW); // 拉低,蜂鸣器停止 Serial.println(class="st">"[GPIO] <<< 报警结束,蜂鸣器已停止"); } /* ---- 2. 读取按键 + 消抖 ---- */ // 读取当前电平。因为是上拉模式,按下 = LOW,松开 = HIGH bool reading = digitalRead(BUTTON_PIN); // 只有当读数与「上次稳定读数」不同,且距离上次变化已超过消抖时间,才认为状态真的变了。 // 这样可以滤掉机械触点在接触瞬间产生的一串快速通断。 if (reading != lastButtonReading && (now - lastDebounceChange) > DEBOUNCE_MS) { lastDebounceChange = now; // 记录这次「确认有效」的变化时刻 lastButtonReading = reading; // 更新稳定读数 if (reading == LOW) { // 确认检测到「按下」动作 buttonPressedAt = now; // 记下按下的时刻,用于判断长按 longPressHandled = false; // 本次按压还没触发过长按 Serial.println(class="st">"[GPIO] 检测到按键按下"); } else { // 检测到「松开」 if (!longPressHandled) { // 如果整个按压过程中没有触发过长按,说明这是一次「短按」→ 切换 LED toggleLed(); } else { Serial.println(class="st">"[GPIO] 长按结束(本次不切换 LED)"); } } } /* ---- 3. 长按检测 ---- */ // 条件:按键仍处于按下状态(reading == LOW)且按住时间已超过阈值,且本次尚未触发过 if (reading == LOW && !longPressHandled && (now - buttonPressedAt) >= LONG_PRESS_MS) { longPressHandled = true; // 标记已触发,防止抖动导致连响 startBuzzer(); // 启动蜂鸣器 } }
本章小结
① INPUT_PULLUP 用芯片内部电阻代替外部上拉,按键只需 2 根线,抗干扰更好。
② 用上拉模式后逻辑要反过来:按键按下 = LOW,松开 = HIGH。
③ 机械抖动是物理现象,软件消抖是唯一可靠解法:读数变化 + 稳定超过 50ms 才认账。
④ longPressHandled 这类一次性标记是防止重复触发的关键。
⑤ 蜂鸣器分有源(通电响)和无源(需方波驱动),买错型号现象完全不同。
本章练习
把 LONG_PRESS_MS 改成 3000,观察长按判定变慢。
去掉 longPressHandled 标记,观察长按时 LED 疯狂翻转。
去掉消抖逻辑,用手指按住按键看串口是否多次触发。
增加第二按键(GPIO33),短按切换 LED、长按关闭蜂鸣器。