GPIO 输入输出——按键、LED 与蜂鸣器

前两篇我们搭好了环境,做了智能小夜灯。你可能已经注意到,那个项目里 Arduino 只能"被动响应"光线变化,我们没法主动控制它。

今天这篇,我们要解决一个核心问题:怎么让 Arduino 既能"听"(读取输入),又能"做"(控制输出)?

学完这篇,你就能用按键、LED 和蜂鸣器搭建一个多模式灯光控制器------按一下切换模式,再按一下又换一种,还能发出提示音。


一、本篇目标

  • 理解数字输入(按键)和数字输出(LED、蜂鸣器)的区别
  • 掌握上拉电阻的原理,搞懂为什么按键电路要那么接
  • 学会按键消抖,解决"按一次触发多次"的经典问题
  • 完成实战项目:按键切换 LED 模式(常亮 / 闪烁 / 呼吸 / 关闭)

二、数字信号:只有 0 和 1

在深入之前,先明确一个概念:数字引脚只能表达两种状态------

  • HIGH(高电平):输出或读取到 5V(逻辑 1)
  • LOW(低电平):输出或读取到 0V(逻辑 0)

这就像电灯开关,只有"开"和"关"两种状态,没有中间值。

操作 函数 方向
控制 LED 亮灭 digitalWrite(引脚, HIGH/LOW) 输出
读取按键状态 digitalRead(引脚) 输入

三、按键输入------为什么需要上拉电阻?

问题:悬空的引脚会"乱跳"

如果你把按键一端接 Arduino 引脚,另一端接 GND,不按的时候,这个引脚处于悬空状态------它既没有连到 5V,也没有连到 GND,就像一个没接天线的收音机,会接收到各种电磁干扰,导致读数在 HIGH 和 LOW 之间随机跳变。

解决:上拉电阻

上拉电阻的作用,就是给悬空的引脚一个"默认状态":

  • 用一个电阻(通常 10kΩ)把引脚拉到 5V
  • 平时(不按按键时),引脚读到的是 HIGH
  • 按下按键时,引脚被直接连到 GND,读到的是 LOW

这样,按键的逻辑就变成了:

  • 不按 → HIGH(默认状态)
  • 按下 → LOW(触发状态)

注意:这个逻辑是"反"的------按下反而是 LOW。这是上拉电阻电路的特点,新手容易搞混,记住就好。

Arduino 内置上拉电阻

其实 Arduino 内置了上拉电阻,你不需要外接。只需要在代码中这样设置:

复制代码
pinMode(buttonPin, INPUT_PULLUP);

INPUT_PULLUP 模式会自动启用内部上拉电阻(约 20kΩ~50kΩ),省去了外接电阻的麻烦。


四、蜂鸣器------让 Arduino "说话"

蜂鸣器分两种:

类型 特点 控制方式
有源蜂鸣器 内部自带振荡电路,通电就响 给 HIGH 就响,给 LOW 就停
无源蜂鸣器 内部没有振荡电路,需要外部驱动 需要用 tone() 函数输出特定频率

本篇使用有源蜂鸣器 ,控制方式最简单------和 LED 一样,digitalWrite(HIGH) 就响,digitalWrite(LOW) 就停。

有源蜂鸣器通常比无源的高一点,顶部有黑色封胶覆盖;无源蜂鸣器顶部通常能看到绿色电路板。


五、硬件清单

元件 数量 作用
Arduino Uno 1块 主控板
面包板 1块 搭建电路
轻触按键 1个 输入控制
LED(任意颜色) 1颗 输出显示
330Ω电阻 1个 LED 限流
有源蜂鸣器 1个 声音提示
杜邦线 若干 连接元件

六、电路接线

按键接线
  • 按键有 4 个脚,实际上内部是两两连通的一对开关
  • 把按键跨在面包板中间沟槽的两侧
  • 一端 接 Arduino 的 D2 引脚
  • 另一端 接 Arduino 的 GND

使用 INPUT_PULLUP 模式,不需要外接电阻。

LED 接线
  • LED 长脚(正极) 串联 330Ω 电阻,接到 Arduino 的 D9(PWM 引脚)
  • LED 短脚(负极) 接到面包板的 GND 排
