本文將大致說明Arduino IDE 2.x版提供的程式偵錯功能,並使用Arduino UNO R4 WiFi開發板(原廠或相容板)示範。先說一下,”debug”指的是找出並解決軟硬體的問題,中文翻譯成「除錯」、「偵錯」或「調試」,本文採用跟Arduino IDE和VS Code編輯器一致的「偵錯」翻譯。
檢查程式是否錯誤,例如,某個條件式是否被執行,某個變數值是否跟預期一樣,可以在程式中加入print()函式,或者透過ESP32晶片的Core Debug(核心偵錯)功能(請參閱《超圖解ESP32應用實作》第2章)在「序列監控窗」輸出一段訊息或變數值。
使用print()偵錯,簡單有效但主要問題是,開發者只能被動地接收微控器在預定情況下發出的訊息,無法動態地調整執行中的程式的參數,也無法改變執行流程或暫停程式碼。用看病來比喻,使用print()輸出訊息,相當於醫生問診,讓病患描述身體的狀況。
為了方便開發人員找出軟硬體的問題,微控器廠商各自推出不同的偵錯方案,例如,一種簡稱ICE(In-Circuit Emulator,線上模擬器)以及JTAG和SWD(Serial Wire Debug,序列線偵錯)等介面裝置,它們宛如醫療診斷儀器,能夠即時查看微控器的運作狀態,而且不限於觀察普通變數,也能深入到處理器內部的暫存器,還有呼叫函式時用到的堆疊(stack)記憶體區域。
此外,這些偵錯工具(硬體設備)也允許開發人員暫停,甚至一步一步執行並觀察程式碼。但是,不同微控器廠商的專屬偵錯技術和軟硬體工具,彼此可能不相容,需要額外購買。目前的主流偵錯介面和工具是JTAG和SWD,關於JTAG在ESP32晶片的應用說明,請參閱《超圖解ESP32應用實作》第14章。
Arduino UNO R4 Minima開發板預留一個連接外部偵錯工具用的SWD介面,日後再介紹。
Arduino UNO R4 WiFi開發板的結構不同於Minima,多了一個ESP32-S3晶片,使得UNO R4 WiFi板具備「僅需USB接線,即可進行偵錯」的功能,無需添購其他偵錯設備。
使用USB接線進行Arduino UNO R4 WiFi開發板偵錯
Arduino IDE上方的工具列,有個「開始偵錯」鈕,左側工具列可開、關「偵錯」面板。
先把Arduino UNO R4 WiFi開發板接上電腦。
在Arduino IDE貼入底下的範例程式。這個程式將從UNO R4微控器的DAC(數位類比轉換器)輸出「超級瑪利兄弟主題曲」的一段旋律,程式的說明請參閱《超圖解Arduino互動設計入門》第11章:
// 超級瑪利兄弟主題曲 #include <analogWave.h> analogWave wave(DAC); int tempo = 200; // 節拍 int beatNote = 2; int wholenote = (60000 * beatNote) / tempo; // 全音符 int melody[][2] = { // 旋律 {76, 4}, {76, 4}, {0, 4}, {76, 4}, {0, 4}, {72, 4}, {76, 4}, {0, 4}, {79, 4}, {0, 4}, {0, 2} }; int noteTotal = sizeof(melody) / sizeof(melody[0]); int noteCounter = 0; void setup() { wave.sine(10); // 輸出正弦波 wave.stop(); } void loop() { int currentNote = melody[noteCounter][0]; int noteDuration = wholenote / melody[noteCounter][1]; float noteFreq = 440 * pow(2, ((currentNote - 69) / 12.0)); if (currentNote != 0) { wave.freq(noteFreq); // 調整輸出頻率 delay(noteDuration * 0.9); wave.stop(); // 停止輸出 delay(noteDuration * 0.1); } else { // 休止符 wave.stop(); delay(noteDuration); } noteCounter++; if (noteCounter > noteTotal) { noteCounter = 0; wave.stop(); delay(1000); } }
勾選主功能表的「Sketch / 除錯最佳化」。
「工具 / 燒錄器」選項,請選擇“ARM CMSIS-DAP compatible”(CMSIS-DAP相容燒錄器):
設定中斷點(breakpoint)
中斷點是在程式碼中的特定點故意停止或暫停的地方,例如,假設我們想讓程式執行到第28行的位置時暫停。按一下原始碼行號左側,將出現一個紅點標記,代表該行已設定了一個中斷點;若按一下紅點,將能取消中斷點。
偵錯工具所能追蹤的中斷點數量是有限的,例如:8個。Arduino官方的UNO R4 WiFi的偵錯教學文件指出,隨便你設定多少中斷點都行,額…這個說法有點隨便,不過我們通常只關注一個程式的幾個地方,不會去設置一大堆中斷點。
開始偵錯
依序點擊工具列的「驗證」、「上傳」鈕,然後點擊「開始偵錯」。
過一會兒,IDE底部的狀態列將變為橙色,並顯示gdb-server和「編輯主控台」視窗。
如果出現如下的OpenOCD錯誤訊息,代表「燒錄工具」沒有選用“ARM CMSIS-DAP compatible”。
在Arduino IDE底層,實際負責偵錯的程式是附屬在GCC(Arduino採用的C/C++語言編譯器)的“GDB”(GNU Debugger,開源的偵錯器)。
OpenOCD(Open On-Chip Debugger,直譯為「開源晶片上的偵錯器」)則是另一個開源的偵錯工具,負責連接到JTAG或SWD等偵錯介面。GDB Server(GDB伺服器)則是OpenOCD的一個元件,扮演「中間人」的角色,將GDB指令轉譯為微控制器可以理解的動作。
控制偵錯器的執行流程
開始偵錯時,「偵錯」面板上方的工具列將呈現「可操作」狀態。
它們的作用:
- 繼續 / 暫停:繼續執行程式,直到碰到下一個斷點時暫停;如果沒有設定斷點,則持續執行。
- 不進入函式(Step Over):執行一步敘述然後暫停,如果是呼叫函式的敘述,則會執行完該函式但不會進入函式內部。
- 逐步執行(Step Into):單步執行敘述,如果是呼叫函式的敘述,則進入函式內部。
- 跳離函式(Step Out):一口氣執行完函式的剩餘程式碼,然後跳回到呼叫該函式的那一行敘述。
- 重新啟動:停止目前的偵錯任務並重新啟動偵錯器。
- 停止:停止並退出偵錯。
程式目前暫停在行28,若持續按一下「逐步執行」,執行到行29的wave.freq()敘述時,偵錯器將進入analogWave.cpp裡的analogWave::freq()方法:
你可以持續按一下「逐步執行」,或點擊「跳離函式」,回到主程式檔:
若不想進入行30的delay()內部,可點擊「不進入函式」:
查看並修改變數值
點擊「偵錯」面板的「變數」欄開啟它,可看到裡面的Local(區域)、Global(全域)、Static(靜態)和Register(暫存器)等分類。底下畫面顯示目前所在函式(loop)的所有區域變數名稱及其值。
你可以雙按其中的任意變數值,例如,暫存音頻的noteFreq變數:
即可重新設定變數值:
改成740的結果:
偵錯器的一些小bug
本文原本打算用底下這個,把簡單問題稍微複雜化的閃爍LED程式碼,示範Arduino IDE 2.x版內建的「偵錯」功能,其中的blink()函式有3個區域變數,透過在偵錯過程調整這些參數,可觀察並改變程式的行為。
const byte LED_PIN = LED_BUILTIN; // 定義LED接腳的常數 bool ledState = false; // 定義LED的初始狀態 // 閃爍LED的函式 void blink() { static int counter = 0; // 計數器變數 int times = 3; // 次數變數 int max_counter = 50; // 計數器上限 if (counter % times == 0) { // 若計數了times次… ledState = !ledState; // 反轉LED的狀態 } if (counter < max_counter) { counter ++; } else { counter = 0; } digitalWrite(LED_PIN, ledState); // 設定LED腳的輸出 } void setup() { pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, ledState); } void loop() { blink(); delay(500); }
然而,Arduino IDE的「偵錯」功能本身也有些小bug…使用帶有bug的工具來檢查程式的bug,這感覺有點微妙,一言難盡…不過也許你看到這篇文章的時候,Arduino IDE本身的bug已經解決了。
這是在macOS的Arduino IDE 2.3.4版偵錯上述程式碼的畫面,這裡出現一個bug:區域變數只列出1個:
另外,我不確定這是不是bug:上面的blink()函式定義1個靜態(static)變數,但它並沒有被列在「變數」欄的Static分類。我試過定義一個全域型的靜態變數,也沒有顯示在Static分類。
這是在Windows的Arduino IDE 2.3.4版偵錯相同程式的畫面,區域變數同樣只顯示1個,而且目前所在的行號前面並沒有呈現三角形指示符號:
編輯有設定「中斷點」的那一行程式時,例如,刪除下圖所示的第3行,內文會突然冒出一個紅點。
最後,在偵錯上一節的超級瑪利兄弟旋律程式時,它有時會出現錯誤的變數值,解決辦法是先停止偵錯器、重新上傳程式,再重新開始偵錯。