不要用 delay
讓板子同時做兩件事。 · ⏱ 約 35 分鐘
你會學到三件事
delay 的真正代價:那段時間什麼都做不了
millis() 是「開機到現在幾毫秒」
- 「看時間,而不是等時間」——這是嵌入式最重要的一個轉念
開始之前
想同時做兩件事:一顆燈每 1 秒閃、另一顆每 0.3 秒閃。
用 delay 寫寫看——你會發現寫不出來。
那不是你不夠聰明,是 delay 這個工具做不到。
一、delay 的代價
delay(1000);
這一行的意思不是「一秒後做下一件事」,
而是「接下來一秒,這塊板子什麼都不做」。
按鈕按了?沒讀到。感測器變了?沒看到。序列埠來了資料?漏掉了。
delay 不是「等一下」,是「停機一下」。
二、millis()
unsigned long t = millis();
millis() 回傳「從開機到現在過了幾毫秒」。它一直在跑,不需要你等。
⚠️ 型態一定要 unsigned long。用 int 的話 32 秒就溢位了。
三、看時間,而不是等時間
unsigned long last = 0;
void loop() {
if (millis() - last >= 1000) {
last = millis();
// ← 這裡放「每秒要做一次」的事
}
}
讀法:「現在的時間減掉上次做的時間,如果超過 1000 毫秒,就做一次。」
loop 還是每秒跑幾萬次——而那一段每秒只進去一次。
其餘幾萬次它是空跑的,所以它有空做別的事。
用 delay ███████等待███████ 做一下 ███████等待███████
用 millis ·······················做一下·····················
↑ 這些點都是空的,可以塞別的事
四、狀態
要讓燈交替,得記住「現在是亮還是暗」:
int state = LOW;
...
if (state == LOW) state = HIGH;
else state = LOW;
digitalWrite(LED_BUILTIN, state);
⚠️ state 和 last 都要宣告在 loop 外面。
宣告在裡面的話,每次 loop 重來它們都會重置——
而 loop 一秒重來幾萬次,等於它們永遠不會改變。
這是「狀態」這個概念第一次真的有必要:
有些東西必須活得比一次 loop 更久。
五、⚠️ 為什麼是 millis() - last 而不是 millis() > last + 1000
millis() 大約 49 天會轉回 0(溢位)。
寫成 millis() - last 的話,即使剛好轉回去,減法的結果還是對的
(因為 unsigned 的減法會繞回來)。寫成 > 那種就會卡死。
這種寫法叫「慣用法」——它看起來只是換個寫法,而它是對的那一個。
完成的樣子
unsigned long last = 0;
int state = LOW;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
if (millis() - last >= 1000) {
last = millis();
if (state == LOW) state = HIGH;
else state = LOW;
digitalWrite(LED_BUILTIN, state);
}
}
換你了
加第二顆燈,讓它每 300 毫秒閃一次——用另一組 last2 / state2。
兩顆燈會各閃各的,互不干擾。 用 delay 做不到這件事。
再回頭做第 4 課那個「按一下切換」——現在你有工具了。
這一課你做了什麼
- 你知道
delay 期間板子什麼都做不了
- 你用
millis() 改成「看時間」而不是「等時間」
- 你第一次為了跨越
loop 而把變數宣告在外面
如果卡住了
| 你看到 |
多半是因為 |
| 燈完全不閃 |
last / state 宣告在 loop 裡面了 |
| 大約 32 秒後行為亂掉 |
millis() 存進 int 了。要 unsigned long |
| 兩顆燈互相干擾 |
共用了同一個 last |
| 還是很卡 |
某個地方還留著 delay |
在編輯器打開這一課 →