蜂鸣器接线
  • 蜂鸣器 正极(长脚/标"+"号) 接到 Arduino 的 D8
  • 蜂鸣器 负极(短脚/标"-"号) 接到面包板的 GND 排
接线检查清单
  • ✅ 按键一端接 D2,另一端接 GND
  • ✅ LED 长脚经电阻接 D9,短脚接 GND
  • ✅ 蜂鸣器正极接 D8,负极接 GND
  • ✅ 面包板的 GND 排与 Arduino 的 GND 连通

七、代码实现:按键切换四种模式

打开 Arduino IDE,新建项目,输入以下代码:

复制代码
// 引脚定义
const int buttonPin = 2;   // 按键接 D2
const int ledPin = 9;      // LED 接 D9(PWM引脚)
const int buzzerPin = 8;   // 蜂鸣器接 D8

// 模式定义
// 0=关闭, 1=常亮, 2=闪烁, 3=呼吸
int mode = 0;

// 按键状态记录
int lastButtonState = HIGH;    // 上一次按键状态
unsigned long lastDebounceTime = 0;  // 上次状态变化的时间
unsigned long debounceDelay = 50;    // 消抖延时(毫秒)

void setup() {
  Serial.begin(9600);

  pinMode(buttonPin, INPUT_PULLUP);  // 启用内部上拉电阻
  pinMode(ledPin, OUTPUT);
  pinMode(buzzerPin, OUTPUT);

  // 开机提示音
  digitalWrite(buzzerPin, HIGH);
  delay(100);
  digitalWrite(buzzerPin, LOW);

  Serial.println("系统就绪,按按键切换模式");
  Serial.println("模式: 0=关闭 1=常亮 2=闪烁 3=呼吸");
}

void loop() {
  // 读取按键状态
  int reading = digitalRead(buttonPin);

  // 按键消抖:只有状态稳定超过 debounceDelay 毫秒,才认为是一次有效按键
  if (reading != lastButtonState) {
    lastDebounceTime = millis();  // 记录状态变化的时间
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // 如果当前是 LOW(按下),且之前是 HIGH(未按下),说明刚按下
    if (reading == LOW && lastButtonState == HIGH) {
      mode = (mode + 1) % 4;  // 在 0~3 之间循环切换
      Serial.print("切换到模式: ");
      Serial.println(mode);

      // 切换模式时蜂鸣器短响一声
      digitalWrite(buzzerPin, HIGH);
      delay(50);
      digitalWrite(buzzerPin, LOW);
    }
  }

  lastButtonState = reading;

  // 根据当前模式执行对应动作
  switch (mode) {
    case 0:  // 关闭
      analogWrite(ledPin, 0);
      break;

    case 1:  // 常亮
      analogWrite(ledPin, 200);
      break;

    case 2:  // 闪烁
      digitalWrite(ledPin, HIGH);
      delay(300);
      digitalWrite(ledPin, LOW);
      delay(300);
      break;

    case 3:  // 呼吸
      // 渐亮
      for (int brightness = 0; brightness <= 255; brightness += 5) {
        analogWrite(ledPin, brightness);
        delay(15);
      }
      // 渐暗
      for (int brightness = 255; brightness >= 0; brightness -= 5) {
        analogWrite(ledPin, brightness);
        delay(15);
      }
      break;
  }
}

八、代码核心逻辑解读

按键消抖------为什么必须做?

机械按键在按下和松开的瞬间,金属触点会发生弹跳,在几毫秒内产生多次 HIGH/LOW 跳变。如果不处理,按一次按键可能被 Arduino 识别成按了 3~5 次。

消抖的思路很简单:

  1. 检测到按键状态发生变化时,不立即响应

  2. 等一小段时间(50ms),确认状态稳定了

  3. 如果稳定后的状态和之前不同,才认为是一次有效按键

    if (reading != lastButtonState) {
    lastDebounceTime = millis(); // 状态变了,重新计时
    }

    if ((millis() - lastDebounceTime) > debounceDelay) {
    // 稳定超过 50ms,确认是有效按键
    }

