Semorphe

不要用 delay

讓板子同時做兩件事。 · ⏱ 約 35 分鐘

你會學到三件事

  1. delay 的真正代價:那段時間什麼都做不了
  2. millis() 是「開機到現在幾毫秒」
  3. 「看時間,而不是等時間」,這是嵌入式最重要的一個轉念

開始之前

想同時做兩件事:一顆燈每 1 秒閃、另一顆每 0.3 秒閃。

用 delay 寫寫看,你會發現寫不出來。

那不是你不夠聰明,是 delay 這個工具做不到。

🟢 先把編輯器打開,跟著下面一起做:跟著做

一、delay 的代價

delay 這一行的期間,這支程式什麼都不能做:

🔎 這一段是試一下——打了看結果就好,不要留在程式裡。

1delay(1000);
1000等待毫秒1

這一行的意思不是「一秒後做下一件事」, 而是「接下來一秒,這塊板子什麼都不做」。

按鈕按了?沒讀到。感測器變了?沒看到。序列埠來了資料?漏掉了。

delay 不是「等一下」,是「停機一下」。

二、millis()

millis() 回的是「開機到現在幾毫秒」:

🔎 這一段是試一下——打了看結果就好,不要留在程式裡。

1unsigned long t = millis();
開機到現在的毫秒數宣告unsigned long變數t=1

millis() 回傳「從開機到現在過了幾毫秒」。它一直在跑,不需要你等。

⚠️ 型態一定要 unsigned long。用 int 的話 32 秒就溢位了。

積木盤上它寫著「開機到現在的毫秒數」; 而下一節那行 last = millis(); 是「把 … 設成 …」。

三、看時間,而不是等時間

照著打上這一段——它一樣每秒閃一次,而中間沒有停下來:

1unsigned long last = 0;2 3void loop() {4    if (millis() - last >= 1000) {5        last = millis();6        // ← 這裡放「每秒要做一次」的事7    }8}
0開機到現在的毫秒數變數last-1000≥變數last開機到現在的毫秒數備註:← 這裡放「每秒要做一次」的事把設成如果則定義函式 回傳型別void(無回傳)loop宣告unsigned long變數last=13456

讀法:「現在的時間減掉上次做的時間,如果超過 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 的減法會繞回來)。寫成 > 那種就會卡死。

這種寫法叫「慣用法」:它看起來只是換個寫法,而它是對的那一個。

完成的樣子

這一課的參考做法——程式碼每一行的號碼,就印在對應的那塊積木上。

1unsigned long last = 0;2int state = LOW;3 4void setup() {5    pinMode(LED_BUILTIN, OUTPUT);6}7 8void loop() {9    if (millis() - last >= 1000) {10        last = millis();11        if (state == LOW) state = HIGH;12        else state = LOW;13        digitalWrite(LED_BUILTIN, state);14    }15}
0LOW變數LED_BUILTINOUTPUT設定腳位為開機到現在的毫秒數變數last-1000≥變數last開機到現在的毫秒數變數stateLOW=變數stateHIGH把設成變數stateLOW把設成變數LED_BUILTIN變數state把腳位設成如果則否則把設成如果則定義函式 回傳型別void(無回傳)loop定義函式 回傳型別void(無回傳)setup宣告int(整數)變數state=宣告unsigned long變數last=12458910111213

換你了

加第二顆燈,讓它每 300 毫秒閃一次,用另一組 last2 / state2。

兩顆燈會各閃各的,互不干擾。用 delay 做不到這件事。

再回頭做第 4 課那個「按一下切換」——現在你有工具了。

到編輯器做這一題:練習:加第二顆燈,讓兩顆各閃各的

做一個

三顆燈,週期分別是 0.3 秒、0.5 秒、0.7 秒,各閃各的,互不干擾。

🔴 用 delay 做不到這一題,先試一次,體會一下卡在哪裡。 ⚠️ 每一顆燈要記住自己上次換的時間,三顆燈就是三個變數。 做完想一下:如果是十顆燈呢?

到編輯器做這一題:做一個:三顆燈,三個節奏

這一課你做了什麼

如果卡住了

你看到 多半是因為
燈完全不閃 last / state 宣告在 loop 裡面了
大約 32 秒後行為亂掉 millis() 存進 int 了。要 unsigned long
兩顆燈互相干擾 共用了同一個 last
還是很卡 某個地方還留著 delay
在編輯器打開這一課 →