現在號誌燈的狀態已經有了統一的管理方式,但真正的控制箱不能每次都靠修改程式來操作,今天要加上一顆實體按鈕,讓現場人員只要動手一按,就能直接向 MCU 下達指令。
本篇在原有 D2 LED 之外加入一顆按鈕,按鈕一端接 D9,另一端接 GND,程式使用 UNO Q D9腳位內建的上拉電阻功能,因此不需要另外加入一顆電阻。
四腳按鈕的同側兩腳通常彼此相通,接線時要讓 D9 與 GND 位於開關兩側,按下後兩端才會導通,若程式執行後輸入一直沒有變化,應先檢查按鈕方向與麵包板位置。
程式開頭新增 BTN_PIN,將現場操作鍵固定對應到 D9。
const int LED_PIN = 2;
const int BTN_PIN = 9;
使用名稱表示腳位後,LED_PIN 負責輸出,BTN_PIN 負責輸入,程式不必在不同位置反覆出現數字 2 與 9,也能直接看出兩個腳位的用途。
在 setup() 中加入 pinMode(BTN_PIN, INPUT_PULLUP);,將 D9 設為數位輸入,並啟用 MCU 內部的上拉電阻。
pinMode(BTN_PIN, INPUT_PULLUP);
注意:啟用 INPUT_PULLUP 後,內部上拉讓輸入腳位在按鈕放開時維持明確的 HIGH,避免腳位懸空而出現不穩定讀值,按鈕放開時 D9 會讀到 HIGH,按下後 D9 與 GND 導通,讀值會變成 LOW,程式判斷按下狀態時要使用 LOW,不能沿用「按下等於 HIGH」的直覺。
數位輸入只能分辨 HIGH 與 LOW,無法直接知道按鈕名稱或使用者意圖,程式必須先依接線方式解讀讀值,本篇將 LOW 定義為現場人員正在按下操作鍵,HIGH 則代表按鈕已放開,後續條件判斷都沿用這個規則。
loop() 會持續執行,程式每次先用 digitalRead(BTN_PIN) 讀取 D9,再將結果存入布林變數 btn。
bool btn = digitalRead(BTN_PIN);
if (btn == LOW) {
toggle_led();
}
當 btn 等於 LOW,代表按鈕目前處於按下狀態,程式便呼叫前一天完成的 toggle_led(),由同一個函式反轉 ledState 並更新 D2 LED。
按鈕程式沒有直接寫 HIGH 或 LOW 到 LED,也沒有另外建立第二套切換流程,現場操作只負責提出切換要求,真正的狀態管理仍由 toggle_led() 完成。
將 sketch/sketch.ino 更新為以下內容,其他檔案保持 Day 5 的版本。
const int LED_PIN = 2;
const int BTN_PIN = 9;
bool ledState = false;
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 (btn == LOW) {
toggle_led();
}
}
啟動時 LED 維持關閉,按下後 btn 變成 LOW,程式開始呼叫切換函式。
按下 App Lab 的 Run 並等待部署完成,此時 LED 應保持原狀,短按按鈕時可看到 LED 改變,這代表 D9 輸入已進入 MCU 的號誌控制流程。
測試時可先上電不碰按鈕,確認 LED 維持關閉,再快速按下並放開,觀察 LED 是否出現變化,最後按住按鈕一秒,確認 LED 會快速切換,三個結果分別驗證放開讀到 HIGH、按下讀到 LOW,以及條件在按住期間會持續成立。
按住按鈕不放時,loop() 會在極短時間內重複讀到 LOW,每次都呼叫 toggle_led(),LED 因此快速切換,這是因為程式目前只知道「按鈕正在被按住」,還無法辨認「剛剛按下的那一刻」,後面我們會比較前一次與目前讀值,讓一次按下只切換一次。
若按下後完全沒有反應,依序檢查 D9 與 GND 接線、按鈕方向、INPUT_PULLUP 是否正確,以及條件是否寫成 btn == LOW。
INPUT_PULLUP 穩定按鈕讀值。digitalRead() 取得 HIGH 與 LOW。toggle_led()。現在按鈕已能切換號誌燈,但是若一直按住則燈號就會持續切換跳動,下一步要讓程式只在按下瞬間反應一次。
下一篇|按一下才切換,別讓燈一直跳