millis() 返回 Arduino 开机以来的毫秒数,用它来做时间判断,比 delay() 更灵活,不会阻塞程序运行。

模式切换------用取模运算实现循环
复制代码
mode = (mode + 1) % 4;

这行代码的效果:0 → 1 → 2 → 3 → 0 → 1 → ...,在 0~3 之间无限循环。% 4(取模 4)就是"除以 4 取余数",当 mode 为 3 时,(3+1) % 4 = 0,自动回到起始值。

switch-case------多分支选择

相比写一堆 if / else ifswitch-case 在多模式切换时代码更清晰:

复制代码
switch (mode) {
  case 0: ... break;
  case 1: ... break;
  case 2: ... break;
  case 3: ... break;
}

每个 case 后面别忘了 break,否则会"穿透"到下一个分支。


九、效果验证

上传程序后,打开串口监视器(波特率 9600),然后:

  1. 开机:蜂鸣器"滴"一声,LED 熄灭(模式 0)
  2. 第一次按:切换到模式 1(常亮),蜂鸣器短响
  3. 第二次按:切换到模式 2(闪烁)
  4. 第三次按:切换到模式 3(呼吸)
  5. 第四次按:回到模式 0(关闭),循环往复

串口监视器会实时打印当前切换到的模式编号。


十、常见问题排查

Q:按一下按键,模式跳了好几个?

按键消抖没生效。检查 debounceDelay 的值,可以适当增大到 80~100ms。

Q:按键按了没反应?

确认按键接线:一端接 D2,另一端接 GND。确认代码中使用了 INPUT_PULLUP 模式。

Q:蜂鸣器不响?

有源蜂鸣器有正负极,长脚是正极,接 D8;短脚是负极,接 GND。接反了不会响但不会烧。

Q:呼吸灯效果很卡顿?

呼吸灯的 for 循环中有 delay(15),这会阻塞按键检测。如果卡顿明显,后面可以改用 millis() 做非阻塞式呼吸灯(进阶内容)。


十一、本篇小结

回顾一下今天学到的核心知识:

  • 数字输入digitalRead() 读取按键状态
  • 上拉电阻 :给悬空引脚一个默认电平,INPUT_PULLUP 模式内置上拉
  • 按键消抖 :用 millis() 做时间判断,过滤机械弹跳
  • 数字输出digitalWrite() 控制 LED 和蜂鸣器
  • 多模式切换switch-case + 取模运算实现循环切换

这些是 Arduino 开发中最基础也最常用的技能。掌握了输入输出的读写,你就具备了和硬件"对话"的能力。

下一篇,我们进入模拟输入------学习如何读取传感器返回的连续数值(而不只是 0 和 1),用旋钮控制 LED 亮度,用光敏电阻做光照强度计。

相关推荐
xu_wenming5 小时前
嵌入式软件架构中的6种解耦艺术
c语言·驱动开发·嵌入式硬件·架构
sramdram5 小时前
国产32位mcu微控制器高速风筒方案
单片机·嵌入式硬件·32位mcu·mcu微控制器·国产32位mcu
单片机仿真设计5 小时前
【proteus仿真】基于STM32单片机直流电机控制设计(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus·毕设
天空'之城5 小时前
单片机基础核心知识点汇总(十八)
单片机·嵌入式硬件
736c6 小时前
STM32-看门狗 10
stm32·单片机
luiyarch6 小时前
汽车电子ISO 26262功能安全系列(第17期):系统集成与测试——验证系统级安全机制
嵌入式硬件·安全·车载系统·汽车
DevHub9 小时前
CANopen 教程:STM32 实现 CANopen 从站,3 个回调替代手写对象字典
c语言·驱动开发·stm32·单片机·嵌入式硬件·mcu
单片机仿真设计11 小时前
【proteus仿真】基于 STM32 农业大棚巡检小车 (仿真图+程序)
stm32·单片机·嵌入式硬件·proteus·毕设
Hello_Damon_Nikola13 小时前
AiP9M161个人护理产品方案从设计到实现
单片机·嵌入式硬件