Arduino UNO 单中断高效控制 10 个按钮
元器件与材料
- Arduino UNO ×1(或其他微控制器)
- 面包板 ×1
- 跳线 ×21
- 轻触开关 ×10(大小不限)
软件与在线服务
- Arduino IDE
项目简介
引言
中断非常实用。除了简化代码,还可用于精确计时或从睡眠模式唤醒 Arduino。
假设你有一个由电池供电的用户界面(如遥控器),可能希望将 Arduino(或独立 ATmega)置于掉电模式以省电。进入掉电模式后,Arduino 只能通过外部中断唤醒。Arduino Uno 使用的 ATmega328P 芯片仅有两个外部引脚中断(引脚 2 和 3 上的 INT0 和 INT1)。既然用户界面通常不止两个按钮,这就成了问题。
标准解法是将所有按钮常规连接,并通过二极管连到中断引脚,但这会显著增加电路复杂度。

除了标准外部中断,ATmega328P 还支持引脚变更中断,有现成库可用,也是解决此问题的好办法。
然而在一次编程比赛中,我发现了一种无需额外电气元件、仅用标准外部中断即可实现的方法。
电路设计



示例使用了 10 个按钮,完美适配面包板。每个按钮一侧接任意引脚(示例用 4~13),另一侧共同连接到单个支持中断的引脚(示例用引脚 2)。Uno 可支持最多 20 个按钮,Mega 甚至可达 70 个(若真需 70 按键,建议用多路复用,无需整块 Mega)。

代码实现
完整代码见文末。上传后打开串口监视器,按下任意按钮即显示其编号。注意 loop() 函数完全未使用。

工作原理
核心是一个配置为 FALLING 模式的中断(示例用引脚 2)。为避免使用二极管,Arduino 即时重构电路,在两种模式间切换:公共模式和独立模式。
公共模式
大部分时间处于公共模式:中断引脚配置为 INPUT_PULLUP,其余按钮引脚设为 OUTPUT 并输出 LOW。
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins)/sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
此模式下按下任意按钮都会拉低中断引脚并触发中断。中断服务程序随即切换到独立模式。
独立模式
中断触发后迅速切换到独立模式:中断引脚变为 OUTPUT 并输出 LOW,其余按钮引脚设为 INPUT_PULLUP。
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins)/sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
独立模式下,只有实际被按下的按钮对应引脚会被拉低。遍历所有引脚即可识别触发中断的按钮。识别完成后可切回公共模式等待下一次中断,或视应用需求保持独立模式处理输入,睡眠前再切回公共模式。
进阶示例:舵机控制与睡眠模式



该示例将每个按钮映射到不同舵机角度(1=0°,2=20°……10=120°),并用电池供电。无操作 5 秒后进入掉电模式省电,舵机通过晶体管供电,不用时切断。完整代码见文末。
代码
串口记录器版本
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
unsigned long lastFire = 0;
void setup() {
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
Serial.begin(9600);
}
void loop() { /* 空 */ }
void pressInterrupt() {
if (millis() - lastFire < 200) return; // 去抖
lastFire = millis();
configureDistinct();
for (int i = 0; i < sizeof(buttonPins)/sizeof(int); i++) {
if (!digitalRead(buttonPins[i])) press(i);
}
configureCommon();
}
void configureCommon() {
pinMode(commonPin, INPUT_PULLUP);
for (int i = 0; i < sizeof(buttonPins)/sizeof(int); i++) {
pinMode(buttonPins[i], OUTPUT);
digitalWrite(buttonPins[i], LOW);
}
}
void configureDistinct() {
pinMode(commonPin, OUTPUT);
digitalWrite(commonPin, LOW);
for (int i = 0; i < sizeof(buttonPins)/sizeof(int); i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void press(int button) {
Serial.println(button + 1);
}
舵机+睡眠模式版本
#include
#include
#include
const int commonPin = 2;
const int buttonPins[] = {4,5,6,7,8,9,10,11,12,13};
const int servoEnablePin = A1;
const int servoPin = A0;
Servo servo;
unsigned long lastFire = 0;
int status = 0;
void setup() {
pinMode(commonPin, INPUT_PULLUP);
configureCommon();
attachInterrupt(digitalPinToInterrupt(commonPin), pressInterrupt, FALLING);
servo.attach(servoPin);
pinMode(servoEnablePin, OUTPUT);
}
void loop() {
if (millis()-lastFire > 5000) {
digitalWrite(servoEnablePin, LOW);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode();
}
delay(10);
}
void pressInterrupt() {
sleep_disable();
power_all_enable();
if (millis()-lastFire < 200) return;
lastFire = millis();
configureDistinct();
for (int i=0; i
原理图
提供 Fritzing 源文件:multiinterrupt_hoF76Oc4T5.fzz、servo_with_sleep_u9ZqxF0jhY.fzz
制造工艺
- 仅用 Arduino 构建 DTMF 解码器 – 无需额外 IC
- 用 Arduino 打造绚丽显示器 Ambilight 系统
- 用 Arduino 制作超声波悬浮装置:分步指南
- Arduino 与智能手机打造智能电压表——简单易做的 DIY 项目
- 基于 Arduino 与 MAX30100 的实时物联网心率监测系统
- Arduino Uno WiFi 网页服务器:通过浏览器控制 LED 开关
- 智能停车计数系统:Arduino + Processing + PHP 实时车位追踪上云
- Arduino 自动化 Chrome 恐龙游戏 - LDR 光敏电阻 + Python 实现全自动跳跃
- 使用 Arduino Nano 和 RDA8057M 制作紧凑型 FM 收音机
- 无线Arduino编程盾:HC-05蓝牙模块实现免USB线烧录
- 基于有限状态机的 Arduino 电子表 – YAKINDU Statechart Tools 实战
- Arduino 电容式触控开关:一枚硬币点亮 LED 的极简 DIY