第20章 Arduino Debug(除錯)與系統化故障排除(上)

發布日期: |更新日期:
文章主圖:第20章 Arduino Debug(除錯)與系統化故障排除(上)

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 差異比較
名稱 主要範圍
Debug 程式、邏輯、執行流程
Troubleshooting 程式、硬體、電源、接線、通訊與環境

Arduino 專案通常需要同時具備:

  • Debug 能力。
  • Troubleshooting 能力。

例如 LED 不亮,可能是:

  • 程式沒有執行。
  • LED 接反。
  • 電阻接錯。
  • GPIO 腳位選錯。
  • 麵包板沒有接通。
  • Arduino 沒有供電。
  • LED 已損壞。
  • 程式輸出 LOW。
  • Active Low 邏輯理解錯誤。

只看程式碼,無法完整解決問題。

知識卡:Arduino 除錯

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 邏輯相反。
  • 開發板沒有供電。

使用閃爍代碼表示錯誤

例如:

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:

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 更新。

最近的修改通常是最有價值的線索。

建立除錯紀錄表

Arduino 除錯紀錄範例
日期 現象 假設 測試 結果 根本原因
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 無法使用:

Arduino 自我檢查 LED 錯誤代碼
代碼 錯誤
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 故障都能逐步縮小並找到真正原因。

相關資源與文章匯出
下載 Arduino 入門基礎教材 下載 DOCX PDF/列印

← 回文章列表