14.1.1.4. การดีบักเฟิร์มแวร์¶
การดีบักบนฮาร์ดแวร์หมายถึงการหยุดโปรเซสเซอร์ การตั้งจุดหยุด (breakpoint) ในโค้ด C การรันทีละขั้น และการตรวจสอบตัวแปร หน่วยความจำ รีจิสเตอร์ และอุปกรณ์ต่อพ่วง -- ทั้งหมดจากภายใน VS Code ซึ่งต้องการสิ่งสำคัญสามอย่าง: debug build, SWD debug probe (Segger J-Link), และส่วนขยาย Cortex-Debug ที่ขับเคลื่อน arm-none-eabi-gdb กับ J-Link GDB server
14.1.1.4.1. การบิลด์สำหรับการดีบัก¶
สร้างเป้าหมายใหม่ด้วย DEBUG=1 เสมอ:
make -j$(nproc) TARGET=<TARGET> DEBUG=1
อิมเมจแบบ release (DEBUG=0) คอมไพล์ด้วย -O2 ในดีบักเกอร์คุณจะเห็น <optimized out> สำหรับตัวแปรหลายตัว ฟังก์ชัน inline จะถูกรวมเข้ากับผู้เรียก และการรันทีละขั้นจะกระโดดไปมาอย่างไม่สามารถคาดเดาได้ การบิลด์ด้วย DEBUG=1 ใช้ -Og -ggdb3 ซึ่งดีบักได้ขณะยังบูตบนกล้อง ไฟล์ ELF ที่คุณชี้ให้ดีบักเกอร์คือ:
build/<TARGET>/bin/firmware.elf
(สำหรับ Alif AE3 ให้ดีบัก build/OPENMV_AE3/bin/firmware_M55_HP.elf -- คอร์ประสิทธิภาพสูง)
14.1.1.4.2. ฮาร์ดแวร์: J-Link ผ่าน SWD¶
เชื่อมต่อ Segger J-Link กับพิน SWD ของกล้อง (SWDIO, SWCLK, GND และ VCC เป้าหมายสำหรับอ้างอิง; กล้องจ่ายไฟผ่าน USB ตามปกติ) J-Link EDU / Base / Pro ทุกรุ่นใช้งานได้ ตำแหน่งของพิน debug แตกต่างกันตามกล้อง -- หลายบอร์ดมีขั้วต่อ JTAG/SWD เฉพาะ บางบอร์ดเปิดเผย SWD บน I/O header หรือ test pad -- ดังนั้นตรวจสอบไดอะแกรม pinout และแผนผังวงจรของบอร์ดนั้นในเอกสารฮาร์ดแวร์ OpenMV เพื่อดูว่าต้องต่อพินใด ติดตั้ง J-Link Software and Documentation Pack จาก segger.com บนเครื่องที่เสียบ probe เข้าไป อัปเดตให้เป็นเวอร์ชันล่าสุดอยู่เสมอ -- ซอฟต์แวร์ J-Link รุ่นเก่าจะไม่รู้จักชื่อดีไวซ์ใหม่ (STM32N6, MIMXRT, Alif)
แต่ละ MCU ต้องการ device name ของ J-Link ที่ถูกต้องเพื่อให้ probe โหลด flash loader และ memory map ที่ถูกต้อง:
กล้อง ( |
MCU |
J-Link |
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
14.1.1.4.3. การตั้งค่า VS Code Cortex-Debug¶
สร้าง .vscode/launch.json ใน repository กรณีที่ง่ายที่สุด -- VS Code, J-Link และ build อยู่บน เครื่องเดียวกัน Linux / macOS -- ใช้ servertype: "jlink" ซึ่งทำให้ Cortex-Debug เริ่ม J-Link GDB server เอง:
{
"version": "0.2.0",
"configurations": [
{
"name": "OpenMV J-Link",
"type": "cortex-debug",
"request": "launch",
"cwd": "${workspaceFolder}",
"executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf",
"servertype": "jlink",
"device": "STM32H743VI",
"interface": "swd",
"runToEntryPoint": "main",
"armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin",
"gdbPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin/arm-none-eabi-gdb"
}
]
}
เปลี่ยน executable และ device ตามบอร์ดของคุณ (ดูตารางด้านบน) กด F5 เพื่อบิลด์-แฟลช-และรันจนถึง main แล้วหยุดที่นั่น
Tip
หากต้องการบิลด์ใหม่โดยอัตโนมัติทุกครั้งที่เริ่มดีบัก ให้เพิ่ม build task ใน .vscode/tasks.json และอ้างอิงจาก launch config ด้วย "preLaunchTask" ตัวอย่างเช่น task ที่รัน make -j$(nproc) TARGET=OPENMV4 DEBUG=1 ชื่อ "build-firmware" บวก "preLaunchTask": "build-firmware" ในการตั้งค่าด้านบน เพื่อให้ F5 บิลด์ใหม่ แฟลช และเริ่มดีบักเกอร์ในขั้นตอนเดียว
Warning
Cortex-Debug ต้องการ arm-none-eabi-gdb ซึ่งมาพร้อม SDK ที่ ~/openmv-sdk-<version>/gcc/bin แต่ ไม่ได้ อยู่ใน PATH โดยค่าเริ่มต้น ทำให้การดีบักล้มเหลวด้วยข้อความ "GDB executable 'arm-none-eabi-gdb' was not found" แก้ไขได้โดยตั้งค่า armToolchainPath / gdbPath ดังที่แสดงด้านบน หรือเพิ่ม ~/openmv-sdk-<version>/gcc/bin เข้า PATH (printenv PATH ควรแสดงขึ้นมา)
14.1.1.4.4. มุมมองรีจิสเตอร์อุปกรณ์ต่อพ่วง (SVD)¶
ชี้ Cortex-Debug ไปที่ไฟล์ CMSIS SVD เพื่อรับมุมมองรีจิสเตอร์อุปกรณ์ต่อพ่วงที่ถอดรหัสแล้ว (ตัวจับเวลา, DMA, อินเทอร์เฟซกล้อง ฯลฯ) ตามชื่อและ bitfield:
"svdFile": "/path/to/STM32H743.svd"
สำหรับ STM32 และ MIMXRT ให้รับ SVD จาก ST / NXP CMSIS pack หรือ Cortex-Debug SVD registry SVD ของ Alif อยู่ใน firmware repo ที่ lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (ใช้ ..._CM55_HP_View.svd สำหรับ AE3 HP core)
14.1.1.4.5. Windows: บริดจ์ WSL ↔ Windows J-Link¶
WSL 2 ไม่สามารถมองเห็นดีไวซ์ USB ของ J-Link โดยตรง ดังนั้นการแบ่งคือ: Windows ให้บริการ probe (ที่เสียบอยู่) และ VS Code + gdb รันใน WSL แล้วเชื่อมต่อผ่าน TCP
บน Windows ติดตั้ง Segger J-Link pack และเสียบ J-Link เข้าพอร์ต USB ของ Windows
บน Windows เริ่ม J-Link Remote Server (มาพร้อม J-Link pack): เปิดใช้งานโดยมี J-Link เสียบอยู่แล้วคลิก OK อนุญาตผ่าน Windows firewall เมื่อถูกถาม หน้าต่างจะแสดง IP address ที่ให้บริการ probe -- จดไว้
ใน WSL บิลด์ด้วย
DEBUG=1และตรวจสอบว่าarm-none-eabi-gdbเข้าถึงได้ (ตั้งarmToolchainPathดังด้านบน)ใน WSL VS Code คง
servertype: "jlink"ไว้ -- GDB server รันใน WSL และเชื่อมต่อ probe ผ่าน Remote Server -- และเพิ่มserverpath+ipAddress{ "name": "OpenMV J-Link (Windows host)", "type": "cortex-debug", "request": "launch", "cwd": "${workspaceFolder}", "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf", "servertype": "jlink", "serverpath": "/opt/SEGGER/JLink/JLinkGDBServer", "ipAddress": "192.168.x.x", "device": "STM32H743VI", "interface": "swd", "runToEntryPoint": "main", "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin" }
ตั้ง
ipAddressเป็นที่อยู่ที่หน้าต่าง Remote Server แสดง นั่นคือสิ่งที่บริดจ์ทั้งหมดต้องการ
Tip
ทางเลือกแทน GDB-server bridge: usbipd-win แทนที่จะรัน server บน Windows คุณสามารถแนบดีไวซ์ USB ของ J-Link เข้า WSL โดยตรงด้วย usbipd-win จาก PowerShell แบบ administrator:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> คือ bus ID ของ J-Link จาก usbipd list) probe จะปรากฏ ภายใน WSL และคุณใช้การตั้งค่า servertype: "jlink" แบบเครื่องเดียวกันจาก VS Code Cortex-Debug setup โดยไม่ต้องระบุ IP address และไม่ต้องมี Windows server แยก บริดจ์ GDB-server ต้องการการตั้งค่าน้อยกว่าสำหรับการใช้งานเป็นครั้งคราว ส่วน usbipd-win สะดวกกว่าสำหรับการพัฒนาประจำ
Tip
ใช้ "request": "attach" เพื่อดีบักเฟิร์มแวร์ ขณะที่กำลังรันอยู่แล้ว โดยไม่รีเซ็ตหรือแฟลชใหม่ -- เหมาะสำหรับการตรวจสอบการค้างในภาคสนาม ใช้ "request": "launch" เพื่อรีเซ็ต แฟลช ELF และเริ่มใหม่ที่ runToEntryPoint
14.1.1.4.6. การดีบักด้วย command line โดยใช้ gdbrunner¶
การตั้งค่า GDB session กับ embedded target ด้วยมือเป็นขั้นตอนห้าขั้น: เริ่ม J-Link / ST-Link GDB server ในหน้าต่างหนึ่งด้วยค่า device, port และ interface flag ที่ถูกต้อง; รอให้มันพิมพ์ Waiting for GDB connection; รัน arm-none-eabi-gdb ในหน้าต่างที่สอง; พิมพ์ target remote localhost:<port>; ชี้ gdb ไปที่ ELF เมื่อ gdb session สิ้นสุด ต้องจำปิดหน้าต่าง server ด้วย gdbrunner เป็น CLI ขนาดเล็กที่รวมทุกอย่างเข้าเป็นคำสั่งเดียวในเบื้องหน้า มาพร้อม Python environment ของ OpenMV SDK จึงไม่ต้องติดตั้งอะไร; จุดเข้าใช้งานปกติคือ make debug target ของ firmware repository:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
คำสั่งนี้รัน gdbrunner พร้อมอาร์กิวเมนต์ debugger จากการตั้งค่าของบอร์ด -- ชื่อดีไวซ์ J-Link และ external flash loader ของ ST-Link ในกรณีที่จำเป็น -- โดยมี arm-none-eabi-gdb ของ SDK อยู่ใน PATH แล้ว backend เริ่มต้นคือ J-Link; make DEBUGGER=STLINK debug ใช้งานได้กับ ST-Link probe แทน
gdbrunner ยังสามารถเรียกใช้โดยตรงได้ (นอก SDK, pip install gdbrunner):
gdbrunner jlink --device STM32H743VI build/OPENMV4/bin/firmware.elf
gdbrunner stlink build/OPENMV4/bin/firmware.elf
gdbrunner qemu --machine mps2-an500 build/MPS2_AN500/bin/firmware.elf
อาร์กิวเมนต์ตำแหน่งแรกเลือก server backend (jlink, stlink, qemu); ส่วนที่เหลือส่งต่อไปยัง backend นั้น พร้อมค่าเริ่มต้นที่ใช้ได้กับ OpenMV cam gdbrunner --help แสดงรายการ flag แต่ละ backend แบบครบถ้วน; ตาราง argument ของแต่ละ backend ขับเคลื่อนด้วย JSON (src/gdbrunner/backends.json) ดังนั้นการเพิ่ม server ใหม่เป็นเพียงการแก้ไขการตั้งค่าไม่ใช่โค้ด
สิ่งที่ gdbrunner ทำสำหรับงาน command line:
กระบวนการเดียว วงชีวิตสะอาด Server เริ่มต้น gdb เชื่อมต่อเมื่อพอร์ตเปิด server ถูกปิดอย่างสะอาดเมื่อ gdb ออก ไม่มี
JLinkGDBServerค้างหลังจาก session จบ ไม่ต้องจัดการสองเทอร์มินัลการค้นหา STM32CubeProgrammer อัตโนมัติ backend
stlinkค้นหาในตำแหน่งติดตั้งปกติ (~/STM32CubeProgrammer/,/opt/st/, ต้นไม้ plugin ของ STM32CubeIDE) เพื่อหาเครื่องมือ STM32CubeProgrammer ดังนั้นไม่ต้องพิมพ์ path ยาว--cube-progทุกครั้ง SDK มีสำเนาของตัวเองที่~/openmv-sdk-<version>/stcubeprog/bin-- ชี้--cube-progไปที่นั่นหากไม่มีการติดตั้งในระบบรองรับ gdbinit ต่อโปรเจกต์ ไฟล์
.gdbinitในไดเรกทอรีปัจจุบันถูกโหลดด้วย-ix-- แทนที่~/.gdbinitทั่วทั้งผู้ใช้ -- ดังนั้น gdb scripting ต่อโปรเจกต์ (pretty-printer, macro เฉพาะบอร์ด, ชุด breakpoint) ใช้งานได้โดยเพียงแค่มีในไดเรกทอรีทำงานmake debugรันจาก root ของ repository ดังนั้น.gdbinitที่นั่นจะมีผลDry run
--dryrunพิมพ์คำสั่ง server โดยไม่รัน มีประโยชน์สำหรับการปรับการเรียกใช้ให้เข้ากับ wrapper script คัดลอกไปยัง IDE launcher config หรือเพียงตรวจสอบอาร์กิวเมนต์ที่ gdbrunner สร้างขึ้นแสดงผลลัพธ์ server
--show-outputคง stdout / stderr ของ server ให้มองเห็น โดยค่าเริ่มต้นจะซ่อนไว้ (เพื่อให้ UI ของ gdb สะอาด); เปิด flag นี้เมื่อ server เองมีปัญหาQEMU backend
qemu-system-armดีบัก firmware build โดยไม่ต้องเสียบบอร์ด เป้าหมายMPS2_AN500เลือก backend นี้ในการตั้งค่าบอร์ด ดังนั้นmake TARGET=MPS2_AN500 DEBUG=1 debugบิลด์สำหรับเครื่องmps2-an500ของ QEMU และรันทีละขั้นโค้ดที่ไม่ขึ้นกับแพลตฟอร์ม -- ทุกอย่างที่ไม่ใช้ peripheral เฉพาะของกล้อง -- ในการจำลอง (qemu-system-armติดตั้งบน host ไม่ใช่ส่วนหนึ่งของ SDK)
สำหรับการรันทีละขั้นระดับ source code พร้อม breakpoint gutter และมุมมองรีจิสเตอร์อุปกรณ์ต่อพ่วง การตั้งค่า VS Code Cortex-Debug ด้านบนเป็นเครื่องมือที่ดีกว่า; gdbrunner เหมาะสำหรับทุกอย่างที่อยู่ใน command line
14.1.1.4.7. การใช้ดีบักเกอร์¶
เมื่อ session กำลังรัน (โปรเซสเซอร์หยุดที่ main):
Breakpoints -- คลิกที่ gutter ถัดจากบรรทัด C หรือใน Debug Console
break <file>:<line>/break <function>คอร์ Cortex-M มี hardware breakpoint comparator จำนวนน้อย (โดยทั่วไป 6--8 ใน M7 / H7, 8 ใน M55) หากเกินจำนวนนั้นบนโค้ดใน flash จะล้มเหลวโดยไม่แจ้ง -- รักษาจำนวน breakpoint ที่ใช้งานอยู่ให้พอเหมาะการรันทีละขั้น -- F10 ข้ามบรรทัด (
next), F11 เข้าไปข้างใน (step), Shift+F11 ออก (finish), F5 ดำเนินต่อ การรันทีละคำสั่งใช้stepi/nextiใน Debug Consoleตัวแปร / watch / call stack -- แผง Variables และ Call Stack แสดงตัวแปรท้องถิ่นและ backtrace; เพิ่มนิพจน์ใน Watch วางเมาส์บนตัวแปรใน source เพื่อดูค่า ทุกอย่างที่แสดง
<optimized out>หมายความว่าคุณไม่ได้บิลด์ด้วยDEBUG=1Watchpoints (data breakpoints) --
watch <expr>หยุดเมื่อตัวแปรถูกเขียนrwatchเมื่ออ่านawatchทั้งสองกรณี หน่วย Cortex-M DWT รองรับ ~4 hardware watchpoint -- มีประโยชน์มากในการหาว่า ใคร ทำให้ตัวแปรเสียหายรีจิสเตอร์และอุปกรณ์ต่อพ่วง -- มุมมอง Cortex Registers แสดง core register; เมื่อตั้ง
svdFileมุมมอง Peripherals จะถอดรหัสรีจิสเตอร์และ bitfield ของอุปกรณ์ต่อพ่วงทุกตัว (DMA, ตัวจับเวลา, อินเทอร์เฟซกล้อง / CSI, XSPI ฯลฯ) -- วิธีที่เร็วที่สุดในการดูว่าทำไม driver ถึงทำงานผิดปกติMemory -- ใช้ตัวดูหน่วยความจำของ Cortex-Debug หรือ gdb
x/เพื่อตรวจสอบบัฟเฟอร์เฟรม, DMA buffer และโครงสร้างข้อมูลโดยตรงprintf โดยไม่หยุด (SWO/RTT) -- สำหรับปัญหาที่เกี่ยวกับเวลา Segger RTT หรือ SWO ให้
printfแบบ overhead แทบเป็นศูนย์ขณะเป้าหมายรัน บิลด์ด้วยDEBUG_PRINTF=1และเพิ่มrttConfig(RTT) หรือswoConfig(SWO ต้องการ core clock) ของ Cortex-Debug นี่คือเครื่องมือที่เหมาะเมื่อ breakpoint จะเปลี่ยนแปลง timing ที่กำลังสังเกตอยู่การตัดการเชื่อมต่อ -- Stop บน session
launchหยุดเป้าหมาย; Disconnect บน sessionattachปล่อยให้กล้องทำงานต่อ รีสตาร์ทกล้องด้วยการถอดไฟเพื่อกลับสู่การทำงานปกติ
14.1.1.4.8. ปัญหาที่พบบ่อยในการดีบัก¶
ตัวแปรที่ถูก optimize ออก ทุกอย่างแสดง
<optimized out>-- คุณบิลด์ด้วยDEBUG=0บิลด์ใหม่ด้วยDEBUG=1"GDB executable not found" --
gcc/binของ SDK ไม่อยู่ในPATH; ตั้งarmToolchainPath/gdbPath"Cannot connect" / wrong memory map -- ชื่อ
deviceผิดหรือขาดหายไป; ใช้สตริงตรงตามตารางBreakpoints ไม่หยุดโดยไม่แจ้ง -- hardware breakpoint บนโค้ดที่อยู่ใน flash มีมากเกินไป; ลดจำนวนลง
Source path ไม่ตรงกัน (ELF ที่บิลด์ด้วย Docker) -- บิลด์ด้วย Docker target
build-firmware-dev(path สัมบูรณ์เดียวกันทั้งใน container และนอก container) หรือตั้ง gdbset substitute-path