上一篇我們已經讓號誌只在按鈕被按下瞬間切換一次,但實體按鈕並不總是乖乖配合,偶爾按一次,訊號卻可能跳動好幾次,今天要加入簡易去彈跳處理,讓每一次按下都能得到更穩定的結果。
實體按鈕內部由金屬接點接通與斷開,按下時接點可能在很短時間內反覆碰觸,MCU 讀到的結果便可能依序出現 LOW、HIGH、LOW,而不是一次乾淨地從 HIGH 變成 LOW。

Day 7 的下降緣判斷會把每次 HIGH 轉 LOW 都視為新的按下動作,因此同一次實體按壓若產生數次電位跳動,toggle_led() 也可能被呼叫數次,最後看到的 LED 狀態便不一定符合預期。
這種機械接點在短時間內反覆變化的現象稱為接點彈跳,程式需要忽略按下後的一小段不穩定時間,避免將同一次操作誤認為多次事件。
本篇採用最簡單的作法,在確認下降緣並完成 LED 切換後,加入 delay(50);,讓 MCU 暫停 50 毫秒,再繼續執行後面的程式。
if (lastBtn == HIGH && btn == LOW) {
toggle_led();
delay(50);
}
delay(50) 要放在條件區塊內, toggle_led() 之後,只有偵測到按下事件並且程式先完成這次有效操作後才暫停,以暫時忽略接點後續跳動,按鈕放開或維持原狀時不需要等待。
將 sketch/sketch.ino 更新如下,Day 7 的程式只增加一行 delay(50);。
const int LED_PIN = 2;
const int BTN_PIN = 9;
bool ledState = false;
bool lastBtn = HIGH;
void toggle_led() {
ledState = !ledState;
digitalWrite(LED_PIN, ledState ? HIGH : LOW);
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BTN_PIN, INPUT_PULLUP);
digitalWrite(LED_PIN, LOW);
}
void loop() {
bool btn = digitalRead(BTN_PIN);
if (lastBtn == HIGH && btn == LOW) {
toggle_led();
delay(50);
}
lastBtn = btn;
}
程式先讀取 D9,再比較 lastBtn 與 btn,只有 HIGH 轉 LOW 時切換 LED,完成後暫停 50 ms,最後將目前讀值保存到 lastBtn,下一圈繼續判斷新的狀態變化。
按下 App Lab 的 Run,部署完成後連續操作按鈕,每次都要完整按下再放開,LED 應依序在亮與滅之間切換,按住不放仍只會觸發一次,快速短按時的多次觸發也應比 Day 7 明顯減少。
可將 delay(50); 暫時註解後測試十次,再恢復程式測試十次,比較兩個版本是否出現按一下卻切換兩次的情況,完成比較後保留有 50 ms 延遲的 Day 8 版本。
測試時每次按壓速度不必完全一致,可混合快速輕按、正常按壓與稍長按住,若三種操作都能穩定維持一次切換,代表這個簡易去彈跳已符合目前原型需求。
若 LED 完全沒有變化,先確認 delay(50) 是否放在下降緣條件內,而不是誤刪 toggle_led(),若按鈕反應仍偶爾不穩定,則重新檢查 D9、GND、按鈕方向與接點是否鬆動。
delay(50) 容易理解,也足以完成目前單一按鈕的原型,但暫停期間 MCU 不會執行 loop() 中的其他工作,未來若同時處理多個輸入、通訊或需要快速反應的任務,delay() 這種阻塞式方法便可能影響其他功能,之後若篇幅容許,筆者會再介紹非阻塞式方法。
delay(50)。現在按鈕操作已經穩定,但現實世界是現場與遠端都會控制同一號誌燈,為了避免狀態不同步,接下來我們需要統一管理燈號狀態。
下一篇|狀態不能有兩個答案