数字 IO 与按键
数字引脚的读与写、上拉/下拉电阻的意义、为什么按键会"抖动",以及两种可靠的消抖写法。
数字引脚的读与写、上拉/下拉电阻的意义、为什么按键会"抖动",以及两种可靠的消抖写法。
OUTPUT(输出,能对外驱动电平)、INPUT(输入,高阻态读取)、INPUT_PULLUP(输入并启用芯片内部上拉电阻)。忘记设模式,引脚行为将不可预测。HIGH(5V) 或 LOW(0V)。只对已设为 OUTPUT 的引脚有效。对 INPUT_PULLUP 引脚 digitalWrite(HIGH) 会开启内部上拉,写 LOW 关闭——这是它的特殊用法。HIGH(1) 或 LOW(0)。用于检测按键是否按下、传感器是否触发。读取的是"此刻"的电平快照,因此要放在 loop 里反复读。INPUT_PULLUP 即可启用,省掉外部电阻。面包板是免焊接的原型工具,理解它的内部连通关系是接线不出错的前提:
把 LED 接到 D8,练习数字输出。注意 LED 有极性:
// 外部 LED 闪烁,接在 D8 const int ledPin = 8; void setup() { pinMode(ledPin, OUTPUT); } void loop() { digitalWrite(ledPin, HIGH); delay(300); digitalWrite(ledPin, LOW); delay(300); }
用内部上拉,按键一端接引脚、另一端直接接 GND,无需外部电阻——这是最省事的经典接法。
// 按键控制 LED:按下亮,松开灭 const int btnPin = 2; const int ledPin = 8; void setup() { pinMode(btnPin, INPUT_PULLUP); // 启用内部上拉 pinMode(ledPin, OUTPUT); } void loop() { // 按键按下时引脚为 LOW if (digitalRead(btnPin) == LOW) { digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } }
如果按键一端接 5V、另一端接引脚,而引脚是普通 INPUT(无上拉/下拉),那么松开按键时引脚悬空,读到的是随机噪声,LED 会乱闪。必须靠上拉或下拉电阻给"松开"状态一个确定电平。用 INPUT_PULLUP 就是让芯片替你接好上拉。
想实现"按一次按键,LED 翻转一次亮/灭状态"。天真写法会因抖动失效:
// 检测到按下后延时 20ms 再确认,简单但会阻塞 const int btnPin = 2; const int ledPin = 8; bool ledState = false; int lastReading = HIGH; void setup() { pinMode(btnPin, INPUT_PULLUP); pinMode(ledPin, OUTPUT); } void loop() { int reading = digitalRead(btnPin); // 检测下降沿:从 HIGH 变到 LOW = 刚刚按下 if (lastReading == HIGH && reading == LOW) { delay(20); // 等抖动过去 if (digitalRead(btnPin) == LOW) { // 再确认一次 ledState = !ledState; // 翻转状态 digitalWrite(ledPin, ledState); } } lastReading = reading; }
延时法的缺点是 delay 会卡住整个程序。用 millis() 记录时间戳,不阻塞、可同时干别的事——这是工业级写法。
// millis() 非阻塞消抖:不卡程序,专业推荐 const int btnPin = 2; const int ledPin = 8; bool ledState = false; int lastSteady = HIGH; // 上一次稳定电平 int lastFlicker = HIGH; // 上一次原始读数 unsigned long lastChangeTime = 0; // 电平变化的时刻 const unsigned long DEBOUNCE = 30; // 消抖窗口 30ms void setup() { pinMode(btnPin, INPUT_PULLUP); pinMode(ledPin, OUTPUT); } void loop() { int reading = digitalRead(btnPin); // 原始读数一变,就记录当前时间 if (reading != lastFlicker) { lastChangeTime = millis(); lastFlicker = reading; } // 距上次变化已超过 30ms,说明电平稳定了 if (millis() - lastChangeTime > DEBOUNCE) { // 稳定值发生了改变 if (reading != lastSteady) { lastSteady = reading; // 只在"稳定地按下"(变为 LOW)时翻转 if (lastSteady == LOW) { ledState = !ledState; digitalWrite(ledPin, ledState); } } } }
millis() 返回开机以来经过的毫秒数(unsigned long)。它像一块从不停歇的秒表。"记录起点时刻 → 反复用当前时刻减起点 → 到时才行动"是 Arduino 非阻塞编程的核心套路,第 9 章会系统展开。约 49.7 天后 millis 会溢出归零,但用减法比较能自动正确处理溢出,无需担心。
消抖窗口 20-50ms 合适。设成 200ms 以上会让按键"迟钝",快速连按会漏掉。另外不要在中断里加 delay 消抖(第 9 章讲中断),会拖慢整个系统。
数字 IO 三大函数:pinMode 设方向、digitalWrite 设输出电平(HIGH/LOW)、digitalRead 读输入电平。输入引脚绝不能悬空,否则读到随机噪声——必须用上拉或下拉电阻给默认电平。Arduino 内置上拉(约 20-50kΩ),用 pinMode(pin, INPUT_PULLUP) 启用,按键另一端接 GND,逻辑变成"平时 HIGH、按下 LOW"(低电平有效),这是最省事的接法;下拉需外接 10kΩ 电阻。接 LED 输出务必串限流电阻(约 330Ω)并注意长脚为正极。机械按键触点接触瞬间有 2-10ms 抖动,会被高速的 CPU 误读成多次按压,必须消抖:简单法是检测到跳变后 delay(20) 再确认(会阻塞),推荐法用 millis() 记录电平变化时刻、稳定超过 30ms 才认可(非阻塞)。要实现"按一次做一次"需做边沿检测:保存上次状态与当前比较,只在下降沿动作。