{{ 'fb_in_app_browser_popup.desc' | translate }} {{ 'fb_in_app_browser_popup.copy_link' | translate }}
{{ 'in_app_browser_popup.desc' | translate }}
歡迎光臨瑪可希維,本站商品皆為台灣現貨、含稅可打統編
在上一篇教學中,我們成功讓 Arduino 讀取了旋鈕的變化,並精準控制了 LED 的亮度。現在,你的專案已經具備了基礎的「感知」與「表達」能力,但它還缺乏一個成為智慧型設備的關鍵要素——獨立思考的邏輯。
如果你希望專案能更聰明一點,例如:「當旋鈕轉過一半 且 感測到人體時,才自動亮起警告燈」;或者是「讓多顆 LED 有條理地自動輪播」,你就必須學會如何對處理器下達精確的決策指令。
本篇教學將帶你跨越初學者的門檻,這是一份從基礎到專業的完整核心指南。我們將帶你掌握:
int、float、bool 等變數容器,學會如何精準存放數據而不失真。setup() 與 loop() 的生命週期,理解 Arduino 的執行順序。if/else 判斷分支,以及 for 與 while 迴圈的自動化控制技巧。最後,我們還要開啟「進階工具箱」!除了教你用 switch...case 與 const 寫出更優雅、安全的程式碼外,更要挑戰新手進階的最大門檻——學會利用 millis() 實現「非阻塞」的時間管理。讓你不再受限於會讓程式卡死的 delay(),真正學會讓 Arduino 同時處理多項任務的多工邏輯!
從線性到多工:為什麼你的 Arduino 需要「更聰明」的時間管理?
Arduino 變數教學:掌握變數宣告、資料型態與數據存放規範
Arduino 結構教學:解析 setup() 與 loop() 的生命週期與執行時機
在之前的實作中,我們將電位器讀取到的數值,直接傳遞給 LED 作為亮度參考。這種程式邏輯是線性的「讀取、轉換、輸出」在任務單一的情況下,能夠良好的運作,但當專案需求變得複
雜時,線性邏輯就會面臨瓶頸。要讓專案展現智慧,我們需要讓程式學會如何「安排任務」。想像一個具備多項任務的專案,你可能需要:
每 10 毫秒偵測⼀次按鈕狀態。
每 100 毫秒更新⼀次感測器數據。
每 500 毫秒切換⼀次狀態指⽰燈。
而「安排任務」與「時間管理」其實是同一件事。 當你希望 Arduino 在「某個時間點」檢查感測器、在「另一段時間」閃爍燈光,就涉及了先後順序的安排。為了實現真正的多工處理(Multitasking),我們必須學會如何更聰明地分配處理器的每一毫秒,讓它在不同任務之間快速切換。而掌握這項技術的起點,必須先釐清程式碼執行的「時間順序」,以及數據是如何被存放與記錄的。
為什麼時間管理這麼重要?因為 Arduino 的處理器一次只能執行一行指令。如果你用傳統的delay() 強制讓程式原地等待,處理器會進入完全的停滯狀態,這段時間它無法同時偵測按鈕、也無法更新其他任務、也無法反應,導致專案對外界變化失去反應。
要從「複製程式碼」進階到「獨立設計功能」,我們必須理解 Arduino 程式的三大支柱。這三者共同決定了程式如何運作,我們可以將其想像成經營一間「自動化感應商店」:
這裡「數據容器」的正式名稱為「變數(Variables)」,是記憶體中用來存放數據的空間。從感測器讀到的數據必須先存入容器,後續才能進行運算。就像商店裡的「收銀機或貨架」。當客人付錢(輸入數據)時,你需要合適的抽屜(變數)來存放,才能確保後續結帳不會出錯。
程式結構指的是指令執行的「頻率與順序」。Arduino 透過 setup() 與 loop() 定義了哪些指令只跑一次,哪些要不斷循環,構成了程式的運行時間軸。就像商店的「營業規定」。setup()是開店前的準備(如裝潢、進貨),只做一次;loop()是營業時間,店員會不斷巡邏,處理進門的客人。
邏輯控制指的是程式「做出決策」的能力。透過 if(判斷)或 for(循環)等指令,根據容器內的數值決定下一步該怎麼做。就像商店的「標準作業程序(SOP)」。例如:「如果客人體溫過
高,就發出警報」;或是「重複刷讀條碼,直到商品掃完為止」。
掌握了我們需要這三大類的程式碼概念後,我們就具備了開發專案的基本地圖。接下來我們會先看一段完整的程式碼,並從完整的程式碼結構拆解,這個範例是「如何透過類比輸入(旋鈕)來控制數位輸出(LED)」是學習 Arduino 邏輯控制最核心的實作。
專案的目標:
硬體呈現:當使用者轉動旋鈕超過中點(512)時,LED 燈會立刻點亮。 邏輯條件:當旋鈕數值低於中點(512)時,LED 燈則保持熄滅。 數據回傳:透過序列埠監控視窗,即時查看目前的旋鈕數值(0 到 1023)。 |
程式碼:
/* * 專案名稱:旋鈕感應燈控制
* 功能描述:讀取 A0 旋鈕值,⼤於 512 點亮 LED,⼩於 512 則關閉 LED */// ========================================== // 區塊一:數據容器(宣告變數與型別) // ==========================================intledPin=13;// 定義 LED 輸出腳位intsensorValue=0;// 儲存旋鈕讀取值的變數// ========================================== // 區塊二:初始化設定(setup 結構) // ==========================================voidsetup() {pinMode(ledPin,OUTPUT);// 設定腳位模式為輸出Serial.begin(9600);// 初始化序列埠通訊}// ========================================== // 區塊三:主要執行邏輯(loop 結構與控制邏輯) // ==========================================voidloop() {sensorValue=analogRead(A0);// 讀取類比輸入值 (0-1023)Serial.println(sensorValue);// 將數值顯⽰於電腦螢幕// 判斷旋鈕是否轉動超過⼀半 (512)if(sensorValue>512) {digitalWrite(ledPin,HIGH);// 輸出⾼電壓,點亮 LED}else{digitalWrite(ledPin,LOW);// 輸出低電壓,關閉 LED} }
區塊一:數據容器(定義誰是誰)
在程式的最頂端,我們先行定義了「容器」。例如 ledPin 告訴 Arduino 燈接在哪裡,而sensorValue 則是用來暫存感測器回傳的數字。就像在開店前先準備好收銀機和貨架 。
區塊二:初始化設定(定義硬體規則)
這部分位於 setup() 函數內。它的任務是告訴處理器:這根針腳是用來發信號的(OUTPUT),還是用來收信號的(INPUT)。這個動作在開機後只會執行一次 。
區塊三:主要執行邏輯(做出反應與決策)
這部分位於 loop() 函數內。它是程式的大腦與守衛,會永無止盡地循環執行。它不斷「讀取」旋鈕的數值,並透過 if/else 邏輯「判斷」是否該給 LED 通電。這就是自動化感應商店的日常運作。
首先對應到我們程式碼的最頂端 區塊一:數據容器 (Variables)
// ========================================== // 區塊一:數據容器(宣告變數與型別) // ==========================================
|
這邊要處理的是數據的存放問題。這就像在商店營業前,必須先準備好收銀機與貨架。
Arduino 每秒鐘能讀取成千上萬次的感測數據,但若這些數據沒有「家」,程式就無法進行後續的判斷與動作 。因此,我們需要變數 (Variables) 在記憶體中預留空間,就像為數據準備專屬的「收銀機抽屜」或「置物櫃」,確保資訊能被暫存並隨時取用 。
為了極大化記憶體效率並確保運算精確,我們必須為不同的數據選擇合適的「容器型別」 。這就像在現實生活中,你不會用小盒子去裝大體積的物品,也不會用裝液體的杯子去收納螺絲;在 Arduino 中,一旦選錯型別,輕則浪費寶貴的記憶體空間,重則導致數據溢位或運算失真 。
這是最常用的容器,專門用來裝「不帶小數點」 的數字。這是 Arduino 最常被使用的型別。適用於:
讀取類比訊號:例如儲存電位器讀取的 0 到 1023 數值。
指派腳位:記錄 LED 接在第幾號腳位。
計數器:記錄按鈕按了幾下,或 LED 閃爍了幾次。
程式碼範例:
|
當你的數字需要 「精確的小數點」 時,就必須使用 float。適用於:
單位換算:將讀取值換算為實際電壓(例如 3.35V)。
感測紀錄:溫度、比例等需要小數點的計算結果。
程式碼範例:
|
它是最節省空間的容器,只會存放 「對或錯 (true / false)」 兩種狀態。適用於:
狀態紀錄:LED 現在是不是亮著。
條件判定:按鍵有沒有被視為「按下」。
標記門檻:是否進入某種狀態(例如:超過門檻就設為 true)。
程式碼範例:
|
| 資料型態 | 佔用空間 | 數值範圍 / 說明 | 建議使用時機 |
| int | 2 Bytes | -32,768 到 32,767 | 最常用。儲存腳位編號、感測器讀值 (0-1023) 。 |
| float | 4 Bytes | 帶小數點的精確 數值 | 計算精確數值(如電壓 3.3V、溫度 26.5 度)。 |
| boolean | 1 Byte | true 或 false | 紀錄開關狀態(如燈亮或熄、按鈕是否按下)。 |
| byte | 1 Byte | 0 到 255 | 儲存微小正整數,或處理 8 位元的二進制原始數據 。 |
| char | 1 Byte | -128 到 127 | 儲存單個字母或符號(如 'A', '!')。 |
| unsigned int | 2 Bytes | 0 到 65,535 | 當你確定數值「絕對不會是負數」且需要稍微大一點 的範圍時。 |
| long | 4 Bytes | 約正負 21 億 | 處理極大的整數運算(如長距離計算)。 |
| unsigned long | 4 Bytes | 0 到 4,294,967,295 | 計時專用。存放 millis() 回傳的開機毫秒數 。 |
| void | 0 Byte | 無類型 | 用於宣告不需傳回值的「函數」開頭(如 void setup )。 |
請注意,變數的資料大小(Data Size)會隨微控制器(MCU)的底層架構而異。以最常用的 int 型別為例:
為什麼這很重要? 當您將高階板(如 ESP32)的程式碼移植回 Arduino 執行時,原本正常運作的大數值,非常容易因爲超出 Arduino 的 2 Bytes 限制而引發數值裝不下的「溢位(Overflow)」錯誤。因此在更換開發板時,記得要先查閱該 MCU 的資料型態規格!
延伸閱讀:為什麼存數字的程式「布林值 (bool)」會出現 true / false?
1. 電腦眼中的 True 與 False 其實也是數字
對於處理器來說,它並不認識人類語言中的「ture」或「false」,它只認識「電壓」。True :對應的是數字 1,代表訊號成立或有電壓。
False :對應的是數字 0,代表訊號不成立或無電壓。
在寫程式時使用 true 或 false,是為了讓我們人類讀起來更直覺。比起 if (isLEDOn == 1)寫,寫成 if (isLEDOn == true) 能讓我們一眼看出這是在判斷一個「狀態」,而 不是在做數學加減。
※這邊的範例程式使用了「兩個等號 ==」是比較值(Comparison)的意思,可以想像是問 Arduino:「左邊跟右邊有 沒有相等?」我們會在到下一章節詳細分享
2. 從「數值」變成了「判斷結果」
處理數字的過程會變出 bool?這通常發生在我們做 「比較」 的時候。 想像你正在讀取 電位器的數值 ( int),目前的數值是 800。當你問 Arduino:「這個數值有超過 512 嗎?」這個問題的答案就不再是一個數字,而是一個 「狀態」 :如果沒超過 :答案就是 false (0)。
如果超過了 :答案就是 true (1)。
3. 為什麼不直接用 int 存 0 和 1 就好?
雖然 int 也可以存 0 或 1,但使用 bool 有兩個專業上的優點:
節省空間:int 容器為了裝下三萬多個數字,佔用的記憶體較大;而 bool 只需要 極小的空間來記錄 0 或 1。
語意清晰:當其他開發者看到型別是 bool,會立刻知道這個變數是用來做「開關」 或「紀錄狀態」的,而不會誤把它拿來做乘除運算。
在 Arduino 這種記憶體空間極為有限的微控制器上,選擇資料型態並非「範圍越大越好」。我們 主要考量兩個核心重點:不失真與不浪費 。
1. 資料精確度(不失真):選對型別避免數據縮水
如果你用 int (整數) 去儲存感測器讀到的溫度,像是 26.5度C 會被強制切掉小數點變成 26度。 這種「自動無條件捨去」的特性會導致數據失真。因此,當你的運算涉及溫度、電壓或精密物 理數值時,請務必選用 float (浮點數) 。
2. 執行效率與空間(不浪費):省空間就是省效能
雖然 float 可以同時裝整數和小數,但它的運算速度較慢且佔用較多記憶體空間(4 Bytes) 。
小數值用小盒子:如果你的數值永遠不會超過 255(例如設定 LED 亮度 0~255),使用 byte 會 比 int 更節省空間且專業 。
效率優先:對於不需要小數點的數據(如腳位編號、0~1023 的類比值),使用 int 能讓程式運 行得更有效率。
3. 安全考量:避開「溢位(Overflow)」風險
選錯容器除了浪費空間,也可能導致程式崩潰。例如 int 的最大極限是 32,767 。如果你要計算 累計的馬達轉速或長時間的計數(預期會超過 3 萬),請務必改用 long 。若堅持用 int 存放, 一旦數值超過極限,它會突然變成負數,導致邏輯出錯。
4. 功能明確化:不需回傳可以選 void
在定義一個純粹執行動作(如閃燈或馬達轉動)、不需要回報任何數字結果的程式區塊時,開頭 請統一使用 void 。這能讓編譯器知道不需要配置回傳空間,讓程式結構更簡潔。
了解了變數型別之後,我們可以回到在範例程式中,宣告了兩個功能截然不同的變數,它們代 表了變數應用的兩大核心:
int ledPin = 13; // 定義 LED 輸出腳位
腳位定義:
用途:將數位腳位編號 13 賦予一個有意義的名字 ledPin 。
重要性:透過這種方式,如果未來你想把 LED 改接在第 9 腳,你只需要更改最頂端 的這一行,而不需要去更動下方的 setup 或 loop 程式碼 。
int sensorValue = 0; // 儲存旋鈕讀取值的變數
數據暫存:
用途:準備一個名為 sensorValue 的「空箱子」,初始值設定為 0 。
動態特性:這個變數的數值會隨著 analogRead(A0) 指令而不斷更新,反映出當下旋 鈕的物理位置
在處理完頂層的變數宣告後,程式會進入「骨架」核心,也就是範例程式的 區塊二:初始化設 定 以及 區塊三:主要執行邏輯。
// ========================================== // 區塊二:初始化設定(setup 結構) // ==========================================voidsetup() {pinMode(ledPin,OUTPUT);// 設定腳位模式為輸出Serial.begin(9600);// 初始化序列埠通訊}
// ========================================== // 區塊三:主要執行邏輯(loop 結構與控制邏輯) // ==========================================voidloop() {sensorValue=analogRead(A0);// 讀取類比輸入值 (0-1023)Serial.println(sensorValue);// 將數值顯⽰於電腦螢幕// 判斷旋鈕是否轉動超過⼀半 (512)if(sensorValue>512) {digitalWrite(ledPin,HIGH);// 輸出⾼電壓,點亮 LED}else{digitalWrite(ledPin,LOW);// 輸出低電壓,關閉 LED} }
所有的 Arduino 程式(我們稱為 Sketch)都必須包含兩個最重要的區塊:setup() 與 loop() 。這兩個函式共同定義了程式是怎麼被執行的以及指令執行的先後順序。決定任務是setup() 「執行一次」還是loop() 「永無止盡地巡邏」 。要讓 Arduino 聰明地執行任務,首先必須理 解指令執行的先後邏輯:
setup() 是程式運行的第一站,主要用於執行初始化設定。你可以把它理解為硬體的「規則定 義區」,確保 Arduino 在正式進入繁重工作前,已經清楚所有的硬體配置與通訊協議。
《執行特性》 只執行一次,當 Arduino 板子接通電源或按下重置鍵(Reset)時,setup() 區塊內的指令 會被執行 1 次,執行完畢後就不再回頭。因為硬體規則只需要交代一次,處理器就會記住, 這也是為什麼不需要將這些設定放進不斷重複的 loop() 裡。 |
《主要任務》
|
當 setup() 跑完最後一行,Arduino 就會進入 loop() 並開始不斷重複地執行。它會從第一行 執行到最後一行,然後立刻跳回第一行重新開始,直到電源切斷為止。你想要程式持續觀察的 數據或反覆執行的動作,都要寫在這裡,可以把 loop() 想像成一個不斷繞圈巡邏的過程。
《執行特性》 在loop()部分會進行無限重複執行。處理器會由上而下掃描區塊內的程式碼,抵達末端後立 刻跳回第一行重新開始。 |
《主要任務》
|
如果你誤把只需要做一次的設定(如 pinMode)放進 loop(),Arduino 每秒鐘會重新定義腳 位上千次,這不僅浪費效能,還可能導致訊號異常。「一次性的設定給 setup,持續性執行的 給 loop」,這是寫好程式的第一個大重點。
為了實現「轉動旋鈕超過一半就亮燈」的目標,我們在兩個區塊內分別使用了核心語法:
硬體設定:在 setup() 寫下 pinMode(ledPin, OUTPUT),正式定義 13 號腳位為電的輸 出狀態。
條件決策:在 loop() 內使用 if (sensorValue > 512)。這是一個邏輯分叉路口,當 判斷結果為「真 (true)」時執行點亮指令,否則(else)執行熄滅指令。
回到我們區塊三:主要執行邏輯。
在 loop() 循環中,Arduino 的核心任務就是不斷地「讀取數據」並「做出決策」。要實現這個過程,我們需要學會如何下達判斷指令。而邏輯控制通常撰寫在就 loop() 區塊內,它是程式的「大腦」,負責根據變數的數據變化即時做出決策。
// ========================================== // 區塊三:主要執行邏輯(loop 結構與控制邏輯) // ==========================================voidloop() {sensorValue=analogRead(A0);// 讀取類比輸入值 (0-1023)Serial.println(sensorValue);// 將數值顯⽰於電腦螢幕// 判斷旋鈕是否轉動超過⼀半 (512)if(sensorValue>512) {digitalWrite(ledPin,HIGH);// 輸出⾼電壓,點亮 LED}else{digitalWrite(ledPin,LOW);// 輸出低電壓,關閉 LED} }
if/else是最常見的「分叉路口」,用於二選一的判斷。我們可以把它想像成感應式路燈的運作邏輯:「如果」天黑了就開燈,「否則」就關燈。
if (條件):當條件成立(為 true)時,執行 {} 大括號號內的動作。
else:當條件不成立(為 false)時,執行另一個 {}大括號中的替代動作。
// 【判斷層】:詢問目前的數據狀態 if (條件判斷) { //這裡是大括號區塊 A
// 當括號內的條件為「真 (true)」時,電腦會執行這裡。 } else { // 這裡是大括號區塊 B
// 當條件「不成立 (false)」時,電腦會改跑這裡。 }
1.條件語意要完整: 括號內請盡量寫出完整的比較邏輯,例如 val > 500。雖然單獨寫 if (val) 電腦也能讀懂(數值非 0 即為真),但這通常不是新手原本想表達的判斷條件,反而會造成邏輯上的失誤。
2.else 是選配:你可以只寫 if 而不寫 else 。代表「只有達標才做事,沒達標就當作沒看 到」。
3.區塊的界限:每個路徑的指令都要用 { } 包起來。後面我們會提到「括號」如果錯誤會編譯 失敗,漏了一個會出現錯誤。
特別提醒:
常見錯誤if (sensorValue > 512);這段程式碼多了一個分號!
分號代表指令結束。如果在這裡加了分號,電腦會以為「如果條件成立,就什麼都不做」,不 管條件有沒有成立,都會執行後面的亮燈指令。在最後我們會補充語法的檢查清單
// 【判斷層】:詢問目前的數據狀態if(sensorValue>512) {// 【執行層 A】:當「大於 512」這件事是真的 (True)digitalWrite(ledPin,HIGH);// 執行動作:點亮 LED}else{// 【執行層 B】:當「大於 512」不成立,也就是剩下的所有狀況 (False)digitalWrite(ledPin,LOW);// 執行動作:關閉 LED}
這段程式碼展示了如何利用if / else 邏輯作為決策中心,並以我們宣告的變數作為判斷依據與操 作對象,透過讀取感測器的數值(sensorValue),來控制 LED 燈的開關狀態。
條件判斷if (sensorValue > 512): 這是決策中心的小括號。它會去讀取變數 sensorValue 裡的數值。如果數值大於 512,它就像是通過了安檢門,程式會進入第一 個大括號 { }。
大括號區塊 { } (動作執行): 這是指令的封閉空間。if 的大括號決定了「成功後要做什 麼」,else 的大括號決定了「不成功要做什麼」。這確保了電腦在同一時間只會執行其中 一個動作。
邏輯的連動: 當 sensorValue 發生變化時,這個判斷式會不斷重新評估。它是連動「感 測器數據」與「輸出結果(亮或滅)」的關鍵橋樑。
當你需要重複執行某個動作多次時,for 迴圈就是你的最佳幫手。它能幫你省下重複撰寫相同 代碼的麻煩,讓程式變得精簡。你可以把它想像成一個自動計數器,專門用於「已知要重複幾 次」的狀況。用跑操場來比喻就像是: 「從第 1 圈開始,只要還沒跑滿 10 圈就繼續跑,每跑完 一圈,計數器就記錄加 1。」
這台「自動計數器」要驅動這台自動化計數器,全靠小括號內定義的這三項 「運作準則」,這三 個運作準則掌控了迴圈從起點到終點的完整執行過程:
for (初始化計數; 執行條件; 每次結束後的動作) { // 這裡是大括號區塊:電腦會反覆執行這裡的指令。 }
1.初始化計數(起點):例如 int i = 0
這是在程式剛啟動時執行的唯一一次動作,目的是準備一 個名為 i 的臨時變數(計帳員),並告訴電腦:「我們要從 0 開始數囉!」這就像你站在操場起 跑線上,手上拿著一個計數器,並在出發前將數字歸零(0),這件事你只會在起跑前做一次。
2.執行條件(終點/ 檢查哨)):例如 i < 5
這是每次重複前的「檢查哨」。在每跑新的一圈之前, 電腦都會詢問:「現在 i 裡的數字還小於 5 嗎?」只要結果為「是 (True)」,它就會繼續執行; 一旦達標(False),機器就立刻停工。這就像你每次起跑前,都會抬頭看一眼告示牌確認:「現 在跑滿 5 圈了嗎?」如果還沒到,你才會邁開步子往前跑。
3.每次結束後的動作(步進 / 紀錄):例如 i++ 這
是「做完事後的紀錄」。每當大括號 { } 裡的任務完整完成一遍後,電腦就會對著變數 i 說:「跑完一圈了,數字加 1。」這就像你每跑完一圈、踩過終點線時,手就會用力按一下計數器,讓紀錄從 0 變成 1,確保進度被正確累加,直到計數器跳到 5 為止。
當程式讀到 for 這一行時,會遵守以下路徑執行:

