我們已讓按鈕操作變得穩定,但當現場按鈕與遠端系統都能控制同一盞燈時,新的問題也隨之出現:燈號狀態到底該聽誰的?今天要讓 MCU 來統一管理燈號狀態。
ledState目前程式已經有 bool ledState = false;,也有負責反轉狀態與更新輸出的 toggle_led(),但 Day 8 的 setup() 仍直接使用 digitalWrite(LED_PIN, LOW); 初始化硬體。
這兩行目前剛好都代表關閉,所以執行結果沒有衝突,但它們仍是兩個獨立設定,若日後將 ledState 初始值改成 true,卻忘記同步修改 digitalWrite(),程式會認為 LED 應該亮起,實體輸出卻仍維持 LOW。
單一狀態來源的意思,是程式只保留一個可判定真實狀態的位置,其他輸出與顯示都依它更新,本專案將這項責任交給 MCU 的 ledState。
Day 9 將 setup() 中寫死的 LOW 改為三元運算子,讓開機輸出直接依照 ledState 初始化。
digitalWrite(LED_PIN, ledState ? HIGH : LOW);
當 ledState 是 false,D2 輸出 LOW,LED 維持關閉,若未來初始值改成 true,同一行程式便會輸出 HIGH,不需要再到 setup() 修改另一個固定值。
這項改動雖然只有一行,卻讓初始化與後續切換遵守相同規則,D2 的輸出不再由不同位置各自決定。
按鈕只負責產生「切換一次」的事件,下降緣成立後呼叫 toggle_led(),它不另外保存自己的 LED 狀態,也不直接決定 HIGH 或 LOW。
void toggle_led() {
ledState = !ledState;
digitalWrite(LED_PIN, ledState ? HIGH : LOW);
}
toggle_led() 先反轉 ledState,再依新狀態更新 D2,未來即使增加其他操作來源,也應呼叫相同函式,不要繞過 ledState 直接控制腳位。
這樣設計後,按鈕、未來的 Python 命令與網頁操作都只提出事件,真正的狀態變更與硬體輸出仍由 MCU 統一處理。
檢查現有程式時可以採用一條固定規則,除了 setup() 依 ledState 初始化輸出,任何需要改變 LED 的位置都不直接呼叫 digitalWrite(),而是統一呼叫 toggle_led(),新增功能時便不會繞過狀態管理。
這項規則也讓除錯有明確順序,先確認 ledState 的內容,再查看 D2 的實際輸出,若兩者不一致,問題會集中在初始化或 toggle_led(),不必逐一搜尋每個按鈕與命令來源。
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, ledState ? HIGH : LOW);
}
void loop() {
bool btn = digitalRead(BTN_PIN);
if (lastBtn == HIGH && btn == LOW) {
toggle_led();
delay(50);
}
lastBtn = btn;
}
相較 Day 8,程式只修改 setup() 的初始化輸出,按鈕讀取、下降緣、去彈跳與切換函式都維持原有版本。
按下 App Lab 的 Run,因為 ledState 初始值是 false,LED 開機後應保持關閉,每按一次 D9,toggle_led() 會反轉狀態,LED 依序在亮與滅之間切換。
可暫時將初始值改成 true 後重新部署,LED 應在開機後直接亮起,這能確認初始化輸出確實讀取 ledState,測試完成後再恢復 false。
連續操作按鈕時,程式中的 ledState 與實體 LED 應始終一致,按鈕只觸發事件,不會建立另一份亮滅記錄。
測試可依序確認開機關燈、第一次按下亮燈、第二次按下關燈,再重新啟動 App,LED 應回到 ledState 設定的初始狀態,四個結果都正確才表示狀態責任已整理完成。
ledState 統一決定開機輸出。toggle_led() 作為唯一切換入口。下一篇會整合並測試目前的 LED、按鈕、狀態與去彈跳,完成可離線運作的現場控制器 1.0。
下一篇|階段驗收 Edge Box 1.0