前两篇我们搭好了环境,做了智能小夜灯。你可能已经注意到,那个项目里 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 次。
消抖的思路很简单:
-
检测到按键状态发生变化时,不立即响应
-
等一小段时间(50ms),确认状态稳定了
-
如果稳定后的状态和之前不同,才认为是一次有效按键
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 if,switch-case 在多模式切换时代码更清晰:
switch (mode) {
case 0: ... break;
case 1: ... break;
case 2: ... break;
case 3: ... break;
}
每个 case 后面别忘了 break,否则会"穿透"到下一个分支。
九、效果验证
上传程序后,打开串口监视器(波特率 9600),然后:
- 开机:蜂鸣器"滴"一声,LED 熄灭(模式 0)
- 第一次按:切换到模式 1(常亮),蜂鸣器短响
- 第二次按:切换到模式 2(闪烁)
- 第三次按:切换到模式 3(呼吸)
- 第四次按:回到模式 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 亮度,用光敏电阻做光照强度计。