| STEP 1 | 初始化計數 | 僅限剛開始時 | 設定起點(例如 i = 0 ),只做這一次。 |
| STEP 2 | 執行條件 | 每圈「開始前」 | 迴圈條件(檢查哨):確認是否符合繼續執行的條件。 |
| STEP 3 | 大括號任務 | 條件成立時 | 進入 { } 執行閃爍 LED 或其他主要任務。 |
| STEP 4 | 每次結束後的動作 | 每圈「結束後」 | 事後紀錄:將 i 數值加 1( i++ )。 |
| STEP 5 | 循環回歸 | 完成 Step 4 後 | 回到 Step 2 再次檢查條件,直到條件不成立為止。 |
標點符號的規定: 括號內的三個零件必須用 分號 ; 隔開。這就像是三段獨立的指令,告 訴電腦:從哪開始、檢查什麼、最後做什麼。
區塊的界限: 所有需要被重複執行的內容,都必須用 { } 包起來。如果漏了大括號,電 腦只會重複執行緊跟在 for 後面的「第一行」指令。
特別提醒:常見錯誤
for (int i = 0; i < 5; i++);同樣的,這裡絕對不能加分號!如果在括號後加 了分號,電腦會以為這台計數器只是在原地「空轉」,轉完 5 次後才執行後面的動作,導致你 的自動化閃爍只會發生一次。
由於我們原先的感測器範例是「即時反應」,為了示範 for 迴圈的自動化能力,我們撰寫一段 新指令:當滿足條件(例如旋鈕轉過半)時,我們不只是要燈亮,而是要執行一段自動化的動 作。讓 LED 連續快閃 5 次,展現 for 迴圈簡化重複程式碼的能力。
// 實戰範例:讓 LED 連續閃爍 5 次for(inti=0;i<5;i++) {digitalWrite(ledPin,HIGH);// 點亮 LEDdelay(200);// 等待 0.2 秒digitalWrite(ledPin,LOW);// 熄滅 LEDdelay(200);// 等待 0.2 秒}
這段程式碼將「計數邏輯」與「硬體動作」緊密結合,運作過程可分為以下三個核心:
自動化計數中心 for (int i = 0; i < 5; i++):
這是這段指令的「大腦」。它負責在執行前讀取 變數 i 的初始值(0),並設定停止的邊界(小於 5)。這就像是設置了一個自動化的安檢 門,只有當 i 的數值符合條件時,才允許程式進入下一個區塊。
計數變數 i (臨時紀錄變數):
這是一個僅在此迴圈內生效的「臨時記帳員」。它的角色不是儲存感測 數據,而是專門負責 記錄次數,讓程式知道目前的執行進度。
條件界限 i < 5 (迴圈條件 / 終止決策):
這是控制自動化何時結束的判斷依據。當變數 i 累積到 5 時,此條件判斷結果轉為「假 (False)」,程式會立即跳出這個區塊,將控制權交還給 loop() 的下一 行指令。
大括號區塊 { } (重複任務):
這是指令的封閉執行空間。for 後方的大括號決定了「哪些 動作需要被重複」。在這裡,它包住了 LED 的亮與滅。只要計數尚未達標,電腦就會在這 個空間內不斷循環,確保每次閃爍的節奏(delay)完全一致。
邏輯的自動連動:
這段程式碼體現了變數狀態與硬體動作的連動。當任務每執行完一圈, 變數 i 就會自動更新(加 1),這個判斷式會即時重新評估。它是連動「執行次數」與 「輸出結果(閃爍次數)」的關鍵橋樑。
如果 for 迴圈是已知次數的「自動計數器」,那麼 while 迴圈就是一個「只要條件符合,就持 續執行」的守門員。它通常用於我們不確定要重複幾次,但知道「什麼時候該停止」的狀況。
while (迴圈條件) { // 這裡是大括號區塊,只要條件成立 (True),電腦就會在這裡無限循環執行。 }
與 for 不同,while 的結構更精簡,它跳過了初始化,直接進入判斷與執行:
| 順序 | 步驟名稱 | 執行時機 | 實際動作內容 |
| Step 1 | 執行條件 | 每圈「開始前」 | 檢查哨:確認是否符合繼續執行的條件。 |
| Step 2 | 大括號任務 | 條件成立時 | 進入 { } 執行任務(例如等待某個按鈕被按下)。 |
| Step 3 | 循環回歸 | 完成任務後 | 回到 Step 1 再次評估條件。 |
在使用 while 時,必須確保大括號 { } 內的動作最終能改變條件的判斷結果。
變數的動態更新: 如果在迴圈內部沒有任何程式碼去更動判斷用的變數,條件就會永遠為 「真 (True)」,電腦會卡死在這個迴圈裡動彈不得。這在程式中稱為「無窮迴圈」,會導致 Arduino 失去反應。
在 Arduino 中,while 常用於「等待硬體準備就緒」。例如,我們希望在感測器數值還沒到達 安全範圍前,程式停在那裡不要執行後面的動作。
// 實戰範例:當數值低於 200 時,程式會「卡」在這裡不斷讀取,直到數值達標while(sensorValue<200) {sensorValue=analogRead(A0);// 在迴圈內不斷更新變數箱子的數值delay(10);// 稍微等待,確保讀取穩定}
這段程式碼展示了變數如何作為「開關」來控制程式的流程:
迴圈條件 while (sensorValue < 200) (判斷中心):
這是這段邏輯的決策點。它會去讀取變 數 sensorValue 裡的數值。只要數值小於 200,程式就像是撞到了阻車桿,被強制留在 第一個大括號 { } 內。
大括號區塊 { } (執行空間與數據更新):
這是指令的封閉區域。重點在於 sensorValue = analogRead(A0); 這行。它在迴圈內部重新讀取變數數據。這確保了每次循環時,變數 箱子裡的數字都是最新的,給了程式跳出迴圈、解除卡死狀態的機會。
邏輯的連動:
這體現了「感測器數據」與「程式執行權」的連動。當外部旋鈕被轉動,使 得 sensorValue 增加到 200 以上時,下一次的檢查哨(Step 1)就會判定為「假 (False)」,阻車桿自動升起,讓程式往下執行後續的代碼。
運算子是程式敘述中負責運算的符號 。在 Arduino 的決策流程中,它們就像是法官手中的權 杖,決定了條件最終是「真 (true/1)」還是「假 (false/0)」 。
這類運算子用來比較兩個變數之間的大小或相等關係 。
| 運算子 | 功能說明 / 定義 | 應用情境 | 程式碼範例 |
|---|---|---|---|
== | 等於:檢查左右兩邊是否相等。 | 確認目前是否處於特定的模式。 | if (mode == 1) |
!= | 不等於:檢查左右兩邊是否「不同」。 | 當感測器數據「不為零」時啟動任務。 | if (val != 0) |
> | 大於:左邊是否超過右邊。 | 旋鈕位置是否轉過中點。 | if (val > 512) |
< | 小於:左邊是否低於右邊。 | 環境亮度是否低於啟動門檻。 | if (lux < 200) |
>= | 大於或等於:含臨界值的上限判斷。 | 溫度是否達到或超過警戒值。 | if (temp >= 30) |
<= | 小於或等於:含臨界值的下限判斷。 | 水位是否低於或等於安全高度。 | if (water <= 10) |
布林運算子負責執行邏輯運算功能,包含 AND (及)、OR (或)、NOT (反相),其運算結果為 1 (true) 或 0 (false) 。當你需要同時考慮多個硬體狀態時(例如:感測器達標 且 按鈕被按下),就必須使用它們。
運算子 | 功能說明 / 定義 | 實戰情境 | 程式碼範例 |
|---|---|---|---|
|
及 (AND):所有條件都必須成立。 |
旋鈕過半且按鈕也被按下(HIGH)時才亮燈 。
|
|
| 或 (OR)。只要左右兩邊的條件中,有任何一個成立(結果為 true),整個判斷式的結果即為 true 。 | 當專案需要「多個觸發條件任一成立」就執行動作時。例如:當 按鈕 A 或 按鈕 B 任何一個被按下,都會點亮 LED 燈 。 |
|
| 反相 (NOT):將狀態「反過來」。 | 只有當按鈕「沒被按下」(邏輯為假)時執行動作 。
|
|
在撰寫程式時,我們非常頻繁地需要「更新變數自己的數值」。例如,每按一次按鈕,計數器就加 1。
為了讓程式碼更精簡、閱讀更快速,Arduino(C 語言)提供了「合成運算子(Compound Operators)」 ,將算術與設定合併 ,這也是專業開發者最愛用的縮寫寫法:
運算子 | 定義 | 應用情境 | 程式碼範例 |
|---|---|---|---|
|
加入 :將右邊的數值加進左邊的變數中。 | 累加感測器觸發的總次數,或持續增加 LED 亮度。 |
|
|
減去 :將左邊的變數扣除右邊的數值。 | 扣除遊戲的生命值,或逐漸調暗燈光。 |
|
|
遞增 (加 1) :變數數值自動加 1。 |
|
|
|
遞減 (減 1) :變數數值自動減 1。 | 製作倒數計時器,或減少剩餘的閃爍次數。 |
|
|
取餘數 :計算出除法後的「餘額」 。 | 實作「每數到 3 的倍數」就執行一次任務的規律邏輯。 |
|
當程式中有「多選一」的情況時(例如:按下按鈕 A 亮紅燈、按 B 亮綠燈、按 C 亮藍燈),如果 寫一大堆 if-else if,程式碼會變得非常冗長且難以閱讀 。這時,switch...case 就是你的「自動 分類機」,能將多重選擇簡化 。
switch(mode) {case1:digitalWrite(ledPin,HIGH);// 若 mode 等於 1,執行這裡break;case2:digitalWrite(ledPin,LOW);// 若 mode 等於 2,執行這裡break;default:Serial.println("無效模式");// 若都不是上述的數字,則執行這裡break; }
switch (運算式):這是分揀機的入口。括號內通常放入一個變數(例如 mode),而且這個變數只能是「整數」或「字元」 。
case 標籤:代表不同的檢查哨 。電腦會拿變數的值與 case 後面的數字核對,若相等就進入執行 。
break 中斷:超級重要! 執行完任務後,必須加 break 告訴電腦「跳離整個 switch 敘述」 。如果忘了寫,電腦會失控一路往下執行到結束,造成嚴重錯誤 。
default 保險機制:當所有的 case 都不符合時,就會統一走這條路 。這是一種防呆保護,避免意外的數據讓程式不知所措。
在程式中,有些數字(如腳位編號)從頭到尾都不應該被改變。為了防止自己或別人不小心更動到這些核心設定,我們使用「常數」來鎖定它們。例在設定腳位時,我們常寫 int ledPin = 13; 。但腳位一旦接好,在程式執行的過程中是「永遠不該被改變」的。如果程式寫到一半不小心把它改成 ledPin = 5;,硬體運作就會大亂。為了防止這種低級錯誤,我們有兩種「上鎖」的方法:
工具名稱 | 定義與特色 | 程式碼範例與應用 |
|---|---|---|
| 常數變數 (Constant):宣告這是一個「唯讀」的變數。一旦設定好初值,後續若有任何程式碼企圖修改它,編譯器會直接報錯攔截。
|
|
|
前置處理器替換:在程式編譯前,直接把某個識別名稱「替換成文字」。
|
|
透過這兩者,你將「腳位 13」這個無意義的數字轉化為具有意義的「標籤(變數名)」。不僅提高了程式的可讀性,更建立了一道防護牆,確保核心設定不會在執行中因程式錯誤而被意外覆寫。
關於腳位編號:越來越多專業開發者傾向使用
const int,因為它的型別檢查能帶來更高的安全性。
這是從新手進階到專業開發者最重要的門檻:學會如何處理「多工任務(Multitasking)」。要讓專案展現真正的智慧(例如:每 10 毫秒偵測按鈕狀態,同時每 500 毫秒閃爍一次警告燈),我們必須學會更聰明地分配處理器的每一毫秒。
delay() 的致命缺點在於它會讓 Arduino 進入「完全停止思考」的狀態。當你下達 delay(1000) 時,程式會原地死等 1 秒鐘。在這段暫停的時空裡,處理器無法偵測按鈕、無法更新感測器數據、對外界變化完全失去反應,等於程式直接「卡死」。
為了解決這個問題,我們必須改用 millis()。它不像 delay() 會強迫暫停,而像是一個「永不停止的碼表」,默默記錄著 Arduino 開機到現在總共經過了多少毫秒。
透過 millis(),我們可以實現真正的「非阻塞執行」。程式會一邊不斷執行 loop() 巡邏,一邊抬頭看現在的碼表時間,決定特定任務的時間是否到了,時間沒到就先去處理其他感測器,達成多工不卡頓的境界。
以下這段程式碼展示了如何讓 LED 每秒閃爍一次,同時 Arduino 仍隨時可以處理其他事情。
// 區塊一:變數設定constintledPin=13;intledState=LOW;// 記錄 LED 目前的狀態 (亮或滅)unsignedlongpreviousMillis=0;// 儲存「上一次動作」的時間點constlonginterval=1000;// 設定我們希望的間隔時間 (1000 毫秒)voidsetup() {pinMode(ledPin,OUTPUT); }voidloop() {// 1. 抬頭看碼表:取得現在的時間unsignedlongcurrentMillis=millis();// 2. 計算時間差:(現在時間 - 上次執行時間) 是否大於或等於設定的間隔?if(currentMillis-previousMillis>=interval) {// 3. 更新紀錄:把現在的時間抄下來,當作下一次的「上次時間」previousMillis=currentMillis;// 4. 執行任務:切換 LED 的狀態if(ledState==LOW) {ledState=HIGH; }else{ledState=LOW; }digitalWrite(ledPin,ledState); }// 在這裡,你可以毫無阻礙地寫入讀取按鈕或感測器的程式碼!}
在這套多工邏輯中,這三個關鍵變數聯手打造了一個強大的「自動排程系統」。我們可以把它們想像成你正在計時的過程:
interval(目標間隔): 這是你設定的「規定時間」。例如 1000 毫秒,代表你希望 LED 狀態每隔 1 秒鐘切換一次。這是一個不會變動的常數。currentMillis(現在的碼表時間): 這是你「當下抬頭看時鐘」的時間。因為程式在 loop() 裡不斷高速循環,每次跑到這行時,都會去問 millis() 現在幾點了,並抓取最新的時間。這個數字會隨著開機時間不斷飆升。previousMillis(上一次動作的時間紀錄): 這是你的「歷史紀錄本」。它專門用來記下你「上一次」點亮或熄滅 LED 時,時鐘指在幾分幾秒。
變數的功能從單純的「儲存數據」直接晉升到了「時間管理」的層次:
(currentMillis - previousMillis >= interval): 電腦不再原地死等,而是透過數學相減來檢查:「現在看時鐘的時間」 減去 「上次紀錄的時間」,有沒有大於或等於 「規定間隔」?如果達標了,就代表「時間到了,該做事了!」。previousMillis = currentMillis: 這是一切能順利循環的最關鍵步驟! 就像是按下了碼表的「計圈」按鈕。當任務執行完畢,必須立刻把剛剛看時鐘的時間(currentMillis),抄寫更新到歷史紀錄本(previousMillis)裡。這樣程式才能以這個新時間為基準,準確去計算下一個 1000 毫秒。unsigned long: 因為 millis() 回傳的毫秒數每秒增加 1000,數字非常龐大(幾十天後會高達數十億)。普通的 int 根本裝不下,所以 currentMillis 和 previousMillis 都必須使用容量極大的 unsigned long(無號長整數)來當作時間專用的容器。
檢查項目 | 符號 / 規則 | 詳細說明與實戰範例 |
|---|---|---|
指令結尾 | 分號 | 指令的句點: 代表該行敘述結束。每一條獨立的動作指令結尾都必須加上分號,漏掉分號是初學者最常遇到的編譯錯誤。 |
程式區塊 | 大括號 | 指令的管轄範圍: 定義函數(如 |
參數與條件 | 小括號 | 函式的包裝: 用於「條件判斷」與「函式呼叫」。同樣左括號必須配對右括號。例如: |
單行註解 |
| 開發者筆記: 從 |
多行註解 |
| 區塊筆記: 用於長篇說明。必須以 |
嚴格大小寫 | 大小寫 | 電腦不接受錯別字: Arduino 語法區分大小寫。例如 |
良好縮排 | 視覺層次 | 影響可讀性:
|
Q1:為什麼我的程式碼上傳失敗,下方一直跳出紅字錯誤?
A: 通常是基礎語法出了小差錯!請優先檢查這三點:
每行獨立指令的結尾是否漏了「分號 ;」 ?
括號是否有成對出現(包含大括號 {} 與小括號 ()) ?
語法大小寫是否打錯(例如將 pinMode 誤打成 Pinmode) ? 這些都是新手最常踩的語法地雷,只要對照文章結尾的「語法檢查清單」就能快速排除。
Q2:既然 int 就可以存數字,為什麼還要特別設定 float 或 bool?
A: 這是為了確保數據「不失真」與記憶體「不浪費」。如果你用整數 int 去存溫度 26.5 度,小數點會被強制捨去變成 26,導致數據失真(此時必須用 float) 。而 bool 專門用來記錄「開/關」這種只有兩種狀態的數據,它只佔極小的記憶體,能讓微控制器的運作更有效率 。
Q3:setup() 和 loop() 有什麼不一樣?程式碼可以隨便放嗎?
A: 兩者的「執行時機」完全不同。setup() 在開機時只會「執行一次」,專門用來交代硬體規則(如腳位是輸入還是輸出) 。而 loop() 則是「無限循環」,負責不斷讀取感測器和判斷邏輯 。如果誤把一次性的設定放進 loop(),處理器每秒會重新設定上千次,不但浪費效能,還可能導致訊號異常 。
Q4:為什麼我的 for 迴圈自動閃爍功能,只閃了一次就不動了?
A: 請檢查你是不是在 for 迴圈的小括號後方「多加了分號」!例如寫成 for (int i=0; i<5; i++); 。這會讓電腦誤以為這台計數器只是在原地「空轉」,轉完 5 次後才去執行後面的動作,導致大括號內的閃爍指令沒有被正確重複執行 。
Q5:我一定要學會用 millis() 嗎?繼續用 delay() 會怎樣?
A: 如果專案很簡單(例如單純讓一顆燈泡閃爍),用 delay() 沒問題。但 delay() 的致命傷是會讓處理器進入「完全停止思考」的狀態 。當你需要專案「同時」做兩件事(例如一邊閃燈、一邊隨時偵測按鈕狀態),delay() 會讓程式在等待期間完全卡死 。學會 millis() 抬頭看碼表的技巧,才能實現真正的多工不卡頓 。
走到這裡,恭喜你跨越了 Arduino 初學者最重要的一道門檻!
回顧這篇核心指南,我們從最基礎的變數容器與 setup/loop 執行時序開始,幫你建構了程式運作的基本地圖;接著,我們學會了用 if/else、for 與 while 搭配各式運算子,賦予 Arduino 獨立思考與決策的能力;最後,我們打開了進階工具箱,用 switch...case 整理複雜邏輯,用 const 保護核心硬體設定,並且真正學會了用 millis() 實現專業的非阻塞多工處理。
寫程式最迷人的地方,在於它不僅僅是背誦英文單字與標點符號,更是鍛鍊「邏輯思維」與「時間管理」的過程。當你決定捨棄 delay(),改用 millis() 抬頭看碼表的那一刻起,你的開發思維就已經從一個「只能單線等待的作業員」,正式升級為「能掌控全域的系統管理員」了。
一開始在實作多工邏輯或組合判斷時,偶爾卡關或亮紅字報錯都是非常正常的。遇到 Bug 時別氣餒,喝口水,回頭看看我們整理的邏輯拆解與表格。只要多練習幾次,這些語法就會成為你大腦的直覺反應,讓你未來在面對任何複雜專案(如自走車、智慧家庭)時,都能游刃有餘!
在進入下一個硬體模組之前,建議你先打開 Arduino IDE 動手做兩個小實驗:
millis() 範例中的 interval = 1000 改成 200,觀察 LED 閃爍節奏的改變,親身感受「變數」如何作為大腦掌控全域設定。;,或者把判斷式裡的 == 寫成單一個 =,看看下方編譯器會跳出什麼樣的紅字警告,提早適應並克服對錯誤訊息的恐懼。
在具備了「邏輯決策」與「時間管理」的程式大腦後,我們準備為你的專案裝上敏銳的「眼睛」!
下一章,我們將正式引入經典的 HC-SR04 超音波測距模組。這將是對本篇邏輯控制最完美的小驗收——我們不僅會帶你搞懂 Trig(發射)與 Echo(接收)的硬體接線原理,利用聲速公式精準計算出物理距離,更會傳授專業的「誤差排除」技巧,讓你的感測數據不再忽大忽小。
當然,我們也會大量運用你剛學會的 if/else 與組合運算子(&&, ||),帶你寫出例如「當物體靠近至 10 公分內 且 持續 3 秒鐘,就觸發自動門或警報」的真實互動專案。