Arduino 專案的故障不一定只來自程式碼,也可能與電源、接線、通訊、函式庫或硬體資源有關。本章建立一套可重複使用的 Debug 與系統化故障排除流程。
內容涵蓋 Arduino IDE 錯誤訊息、最小可重現程式、Serial 與 LED 除錯、萬用電表檢查,以及 UART、I²C、SPI 常見問題。除錯時應一次只修改一項,並以測試結果驗證假設。
學習目標
完成本章上半部後,你將能夠:
- 了解 Bug、Debug 與 Troubleshooting 的差異。
- 建立系統化的 Arduino 除錯流程。
- 分辨編譯、連結、上傳、執行與邏輯錯誤。
- 閱讀 Arduino IDE 常見錯誤訊息。
- 使用最小可重現程式縮小問題範圍。
- 使用 Serial Monitor 追蹤程式流程與變數。
- 使用 LED 建立簡單的硬體除錯訊號。
- 檢查 Arduino 電源、接地、接線與 GPIO。
- 判斷 USB 線、連接埠與驅動程式問題。
- 使用萬用電表進行基本電壓與導通檢查。
- 系統化排查 UART、I²C 與 SPI 通訊問題。
- 避免一次修改過多項目造成問題更加複雜。
什麼是 Bug?
Bug 中文常翻譯為:
程式錯誤、缺陷或故障。
Bug 不一定只存在於程式碼中。
Arduino 專案中的 Bug 可能來自:
- 程式語法。
- 程式邏輯。
- 接線錯誤。
- 電源不足。
- 感測器故障。
- 函式庫不相容。
- 開發板選擇錯誤。
- 通訊速率錯誤。
- 接觸不良。
- 記憶體不足。
- 時序問題。
- 硬體資源衝突。
因此,Arduino 除錯不能只盯著程式碼。
一個真正完整的 Arduino 系統包含:
程式
硬體
電源
接線
通訊
函式庫
開發環境
外部設備
任何一個部分出錯,都可能造成整個系統無法正常運作。
什麼是 Debug?
Debug 中文稱為:
除錯。
它不是單純「一直修改程式直到成功」。
真正的 Debug 是一個有步驟的工程流程:
觀察問題
↓
收集證據
↓
提出假設
↓
設計測試
↓
驗證假設
↓
找到根本原因
↓
修正問題
↓
重新測試
Debug 的重點不是猜答案,而是:
用可重複的測試逐步排除不可能的原因。
Troubleshooting 與 Debug 的差異
Debug 通常偏向找出程式或系統中的缺陷。
Troubleshooting 則是較廣義的故障排除。
| 名稱 | 主要範圍 |
|---|---|
| Debug | 程式、邏輯、執行流程 |
| Troubleshooting | 程式、硬體、電源、接線、通訊與環境 |
Arduino 專案通常需要同時具備:
- Debug 能力。
- Troubleshooting 能力。
例如 LED 不亮,可能是:
- 程式沒有執行。
- LED 接反。
- 電阻接錯。
- GPIO 腳位選錯。
- 麵包板沒有接通。
- Arduino 沒有供電。
- LED 已損壞。
- 程式輸出 LOW。
- Active Low 邏輯理解錯誤。
只看程式碼,無法完整解決問題。
知識卡:Arduino 除錯
| 項目 | 說明 |
|---|---|
| Bug | 程式或系統缺陷 |
| Debug | 找出並修正缺陷 |
| Troubleshooting | 系統化故障排除 |
| 主要工具 | Serial、LED、萬用電表、示波器、邏輯分析儀 |
| 核心方法 | 一次只改一個變數 |
| 重要技巧 | 最小可重現程式 |
| 最終目標 | 找到根本原因,而不只是暫時恢復運作 |
除錯不是猜測
初學者遇到問題時,常見做法是:
改一下程式
換一條線
重插模組
換函式庫
重新上傳
再次修改
如果一次更改很多項目,即使問題消失,也不知道真正原因。
這種方式可能暫時成功,但無法建立可靠經驗。
正確做法應是:
一次只改一項
記錄修改內容
重新測試
比較結果
例如 I²C 顯示器沒有畫面:
- 先確認電源。
- 再確認 GND。
- 再確認 SDA、SCL。
- 再執行 I²C Scanner。
- 再確認函式庫位址。
- 最後才修改顯示程式。
不要同時:
- 換線。
- 換位址。
- 換函式庫。
- 改程式。
- 改供電。
否則無法知道是哪一個動作真正解決問題。
建立工程師的除錯思維
良好的除錯思維包含以下幾個原則。
先確認問題是否能重現
需要確認:
- 每次都會發生嗎?
- 偶爾才發生嗎?
- 在特定動作後發生嗎?
- 只有使用某個模組時發生嗎?
- 重新開機後是否仍發生?
- 換另一塊 Arduino 是否仍發生?
若問題無法穩定重現,排查會更加困難。
描述實際現象
不要只說:
Arduino 壞了
應該描述:
程式可以編譯,但上傳時顯示 avrdude 錯誤。
或:
OLED 有供電,I²C Scanner 找到 0x3C,但畫面沒有顯示文字。
或:
LED 可以亮,但按鍵按下後沒有切換狀態。
描述越精確,越容易定位問題。
區分預期結果與實際結果
例如:
預期:
按下按鍵一次,LED 切換一次。
實際:
按下按鍵一次,LED 快速切換數次。
這樣很容易聯想到:
按鍵彈跳
收集證據
證據可以包括:
- Arduino IDE 完整錯誤訊息。
- Serial Monitor 輸出。
- 電壓量測結果。
- 接線照片。
- I²C Scanner 結果。
- 邏輯分析儀波形。
- 最小測試程式。
- 使用的開發板與函式庫版本。
不要只保留:
exit status 1
因為真正原因通常出現在它前面的訊息。
Arduino 錯誤的主要分類
Arduino 問題可先分為以下幾類:
- 編譯錯誤。
- 連結錯誤。
- 上傳錯誤。
- 執行錯誤。
- 邏輯錯誤。
- 硬體錯誤。
- 通訊錯誤。
- 時序錯誤。
- 資源衝突。
- 間歇性錯誤。
先分類,再除錯,通常比直接修改程式更有效率。
編譯錯誤
Compile Error 發生在程式尚未成功轉換成機器碼時。
常見原因包括:
- 少了分號。
- 括號不成對。
- 變數未宣告。
- 函式名稱拼錯。
- 資料型別錯誤。
- 標頭檔不存在。
- 語法不正確。
例如:
int ledPin = 13
void setup()
{
pinMode(ledPin, OUTPUT);
}
少了:
;
編譯器可能顯示:
expected ',' or ';' before 'void'
連結錯誤
Linker Error 發生在各個程式檔案已經分別編譯,但無法正確組合時。
常見原因:
- 函式有宣告但沒有實作。
- 同一變數被重複定義。
- 函式庫缺少原始碼。
- .h與.cpp參數不一致。
- 多個檔案重複定義同名函式。
例如標頭檔中有:
void startMotor();
但沒有任何地方實作:
void startMotor()
{
}
可能出現:
undefined reference to `startMotor()'
上傳錯誤
Upload Error 表示程式可能已成功編譯,但無法傳送到 Arduino。
常見原因:
- 選錯開發板。
- 選錯 COM Port。
- USB 線只有充電功能。
- 驅動程式未安裝。
- D0、D1 被外部設備干擾。
- Bootloader 問題。
- Arduino 沒有供電。
- 連接埠被其他軟體占用。
- 開發板自動重置失敗。
常見訊息:
avrdude: stk500_recv():
programmer is not responding
執行錯誤
Runtime Error 表示程式已經成功上傳,但執行時發生問題。
例如:
- 程式卡住。
- 無限迴圈。
- 陣列越界。
- SRAM 不足。
- Watchdog 重啟。
- 除以零。
- 指標錯誤。
- 程式反覆重新啟動。
Arduino 通常不像電腦程式一樣顯示清楚的 Runtime Error 視窗。
它可能只表現為:
- 沒反應。
- 隨機重啟。
- LED 異常。
- Serial 輸出停止。
- 數值突然變亂。
邏輯錯誤
Logic Error 是程式可以正常編譯、上傳與執行,但結果不符合預期。
例如:
if(sensorValue < 500)
{
alarmOn();
}
實際需求卻是:
sensorValue > 500
這種程式不會產生編譯錯誤,但控制邏輯是錯的。
其他例子:
- 計算公式錯誤。
- 判斷方向相反。
- 單位換算錯誤。
- 陣列索引順序錯誤。
- PWM 範圍錯誤。
- Active Low 當成 Active High。
- 整數除法造成小數消失。
硬體錯誤
硬體錯誤包括:
- LED 接反。
- GND 未共地。
- 電源電壓不正確。
- 感測器接腳錯誤。
- 杜邦線斷裂。
- 麵包板接觸不良。
- 模組損壞。
- Arduino GPIO 損壞。
- 電流超過腳位能力。
- 馬達雜訊造成重啟。
硬體錯誤有時會讓程式看起來像有 Bug。
通訊錯誤
通訊錯誤常見於:
- UART。
- I²C。
- SPI。
- USB。
- Ethernet。
- Wi-Fi。
常見原因:
- Baud Rate 不一致。
- TX、RX 沒有交叉。
- 沒有共地。
- I²C 位址錯誤。
- SPI CS 腳位錯誤。
- 電壓邏輯不相容。
- 線路過長。
- 上拉電阻不正確。
- 函式庫模式不一致。
時序錯誤
時序錯誤通常與時間有關,例如:
- 感測器尚未準備好就讀取。
- 啟動後等待時間不足。
- ISR 執行太久。
- delay()阻塞其他工作。
- Serial 輸出改變程式時序。
- 讀取速度超過感測器能力。
- CS 切換過快。
- 按鍵彈跳。
這類問題可能只在某些情況發生,因此較難重現。
資源衝突
Arduino Uno 的硬體資源有限。
衝突可能來自:
- Servo 與 Timer1。
- tone()與 Timer2。
- 自訂 Timer 與 PWM。
- 多個 SPI 裝置共用 CS。
- I²C 位址相同。
- SoftwareSerial 與中斷。
- D0、D1 同時接模組與 USB。
程式可以編譯,不代表所有函式庫能同時正常運作。
間歇性錯誤
Intermittent Error 是偶爾發生、難以穩定重現的問題。
常見原因:
- 接觸不良。
- 電源瞬間下降。
- 馬達雜訊。
- SRAM 不足。
- 未初始化變數。
- 中斷競爭。
- 過熱。
- USB 線品質不佳。
- 長時間運作後millis()溢位。
- 動態記憶體碎片。
間歇性問題通常需要:
- 長時間記錄。
- 錯誤計數器。
- 電壓監測。
- 事件時間戳。
- Watchdog 記錄。
- 邏輯分析儀或示波器。
如何閱讀 Arduino IDE 錯誤訊息?
Arduino IDE 的錯誤輸出通常包含很多行。
初學者常只看到最後一行:
exit status 1
但這一行只表示:
編譯或上傳失敗。
真正原因通常在前面。
閱讀順序應是:
- 找到第一個明確 Error。
- 查看檔案名稱。
- 查看行號與欄位。
- 閱讀錯誤文字。
- 再查看後續錯誤是否只是連鎖反應。
第一個錯誤通常最重要
例如:
sketch.ino:12:5:
error: 'sensorValue' was not declared in this scope
sketch.ino:13:9:
error: expected ';' before 'Serial'
exit status 1
第一個真正錯誤是:
sensorValue 未宣告
後面的錯誤可能只是因為編譯器已無法正確理解後續程式。
因此,應先修正最前面的錯誤,再重新編譯。
expected ';' before
錯誤訊息:
expected ';' before
通常表示前一行少了分號。
錯誤:
int value = 100
Serial.println(value);
正確:
int value = 100;
Serial.println(value);
注意:
編譯器指出的行號,不一定就是實際出錯行。
因為它常在讀到下一行時,才發現前一行沒有結束。
was not declared in this scope
錯誤:
'ledPin' was not declared in this scope
表示目前位置找不到:
ledPin
常見原因:
- 忘記宣告。
- 名稱拼錯。
- 大小寫不同。
- 變數只宣告在另一個區域。
- 區域變數超出作用範圍。
例如:
void setup()
{
int ledPin = 13;
}
void loop()
{
digitalWrite(
ledPin,
HIGH
);
}
ledPin只存在於setup()內。
正確方式之一:
const int LED_PIN = 13;
void setup()
{
pinMode(LED_PIN, OUTPUT);
}
void loop()
{
digitalWrite(
LED_PIN,
HIGH
);
}
expected '}' at end of input
錯誤:
expected '}' at end of input
表示程式可能缺少右大括號:
}
錯誤:
void loop()
{
if(buttonPressed)
{
digitalWrite(
LED_BUILTIN,
HIGH
);
}
這裡只關閉了if或loop()其中之一。
建議使用 Arduino IDE 的:
- 自動格式化。
- 括號高亮。
- 程式縮排。
來找出括號不成對的位置。
expected ')' before
這通常表示:
- 少了右括號。
- 多了逗號。
- 條件式語法錯誤。
錯誤:
if(sensorValue > 500
{
Serial.println("High");
}
正確:
if(sensorValue > 500)
{
Serial.println("High");
}
no matching function for call to
錯誤:
no matching function for call to
表示呼叫函式時:
- 參數數量不對。
- 參數型別不對。
- 使用了不存在的多載版本。
例如:
digitalWrite(13);
但digitalWrite()需要:
digitalWrite(pin, value);
正確:
digitalWrite(
13,
HIGH
);
invalid conversion
例如:
invalid conversion from 'const char*' to 'int'
表示資料型別不相容。
錯誤:
int value = "123";
"123"是文字,不是整數。
可改成:
int value = 123;
若文字來自輸入,需要進行轉換。
redefinition of
錯誤:
redefinition of 'int ledPin'
表示同一作用範圍內重複定義。
錯誤:
int ledPin = 9;
int ledPin = 10;
應保留單一定義,或使用不同名稱。
multiple definition of
這通常是連結錯誤。
例如全域變數直接寫在標頭檔:
int sharedValue = 0;
標頭檔被多個.cpp包含後,就產生多份定義。
較好的方式:
在.h中:
extern int sharedValue;
在單一.cpp中:
int sharedValue = 0;
undefined reference to
錯誤:
undefined reference to
通常表示:
- 函式有宣告但沒有實作。
- .cpp沒有被編譯。
- 函式名稱或參數不一致。
- 函式庫不完整。
標頭:
void startMotor(
int speed
);
實作卻是:
void startMotor()
{
}
兩者不是同一個函式。
No such file or directory
錯誤:
SomeLibrary.h:
No such file or directory
表示找不到標頭檔。
檢查:
- 函式庫是否安裝。
- 名稱大小寫是否正確。
- 是否裝錯同名函式庫。
- ZIP 結構是否正確。
- 資料夾是否多包一層。
- IDE 是否重新啟動。
- 是否缺少相依函式庫。
exit status 1
exit status 1不是根本原因。
它只表示:
作業失敗
必須往上找真正的:
error:
fatal error:
undefined reference:
avrdude:
avrdude: stk500_recv()
常見訊息:
avrdude: stk500_recv():
programmer is not responding
排查順序:
- 開發板型號是否正確?
- Processor 選項是否正確?
- COM Port 是否正確?
- USB 線是否支援資料?
- Serial Monitor 是否占用連接埠?
- D0、D1 是否接有外部模組?
- Arduino 是否正常供電?
- 是否可按 Reset 後再上傳?
- 驅動程式是否正常?
- Bootloader 是否損壞?
ser_open(): can't open device
例如:
ser_open():
can't open device
可能原因:
- COM Port 不存在。
- Arduino 已拔除。
- 連接埠被其他程式占用。
- 權限不足。
- 驅動程式異常。
- IDE 保留舊連接埠。
處理方式:
- 關閉 Serial Monitor。
- 關閉其他終端機程式。
- 重新插拔 USB。
- 重新選擇 Port。
- 重新啟動 IDE。
- 檢查裝置管理員。
not in sync
錯誤:
avrdude:
stk500_getsync():
not in sync
可能原因:
- 開發板型號錯誤。
- Bootloader 不相容。
- D0、D1 有干擾。
- 自動重置失敗。
- MCU 沒有正常工作。
- USB-to-Serial 晶片異常。
programmer is not responding
若使用一般 USB 上傳,Arduino IDE 使用的 Programmer 選項通常不是最先需要修改的項目。
初學者常誤以為要不斷切換:
Tools → Programmer
但一般 Uno 透過 Bootloader 上傳時,主要檢查的是:
- Board。
- Processor。
- Port。
- USB。
- Bootloader。
程式可以編譯,但為什麼不運作?
編譯成功只代表:
程式語法與連結大致正確
不代表:
- 接線正確。
- 邏輯正確。
- 感測器存在。
- 位址正確。
- 電源足夠。
- 時序正確。
- 硬體沒有損壞。
因此:
編譯成功是起點,不是成功完成專案的證明。
最小可重現程式
Minimal Reproducible Example 常縮寫為:
MRE
它是一個:
- 程式最短。
- 問題仍可重現。
- 不含無關功能。
- 別人可以直接測試。
的範例。
為什麼要建立 MRE?
假設原始程式有:
- OLED。
- 溫度感測器。
- 馬達。
- EEPROM。
- Timer。
- 按鍵。
- Wi-Fi。
- 多個函式庫。
如果 OLED 沒有顯示,不應直接在整個大型專案中亂改。
應建立最小程式,只保留:
#include <Wire.h>
#include <DisplayLibrary.h>
void setup()
{
Wire.begin();
display.begin();
display.print("TEST");
}
void loop()
{
}
如果最小程式仍失敗,問題可能在:
- 接線。
- 位址。
- 函式庫。
- 模組。
- 供電。
如果最小程式成功,問題才可能來自大型專案中的資源衝突或邏輯。
建立 MRE 的步驟
- 複製原始專案。
- 保留問題功能。
- 刪除無關模組。
- 刪除無關函式庫。
- 移除複雜控制流程。
- 使用固定測試值。
- 保留必要接線。
- 確認問題仍然存在。
- 記錄開發板與版本。
二分法除錯
當大型程式無法找到問題時,可以使用:
Binary Search Debugging(二分法除錯)。
做法:
- 暫時停用一半功能。
- 測試問題是否仍存在。
- 如果仍存在,問題在保留的一半。
- 如果消失,問題在被停用的一半。
- 再將可疑區域分成一半。
- 重複測試。
例如:
完整程式
├── 感測器
├── 顯示器
├── 馬達
├── Serial
├── EEPROM
└── Timer
先停用:
馬達、EEPROM、Timer
觀察問題是否消失。
這比逐行亂改更有效率。
使用註解停用程式
可以暫時停用:
// updateMotor();
// saveSettings();
// updateDisplay();
或使用條件編譯:
#define ENABLE_MOTOR 0
#if ENABLE_MOTOR
updateMotor();
#endif
條件編譯適合保留程式結構,又能快速開關功能。
Serial Debug 基本方法
最簡單的流程追蹤:
Serial.println(
F("A")
);
initializeSensor();
Serial.println(
F("B")
);
initializeDisplay();
Serial.println(
F("C")
);
如果輸出只到:
A
B
表示程式可能卡在:
initializeDisplay();
或其前後區域。
使用有意義的訊息
不推薦:
Serial.println("1");
Serial.println("2");
Serial.println("3");
較好的方式:
Serial.println(
F("[SETUP] Sensor initialization")
);
Serial.println(
F("[SETUP] Display initialization")
);
Serial.println(
F("[SETUP] System ready")
);
數日後重新除錯時,仍能理解訊息代表什麼。
顯示變數
Serial.print(
F("sensorValue=")
);
Serial.println(
sensorValue
);
多個變數:
Serial.print(
F("raw=")
);
Serial.print(rawValue);
Serial.print(
F(" filtered=")
);
Serial.print(filteredValue);
Serial.print(
F(" threshold=")
);
Serial.println(threshold);
顯示條件結果
bool alarmCondition =
sensorValue > threshold;
Serial.print(
F("alarmCondition=")
);
Serial.println(
alarmCondition
? F("TRUE")
: F("FALSE")
);
這比只觀察最終 LED 是否亮,更容易判斷是哪個條件不正確。
顯示程式狀態
狀態機程式可以輸出:
Serial.print(
F("state=")
);
Serial.println(
currentState
);
更好的方式是輸出名稱:
const char *stateName =
"UNKNOWN";
依狀態選擇:
switch(currentState)
{
case STATE_IDLE:
stateName = "IDLE";
break;
case STATE_RUNNING:
stateName = "RUNNING";
break;
case STATE_ERROR:
stateName = "ERROR";
break;
}
再輸出:
Serial.println(stateName);
加入時間戳
Serial.print('[');
Serial.print(millis());
Serial.print(
F(" ms] Sensor=")
);
Serial.println(
sensorValue
);
輸出:
[1000 ms] Sensor=512
[1500 ms] Sensor=520
時間戳可以判斷:
- 程式有沒有卡住。
- 工作是否準時執行。
- Reset 是否發生。
- 事件間隔是否合理。
限制除錯輸出頻率
不要在每次loop()都輸出。
unsigned long previousDebugMillis = 0;
const unsigned long DEBUG_INTERVAL =
500;
void updateDebugOutput()
{
unsigned long currentMillis =
millis();
if(
currentMillis
- previousDebugMillis
< DEBUG_INTERVAL
)
{
return;
}
previousDebugMillis +=
DEBUG_INTERVAL;
Serial.print(
F("Sensor=")
);
Serial.println(
analogRead(A0)
);
}
只在資料改變時輸出
int previousValue = -1;
void loop()
{
int currentValue =
digitalRead(2);
if(currentValue != previousValue)
{
previousValue =
currentValue;
Serial.print(
F("Button=")
);
Serial.println(
currentValue
);
}
}
這適合:
- 按鍵。
- 開關。
- 模式。
- 狀態機。
- 錯誤狀態。
條件式 Debug 巨集
#define DEBUG_ENABLED 1
#if DEBUG_ENABLED
#define DEBUG_PRINT(value) \
Serial.print(value)
#define DEBUG_PRINTLN(value) \
Serial.println(value)
#else
#define DEBUG_PRINT(value)
#define DEBUG_PRINTLN(value)
#endif
使用:
DEBUG_PRINT(
F("ADC=")
);
DEBUG_PRINTLN(
adcValue
);
關閉除錯時:
#define DEBUG_ENABLED 0
輸出程式會在編譯時移除。
Debug Level
大型程式可使用不同層級:
enum DebugLevel
{
DEBUG_NONE,
DEBUG_ERROR,
DEBUG_WARNING,
DEBUG_INFO,
DEBUG_VERBOSE
};
例如:
DebugLevel debugLevel =
DEBUG_INFO;
錯誤訊息:
if(debugLevel >= DEBUG_ERROR)
{
Serial.println(
F("[ERROR] Sensor failed")
);
}
詳細資料:
if(debugLevel >= DEBUG_VERBOSE)
{
Serial.print(
F("[VERBOSE] ADC=")
);
Serial.println(
adcValue
);
}
不要在 ISR 中直接大量除錯
錯誤示範:
ISR(TIMER1_COMPA_vect)
{
Serial.println(
"Timer interrupt"
);
}
問題包括:
- Serial Buffer 可能塞滿。
- ISR 可能卡住。
- 中斷延遲。
- 其他事件遺失。
- 程式時序被嚴重改變。
正確方式:
volatile unsigned long
timerInterruptCount = 0;
ISR(TIMER1_COMPA_vect)
{
timerInterruptCount++;
}
主程式再安全讀取:
noInterrupts();
unsigned long countCopy =
timerInterruptCount;
interrupts();
Serial.println(countCopy);
LED Debug
當 Serial 無法使用時,可以使用 LED 判斷程式流程。
例如:
digitalWrite(
LED_BUILTIN,
HIGH
);
代表:
程式已進入某個位置
啟動指示
void setup()
{
pinMode(
LED_BUILTIN,
OUTPUT
);
digitalWrite(
LED_BUILTIN,
HIGH
);
}
若 LED 完全不亮,可能表示:
- 程式沒有執行。
- 上傳失敗。
- 腳位錯誤。
- LED 邏輯相反。
- 開發板沒有供電。
使用閃爍代碼表示錯誤
例如:
| 閃爍次數 | 錯誤 |
|---|---|
| 1 | 感測器錯誤 |
| 2 | 顯示器錯誤 |
| 3 | EEPROM 錯誤 |
| 4 | 通訊錯誤 |
概念程式:
void blinkErrorCode(
byte code
)
{
for(byte index = 0;
index < code;
index++)
{
digitalWrite(
LED_BUILTIN,
HIGH
);
delay(200);
digitalWrite(
LED_BUILTIN,
LOW
);
delay(200);
}
delay(1000);
}
這是阻塞式範例,適合系統已進入致命錯誤、其他功能不再執行的情況。
心跳 LED
Heartbeat LED 用來表示主程式仍正常運作。
unsigned long previousHeartbeatMillis = 0;
bool heartbeatState = false;
void updateHeartbeat()
{
unsigned long currentMillis =
millis();
if(
currentMillis
- previousHeartbeatMillis
>= 500
)
{
previousHeartbeatMillis +=
500;
heartbeatState =
!heartbeatState;
digitalWrite(
LED_BUILTIN,
heartbeatState
);
}
}
若 LED 停止閃爍,可能表示:
- 程式卡住。
- 進入無限迴圈。
- 中斷占用過多 CPU。
- 系統不斷重啟。
使用 GPIO 測量執行時間
const int TEST_PIN = 8;
void setup()
{
pinMode(TEST_PIN, OUTPUT);
}
void loop()
{
digitalWrite(
TEST_PIN,
HIGH
);
performTask();
digitalWrite(
TEST_PIN,
LOW
);
}
使用示波器或邏輯分析儀量測 HIGH 時間,即可估算:
performTask() 執行時間
若需要更快的測量,可進一步使用直接暫存器操作,減少digitalWrite()本身造成的時間。
硬體除錯第一步:檢查電源
許多看似程式問題,其實是電源問題。
檢查:
- Arduino Power LED 是否亮?
- 5V 是否真的接到 5V?
- 3.3V 模組是否誤接 5V?
- 外部設備是否需要更大電流?
- USB 是否提供足夠電流?
- 馬達啟動時電壓是否下降?
- GND 是否共地?
不要只看 Power LED
Power LED 亮只代表板上有某種電壓。
不代表:
- 5V 穩定。
- 3.3V 正常。
- 外部模組已供電。
- 接線沒有壓降。
- 馬達啟動時不會掉電。
應使用萬用電表實際量測。
使用萬用電表量電壓
量測 Arduino 5V:
- 將萬用電表設為直流電壓。
- 黑表筆接 GND。
- 紅表筆接 5V。
- 觀察電壓。
正常值應接近:
5 V
實際值可能略有誤差。
量測 3.3V 方式相同。
檢查 GND
所有相互通訊的設備通常需要共地。
例如:
Arduino TX → 模組 RX
Arduino RX ← 模組 TX
Arduino GND ─ 模組 GND
如果沒有共地,訊號電壓沒有共同參考點,通訊可能:
- 完全失敗。
- 偶爾成功。
- 出現亂碼。
- 容易受雜訊影響。
檢查接線方向
依序確認:
- VCC 是否接到正確電壓?
- GND 是否接 GND?
- TX 是否接 RX?
- RX 是否接 TX?
- SDA 是否接 SDA?
- SCL 是否接 SCL?
- SPI MOSI 是否接 MOSI?
- SPI MISO 是否接 MISO?
- SCK 是否接 SCK?
- CS 是否接指定腳位?
不要只依賴模組腳位順序。
不同廠牌模組的排針順序可能不同。
應查看:
- PCB 絲印。
- 模組資料表。
- 原理圖。
- 晶片型號。
LED 方向
一般 LED:
- 長腳通常是陽極。
- 短腳通常是陰極。
- 平邊通常靠近陰極。
典型接法:
Arduino GPIO
↓
限流電阻
↓
LED 陽極
LED 陰極
↓
GND
LED 接反通常不會亮。
檢查限流電阻
LED 應串接限流電阻,例如:
220 Ω
330 Ω
1 kΩ
不要將一般 LED 直接接在 5V 與 GND 之間。
也不要讓 Arduino GPIO 長時間輸出超過安全電流。
麵包板的常見問題
麵包板可能造成:
- 電源軌中間斷開。
- 左右電源軌不相通。
- 元件腳沒有插入同一節點。
- 導線插錯排。
- 元件腳太細造成接觸不良。
- 麵包板老化鬆動。
- 按鍵方向插錯。
麵包板電源軌不一定全長相通
有些麵包板的:
+ 電源軌
- 電源軌
在中間會斷開。
因此上半部有 5V,不代表下半部也有 5V。
應使用萬用電表:
- 量測電壓。
- 使用導通模式。
- 加跳線連接中斷區段。
四腳按鍵的內部連接
常見四腳按鍵同一側的兩腳內部已相連。
按下時才會連接左右兩側。
如果接線接在同一側,按下前後可能完全沒有變化。
應先用萬用電表導通模式確認腳位關係。
檢查杜邦線
杜邦線可能:
- 內部斷裂。
- 接頭鬆動。
- 壓接不良。
- 接觸電阻過大。
最簡單測試方式:
- 換一條已知正常的線。
- 用導通模式測試。
- 輕微晃動時觀察是否斷續。
USB 線問題
有些 USB 線只支援充電,不支援資料。
現象:
- Arduino 有電。
- Power LED 亮。
- 電腦沒有出現 COM Port。
- 無法上傳程式。
解決方式:
- 換一條已知可傳資料的 USB 線。
- 更換 USB 連接埠。
- 避免品質差的延長線或 Hub。
Windows 裝置管理員
在 Windows 中,可查看:
裝置管理員
→ 連接埠(COM 和 LPT)
確認 Arduino 是否出現。
常見 USB-to-Serial 晶片包括:
- ATmega16U2。
- CH340。
- CP2102。
- FT232。
- PL2303。
相容板可能需要額外驅動程式。
COM Port 變化
Arduino 插入不同 USB 插孔時,Windows 可能分配不同 COM Port。
例如:
COM3
COM5
COM8
每次重新連接後,都應確認 Arduino IDE 中選擇的 Port。
使用 Blink 驗證開發板
在排查複雜專案前,先上傳最基本 Blink。
void setup()
{
pinMode(
LED_BUILTIN,
OUTPUT
);
}
void loop()
{
digitalWrite(
LED_BUILTIN,
HIGH
);
delay(500);
digitalWrite(
LED_BUILTIN,
LOW
);
delay(500);
}
如果 Blink 都不能成功:
- 不要先檢查感測器。
- 不要先換函式庫。
- 先解決開發板、USB、Port 與上傳問題。
使用 GPIO 測試程式
測試特定腳位:
const int TEST_PIN = 8;
void setup()
{
pinMode(
TEST_PIN,
OUTPUT
);
}
void loop()
{
digitalWrite(
TEST_PIN,
HIGH
);
delay(1000);
digitalWrite(
TEST_PIN,
LOW
);
delay(1000);
}
用:
- LED。
- 萬用電表。
- 示波器。
確認腳位是否正常切換。
輸入腳位測試
const int INPUT_PIN = 2;
void setup()
{
pinMode(
INPUT_PIN,
INPUT_PULLUP
);
Serial.begin(9600);
}
void loop()
{
Serial.println(
digitalRead(INPUT_PIN)
);
delay(100);
}
將 D2 短接 GND:
讀值應為 LOW
放開:
讀值應為 HIGH
若結果不變,檢查:
- 腳位。
- GND。
- 接線。
- 程式。
- GPIO 是否損壞。
Floating Input
如果設定:
pinMode(
INPUT_PIN,
INPUT
);
但腳位沒有明確接:
- HIGH。
- LOW。
- 上拉。
- 下拉。
讀值可能隨機變化。
這稱為:
Floating Input(浮動輸入)。
簡單按鍵可以使用:
INPUT_PULLUP
避免浮動。
萬用電表導通測試
導通模式可檢查:
- 杜邦線是否斷裂。
- 麵包板節點是否相通。
- 按鍵腳位是否正確。
- GND 是否連通。
- 焊點是否斷裂。
進行導通測試前,通常應先關閉電源,避免損壞萬用電表或電路。
量電阻前應斷電
測量電阻時,應:
- 關閉電源。
- 最好將元件至少一端移出電路。
- 避免其他並聯路徑影響讀值。
不要在通電狀態下直接使用電阻檔量測。
UART 除錯
UART 通訊失敗時,依序檢查:
- TX、RX 是否交叉?
- GND 是否共地?
- Baud Rate 是否一致?
- Data Bits 是否一致?
- Stop Bits 是否一致?
- Parity 是否一致?
- 邏輯電壓是否相容?
- 是否把 TTL UART 當成 RS232?
- D0、D1 是否被 USB 共用?
- 模組是否需要特定啟動命令?
TX 與 RX 要交叉
正確:
Arduino TX → 模組 RX
Arduino RX ← 模組 TX
Arduino GND ─ 模組 GND
錯誤:
TX → TX
RX → RX
TTL Serial 與 RS232 不同
Arduino Uno UART 是 TTL 邏輯。
一般電壓大致為:
0 V
5 V
或部分設備為:
0 V
3.3 V
傳統 RS232 可能使用:
- 正負電壓。
- 反相邏輯。
- 較高電壓範圍。
不能將 Arduino TTL UART 直接接到傳統 RS232 接口。
需要:
MAX232
MAX3232
等電平轉換晶片。
UART Loopback 測試
可以測試 UART 傳送與接收。
概念:
TX 接 RX
Arduino 傳出的資料會回到接收端。
但 Arduino Uno 的 D0、D1 同時連接 USB-to-Serial 晶片,測試方式要避免電氣衝突。
也可以使用外部 USB-to-TTL 模組進行 Loopback:
模組 TX 與 RX 短接
在電腦終端機輸入文字,若能原樣返回,表示 USB-to-TTL 模組基本正常。
Baud Rate 亂碼
若 Serial Monitor 顯示亂碼,先檢查:
Serial.begin(115200);
Monitor 是否也選:
115200
若使用外部模組,還要確認模組預設 Baud Rate。
常見模組可能使用:
9600
38400
115200
I²C 除錯
I²C 通訊失敗時,先檢查:
- VCC。
- GND。
- SDA。
- SCL。
- 位址。
- 上拉電阻。
- 邏輯電壓。
- 線路長度。
- 模組是否進入正確模式。
Arduino Uno:
SDA → A4
SCL → A5
也可以使用板上的專用 SDA、SCL 腳位。
第一個 I²C 工具:Scanner
I²C Scanner 可以掃描匯流排上的裝置位址。
#include <Wire.h>
void setup()
{
Serial.begin(9600);
Wire.begin();
Serial.println(
F("I2C Scanner")
);
}
void loop()
{
byte deviceCount = 0;
for(byte address = 1;
address < 127;
address++)
{
Wire.beginTransmission(
address
);
byte error =
Wire.endTransmission();
if(error == 0)
{
Serial.print(
F("Device found at 0x")
);
if(address < 16)
{
Serial.print('0');
}
Serial.println(
address,
HEX
);
deviceCount++;
}
else if(error == 4)
{
Serial.print(
F("Unknown error at 0x")
);
if(address < 16)
{
Serial.print('0');
}
Serial.println(
address,
HEX
);
}
}
if(deviceCount == 0)
{
Serial.println(
F("No I2C devices found")
);
}
else
{
Serial.print(
F("Device count: ")
);
Serial.println(
deviceCount
);
}
Serial.println();
delay(3000);
}
Scanner 找不到裝置
檢查順序:
- 模組是否有供電?
- GND 是否共地?
- SDA、SCL 是否接反?
- 使用的是否真的是 I²C 模組?
- 邏輯電壓是否正確?
- 是否需要上拉電阻?
- 杜邦線是否正常?
- 模組是否損壞?
- Arduino 腳位是否正確?
- 是否有其他裝置拉住匯流排?
Scanner 找到位址,但程式仍失敗
如果 Scanner 找到:
0x3C
但程式設定:
display.begin(..., 0x3D);
則位址不一致。
也可能是:
- 函式庫不支援該控制晶片。
- 顯示器解析度錯誤。
- 初始化參數錯誤。
- Reset 腳位設定錯誤。
- SRAM 不足。
- 顯示 Buffer 沒有送出。
例如有些 OLED 函式庫需要:
display.display();
才真正更新畫面。
I²C 位址衝突
若兩個裝置都使用:
0x3C
Arduino 無法直接區分。
解決方式可能包括:
- 修改裝置位址跳線。
- 使用 I²C Multiplexer。
- 選擇不同型號。
- 使用不同 I²C 控制器。
I²C Bus 被拉低
如果 SDA 或 SCL 長時間保持 LOW,整個 I²C Bus 可能無法工作。
可能原因:
- 裝置未正常重置。
- 通訊中途斷電。
- 接線短路。
- 裝置損壞。
- 上拉電阻不正確。
可以用萬用電表或示波器檢查空閒狀態。
I²C 空閒時,SDA 與 SCL 通常應為 HIGH。
SPI 除錯
SPI 通訊需檢查:
- MOSI。
- MISO。
- SCK。
- CS。
- GND。
- 電壓。
- SPI Mode。
- Clock Frequency。
- Bit Order。
Arduino Uno 常見硬體 SPI:
| 訊號 | 腳位 |
|---|---|
| MOSI | D11 |
| MISO | D12 |
| SCK | D13 |
| SS | D10 |
也可從 ICSP Header 取得 SPI 訊號。
SPI 的 CS 問題
每個 SPI 裝置通常需要獨立 CS。
例如:
SD Card CS → D10
Display CS → D9
Sensor CS → D8
未使用的裝置 CS 應保持 HIGH。
如果兩個 CS 同時為 LOW,可能有兩個裝置同時驅動 MISO,造成資料衝突。
SPI Mode
SPI Mode 由:
- CPOL。
- CPHA。
組成。
常見:
MODE0
MODE1
MODE2
MODE3
主機與從機必須使用相同模式。
若模式錯誤,可能:
- 完全無資料。
- 資料位元錯位。
- 數值不穩定。
- 低速可用,高速失敗。
SPI Clock 太快
有些裝置無法支援高頻 SCK。
若通訊不穩,可以降低速度。
使用 Transaction:
SPI.beginTransaction(
SPISettings(
1000000,
MSBFIRST,
SPI_MODE0
)
);
這裡設定:
1 MHz
如果仍不穩,可以降低到:
500 kHz
100 kHz
測試。
SPI 沒有自動 Scanner
I²C 有裝置位址與 ACK,因此可以掃描。
SPI 沒有統一位址與標準 ACK 機制,所以通常無法使用通用 Scanner。
SPI 除錯通常依賴:
- 晶片 ID 暫存器。
- 固定回傳值。
- 邏輯分析儀。
- 示波器。
- 最小測試程式。
感測器除錯基本流程
感測器沒有正常資料時:
- 查晶片型號。
- 確認供電範圍。
- 確認通訊介面。
- 確認腳位名稱。
- 執行最小範例。
- 檢查初始化回傳值。
- 讀取晶片 ID。
- 檢查原始資料。
- 再進行單位換算。
- 最後加入濾波與控制。
先看原始值,再看換算值
例如溫度計算錯誤時,先輸出原始 ADC:
int rawValue =
analogRead(A0);
Serial.print(
F("raw=")
);
Serial.println(rawValue);
再輸出中間變數:
float voltage =
rawValue
* 5.0
/ 1023.0;
Serial.print(
F("voltage=")
);
Serial.println(
voltage,
3
);
最後才輸出溫度:
Serial.print(
F("temperature=")
);
Serial.println(
temperature,
2
);
這樣可以知道錯誤發生在:
- ADC。
- 電壓換算。
- 溫度公式。
整數除法錯誤
錯誤:
float voltage =
rawValue
* 5
/ 1023;
由於中間可能先使用整數運算,結果精度可能不符合預期。
更安全:
float voltage =
rawValue
* 5.0
/ 1023.0;
map() 不支援浮點精度
Arduino 的 map() 使用 long 整數運算,不提供浮點數精度。
例如:
long mapped =
map(
value,
0,
1023,
0,
500
);
如果需要高精度浮點換算,建議直接使用公式。
ADC 值不穩定
可能原因:
- 輸入浮動。
- 感測器輸出阻抗過高。
- 電源雜訊。
- GND 不穩。
- 馬達干擾。
- 參考電壓變動。
- 接線過長。
- ADC 取樣頻率過高。
可測試:
- 將 A0 直接接 GND,應接近 0。
- 將 A0 接 5V,應接近 1023。
- 使用可變電阻建立穩定輸入。
- 加入平均濾波。
- 改善電源與接地。
PWM 除錯
PWM 看似沒有正確輸出時,檢查:
- 腳位是否支援 PWM?
- 是否使用analogWrite()?
- 數值是否為 0~255?
- 對應 Timer 是否被修改?
- Servo 或其他函式庫是否占用 Timer?
- LED 是否接反?
- 馬達是否需要驅動器?
- 萬用電表是否能正確顯示 PWM 平均值?
萬用電表看到的是平均值
PWM 不是連續類比電壓。
例如 5V、50% Duty Cycle,萬用電表可能顯示接近:
2.5 V
但實際波形仍是:
0 V 與 5 V 快速切換
要看真正波形,需要示波器或邏輯分析儀。
中斷除錯
中斷沒有觸發時,檢查:
- 是否使用正確中斷腳位?
- digitalPinToInterrupt()是否正確?
- 觸發模式是否正確?
- 輸入是否浮動?
- ISR 名稱是否正確?
- 全域中斷是否被關閉?
- 是否有按鍵彈跳?
- ISR 是否執行太久?
- 共享變數是否使用volatile?
用計數器取代 ISR Serial
volatile unsigned long
interruptCount = 0;
void handleInterrupt()
{
interruptCount++;
}
主程式:
unsigned long countCopy;
noInterrupts();
countCopy =
interruptCount;
interrupts();
Serial.println(
countCopy
);
若計數不增加,表示中斷可能沒有觸發。
若一次增加很多,可能是:
- 按鍵彈跳。
- 雜訊。
- 觸發邊緣錯誤。
- 輸入浮動。
Timer 除錯
Timer 沒有正確運作時,檢查:
- CPU 時脈是否正確?
- Prescaler 是否正確?
- OCR 是否在範圍內?
- CTC 或 Overflow 模式是否正確?
- 中斷 Mask 是否啟用?
- ISR Vector 是否正確?
- 是否與 Servo、PWM 或tone()衝突?
- 是否修改 Timer0?
- ISR 是否太慢?
使用測試腳位驗證 Timer
ISR(TIMER1_COMPA_vect)
{
PINB =
(1 << PINB0);
}
在 ATmega328P 上,這類直接暫存器操作可快速切換對應腳位,但它具有:
- 開發板相依性。
- 暫存器相依性。
- 不易閱讀。
初學測試可先在 ISR 設定旗標,由主程式控制 LED。
進階時再搭配示波器測量精準週期。
系統化故障排除流程
推薦依照以下順序。
Step 1:定義問題
記錄:
- 預期結果。
- 實際結果。
- 發生條件。
- 是否可重現。
- 最近修改內容。
Step 2:確認基本環境
檢查:
- 開發板型號。
- Processor。
- Port。
- USB 線。
- 驅動程式。
- Arduino IDE 版本。
- 函式庫版本。
Step 3:確認電源
量測:
- 5V。
- 3.3V。
- GND。
- 模組 VCC。
- 負載啟動時電壓。
Step 4:確認接線
逐條檢查:
- 腳位名稱。
- 接線方向。
- 共地。
- 電壓。
- 電源軌。
- 元件方向。
Step 5:使用已知正常範例
例如:
- Blink。
- AnalogReadSerial。
- I²C Scanner。
- Servo Sweep。
- 函式庫官方範例。
Step 6:建立 MRE
只保留故障功能。
Step 7:加入觀察點
使用:
- Serial。
- LED。
- GPIO。
- 萬用電表。
Step 8:一次只改一項
每次修改後記錄結果。
Step 9:確認修正沒有產生新問題
重新測試:
- 原本故障。
- 其他既有功能。
- 長時間運作。
- 邊界值。
- 重新開機。
Step 10:記錄根本原因
不要只記錄:
換線後好了
應記錄:
原杜邦線內部斷路,導致 I²C SCL 無法傳輸。更換導線後恢復。
這樣才能累積可重複使用的工程知識。
五問除錯法
遇到問題時,可以問:
- 電源正確嗎?
- 接線正確嗎?
- 最小程式能運作嗎?
- 輸入與中間變數正確嗎?
- 最近改了什麼?
這五個問題能快速排除大量常見故障。
不要忽略最近一次修改
如果系統昨天正常,今天修改後失敗,先檢查:
- 新加入的函式庫。
- 新增的 Timer。
- 新接的模組。
- 更改的腳位。
- 新增的全域變數。
- 電源負載變化。
- IDE 或 Board Core 更新。
最近的修改通常是最有價值的線索。
建立除錯紀錄表
| 日期 | 現象 | 假設 | 測試 | 結果 | 根本原因 |
|---|---|---|---|---|---|
| 7/17 | OLED 無畫面 | 位址錯誤 | Scanner | 找到 0x3C | 程式使用 0x3D |
| 7/18 | 無法上傳 | COM 錯誤 | 重新插拔 | Port 變成 COM7 | IDE 仍選 COM5 |
| 7/19 | 馬達啟動重啟 | 電源不足 | 量測 5V | 降至 3.9V | 馬達與 MCU 共用弱電源 |
除錯紀錄可以避免重複犯相同錯誤。
初學者常見除錯錯誤
錯誤一:只看最後一行錯誤
只看到:
exit status 1
卻忽略前面的真正原因。
錯誤二:一次修改很多項目
最後無法知道哪一項真正有效。
錯誤三:直接懷疑 Arduino 壞掉
實際上更常見的是:
- 接線。
- Port。
- USB 線。
- 位址。
- 程式邏輯。
錯誤四:相信顏色,不確認腳位
杜邦線顏色不代表固定功能。
紅線不一定是 5V,黑線也不一定是 GND。
必須依實際接線確認。
錯誤五:只看 Power LED
Power LED 亮不代表外部模組電壓正確。
錯誤六:忽略共地
UART、PWM、感測器與外部電源系統若未共地,可能無法正常工作。
錯誤七:直接在大型專案中除錯
應先建立最小可重現程式。
錯誤八:在 ISR 中大量 Serial 輸出
可能讓原本問題更加嚴重。
錯誤九:將 TTL Serial 直接接 RS232
可能造成通訊失敗,甚至損壞硬體。
錯誤十:一直更換函式庫
應先確認:
- 晶片型號。
- 通訊介面。
- 函式庫作者。
- 範例與版本。
錯誤十一:把軟體問題與硬體問題混在一起
應分層測試:
開發板
→ GPIO
→ 通訊
→ 模組
→ 函式庫
→ 完整專案
錯誤十二:問題消失就停止分析
若沒有找到根本原因,問題可能再次出現。
本章上半部實作練習
練習一:修正編譯錯誤
找出以下程式問題:
int ledPin = 13
void setup()
{
pinMode(
ledpin,
OUTPUT
)
}
void loop()
{
digitalWrite(
ledPin,
HIGH
);
}
至少包含:
- 分號問題。
- 大小寫問題。
練習二:作用範圍
說明為什麼以下程式無法編譯:
void setup()
{
int threshold = 500;
}
void loop()
{
if(
analogRead(A0)
> threshold
)
{
Serial.println(
"High"
);
}
}
將它修正為可正常執行。
練習三:建立 MRE
將一個包含:
- OLED。
- DHT。
- Servo。
- EEPROM。
- 按鍵。
的專案縮減成只測試 OLED 的最小程式。
記錄刪除了哪些功能。
練習四:Serial 流程標記
建立程式,輸出:
[SETUP] Start
[SETUP] GPIO ready
[SETUP] Sensor ready
[SETUP] Complete
模擬某一初始化函式卡住,觀察最後輸出位置。
練習五:心跳 LED
使用millis()建立每 500 ms 切換一次的心跳 LED。
主程式中不得使用delay()。
練習六:GPIO 測試
建立一個程式讓 D8 每秒切換 HIGH 與 LOW。
使用:
- LED。
- 萬用電表。
驗證腳位輸出。
練習七:輸入測試
D2 使用INPUT_PULLUP。
將 D2 接 GND 時顯示:
PRESSED
放開時顯示:
RELEASED
只在狀態改變時輸出。
練習八:I²C Scanner
執行 Scanner 並記錄:
- 找到的位址。
- 裝置數量。
- 拔掉模組後的結果。
- SDA、SCL 接反後的結果。
完成後恢復正確接線。
練習九:UART 排查
建立一張 UART 檢查表,至少包含:
- TX/RX。
- GND。
- Baud Rate。
- 邏輯電壓。
- TTL/RS232。
- Port 占用。
練習十:故障紀錄
故意建立以下問題之一:
- LED 接反。
- 選錯 COM Port。
- I²C 位址錯誤。
- 少一個分號。
使用除錯紀錄表寫下:
- 現象。
- 假設。
- 測試。
- 結果。
- 根本原因。
Challenge:建立 Arduino 啟動自我檢查
建立一個 Arduino 自我檢查程式。
功能需求
開機時依序檢查:
- Serial 啟動。
- GPIO 測試。
- A0 是否在合理範圍。
- I²C 是否找到至少一個裝置。
- EEPROM 是否可讀取設定。
- 按鍵是否卡在 LOW。
- 系統是否進入 Ready 狀態。
輸出格式
[SELFTEST] START
[PASS] SERIAL
[PASS] GPIO
[PASS] ADC
[PASS] I2C ADDRESS=0x3C
[PASS] EEPROM
[PASS] BUTTON
[SELFTEST] RESULT=PASS
若失敗:
[FAIL] I2C DEVICE_NOT_FOUND
[SELFTEST] RESULT=FAIL
LED 錯誤代碼
若 Serial 無法使用:
| 代碼 | 錯誤 |
|---|---|
| 1 | ADC |
| 2 | I²C |
| 3 | EEPROM |
| 4 | Button |
系統失敗時,使用內建 LED 重複顯示代碼。
建議函式
runSelfTest();
testGpio();
testAdc();
testI2c();
testEeprom();
testButton();
printPass();
printFail();
setSystemReady();
setSystemError();
blinkErrorCode();
本章上半部重點整理
完成本章上半部後,你應該已經了解:
- Debug 是用證據驗證假設,不是反覆猜測。
- Arduino 問題可分為編譯、連結、上傳、執行、邏輯、硬體與通訊錯誤。
- exit status 1通常不是根本原因。
- 第一個明確錯誤訊息通常最值得先修正。
- 編譯成功不代表硬體與邏輯正確。
- 最小可重現程式能快速縮小問題範圍。
- 二分法適合排查大型專案。
- Serial 可用來追蹤流程、變數、條件與狀態。
- 除錯輸出應加入欄位名稱、時間戳與層級。
- ISR 中不應大量使用 Serial。
- LED 可作為啟動、心跳與錯誤代碼工具。
- 電源、GND、USB、杜邦線與麵包板是常見故障來源。
- 萬用電表可檢查電壓、導通與接線。
- UART 必須確認 TX/RX、Baud Rate、共地與邏輯電壓。
- I²C Scanner 是排查 I²C 裝置的重要工具。
- SPI 需檢查 CS、Mode、Clock 與 Bit Order。
- 感測器除錯應先看原始值,再看換算結果。
- 問題修正後仍應重新測試其他功能與長時間運作。
- 除錯紀錄能將一次性的修理經驗轉為可重複使用的工程知識。
第20章(下)預告
下半部將進一步介紹:
- SRAM、Stack、Heap 與記憶體除錯。
- 陣列越界與未初始化變數。
- String碎片化與隨機重啟。
- Watchdog Timer 與當機恢復。
- Brown-out 與電源瞬降。
- 示波器基本操作。
- 邏輯分析儀與通訊解碼。
- 中斷與競爭條件除錯。
- 長時間運作與間歇性故障。
- 大型專案 Diagnostic Console。
- 完整 Arduino Debug Checklist。
- 綜合 Challenge:建立自我診斷與故障記錄系統。
本章上半部的核心不是教讀者記住所有錯誤訊息,而是建立一套能重複使用的問題分析流程。只要先分層、再測試、一次只改一項,大多數 Arduino 故障都能逐步縮小並找到真正原因。