14.1.1.4. Gỡ lỗi firmware¶
Gỡ lỗi trực tiếp trên phần cứng có nghĩa là tạm dừng bộ xử lý, đặt điểm dừng trong mã nguồn C, chạy từng bước, và kiểm tra các biến, bộ nhớ, thanh ghi và ngoại vi -- từ bên trong VS Code. Điều này cần ba thứ: một bản dựng debug, một đầu dò debug SWD (Segger J-Link), và tiện ích mở rộng Cortex-Debug điều khiển arm-none-eabi-gdb kết nối với J-Link GDB server.
14.1.1.4.1. Xây dựng bản dựng để gỡ lỗi¶
Luôn xây dựng lại mục tiêu với DEBUG=1
make -j$(nproc) TARGET=<TARGET> DEBUG=1
Một ảnh release (DEBUG=0) được biên dịch với -O2; trong trình gỡ lỗi bạn sẽ thấy <optimized out> cho nhiều biến, các hàm nội tuyến hợp nhất vào phần gọi chúng, và việc chạy từng bước nhảy lung tung khó lường. DEBUG=1 xây dựng với -Og -ggdb3, có thể gỡ lỗi trong khi vẫn khởi động được trên camera. File ELF mà bạn chỉ cho trình gỡ lỗi là:
build/<TARGET>/bin/firmware.elf
(Với Alif AE3, hãy gỡ lỗi build/OPENMV_AE3/bin/firmware_M55_HP.elf -- nhân hiệu năng cao.)
14.1.1.4.2. Phần cứng: J-Link qua SWD¶
Kết nối Segger J-Link vào các chân SWD của camera (SWDIO, SWCLK, GND, và VCC mục tiêu để tham chiếu; camera được cấp nguồn qua USB như thường lệ). J-Link EDU / Base / Pro đều hoạt động. Vị trí các chân debug khác nhau tùy camera -- nhiều bo mạch có đầu nối JTAG/SWD riêng, một số khác lộ SWD qua header I/O hoặc trên các pad kiểm tra -- vì vậy hãy kiểm tra sơ đồ chân và schematic của bo mạch đó trong tài liệu phần cứng OpenMV để biết chân nào cần nối. Cài đặt J-Link Software and Documentation Pack từ segger.com trên máy mà đầu dò được cắm vào trực tiếp. Hãy giữ nó ở phiên bản khá mới -- phần mềm J-Link cũ sẽ không nhận biết các tên thiết bị mới hơn (STM32N6, MIMXRT, Alif).
Mỗi MCU cần tên thiết bị J-Link chính xác để đầu dò tải đúng flash loader và bản đồ bộ nhớ:
Camera ( |
MCU |
J-Link |
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
14.1.1.4.3. Thiết lập Cortex-Debug trong VS Code¶
Tạo .vscode/launch.json trong kho lưu trữ. Trường hợp đơn giản nhất -- VS Code, J-Link và bản dựng đều trên cùng một máy Linux / macOS -- sử dụng servertype: "jlink", khiến Cortex-Debug tự khởi động 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"
}
]
}
Thay đổi executable và device cho bo mạch của bạn (xem bảng trên). Nhấn F5 để build-flash-and-run đến main và dừng tại đó.
Mẹo
Để tự động xây dựng lại mỗi khi bạn bắt đầu gỡ lỗi, hãy thêm một tác vụ build vào .vscode/tasks.json và tham chiếu từ cấu hình launch với "preLaunchTask". Ví dụ một tác vụ chạy make -j$(nproc) TARGET=OPENMV4 DEBUG=1, đặt tên "build-firmware", cộng với "preLaunchTask": "build-firmware" trong cấu hình trên, để F5 tự xây dựng lại, nạp flash và khởi động trình gỡ lỗi chỉ trong một bước.
Cảnh báo
Cortex-Debug cần arm-none-eabi-gdb. Nó được đi kèm trong SDK tại ~/openmv-sdk-<version>/gcc/bin nhưng không có trong PATH mặc định, nên việc gỡ lỗi thất bại với thông báo "GDB executable 'arm-none-eabi-gdb' was not found". Khắc phục bằng cách đặt armToolchainPath / gdbPath như đã chỉ ra ở trên, hoặc thêm ~/openmv-sdk-<version>/gcc/bin vào PATH của bạn (printenv PATH sau đó phải liệt kê nó).
14.1.1.4.4. Xem thanh ghi ngoại vi (SVD)¶
Trỏ Cortex-Debug vào file CMSIS SVD để xem thanh ghi ngoại vi đã giải mã (bộ định thời, DMA, giao diện camera, v.v.) theo tên và trường bit:
"svdFile": "/path/to/STM32H743.svd"
Với STM32 và MIMXRT, lấy SVD từ các gói ST / NXP CMSIS hoặc registry SVD của Cortex-Debug. Các SVD của Alif được đưa vào kho firmware tại lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (dùng ..._CM55_HP_View.svd cho nhân AE3 HP).
14.1.1.4.5. Windows: cầu nối J-Link WSL ↔ Windows¶
WSL 2 không thể nhìn thấy thiết bị USB của J-Link trực tiếp, nên cách chia là: Windows phục vụ đầu dò (nơi nó được cắm vào) và VS Code + gdb chạy trong WSL và kết nối qua TCP.
Trên Windows, cài đặt gói Segger J-Link và cắm J-Link vào cổng USB Windows.
Trên Windows, khởi động J-Link Remote Server (đi kèm với gói J-Link): khởi chạy nó khi J-Link đã được gắn và nhấp OK. Cho phép nó qua tường lửa Windows khi được nhắc. Cửa sổ hiển thị địa chỉ IP mà nó đang phục vụ đầu dò -- hãy ghi lại.
Trong WSL, xây dựng với
DEBUG=1và đảm bảoarm-none-eabi-gdbcó thể truy cập được (đặtarmToolchainPathnhư trên).Trong WSL VS Code, giữ
servertype: "jlink"-- GDB server chạy trong WSL và kết nối đến đầu dò qua Remote Server -- và thêmserverpath+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" }
Đặt
ipAddressthành địa chỉ mà cửa sổ Remote Server hiển thị. Đó là toàn bộ cầu nối.
Mẹo
Phương án thay thế cho cầu nối GDB-server: usbipd-win. Thay vì chạy server trên Windows, bạn có thể gắn thiết bị USB của J-Link thẳng vào WSL với usbipd-win. Từ PowerShell với quyền quản trị viên:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> là bus ID của J-Link từ usbipd list.) Đầu dò sau đó xuất hiện bên trong WSL, và bạn dùng cấu hình servertype: "jlink" trên cùng một máy từ VS Code Cortex-Debug setup không cần địa chỉ IP và không cần server Windows riêng. Cầu nối GDB-server yêu cầu ít thiết lập hơn cho sử dụng không thường xuyên; usbipd-win thuận tiện hơn cho phát triển thường ngày.
Mẹo
Dùng "request": "attach" để gỡ lỗi firmware khi nó đang chạy mà không reset hoặc nạp flash lại -- lý tưởng để bắt lỗi treo trong thực tế. Dùng "request": "launch" để reset, nạp ELF lên flash và khởi động mới tại runToEntryPoint.
14.1.1.4.6. Gỡ lỗi dòng lệnh với gdbrunner¶
Thiết lập phiên GDB cho một mục tiêu nhúng theo cách thủ công là một quy trình năm bước: khởi động J-Link / ST-Link GDB server trong một cửa sổ với thiết bị, cổng và cờ giao diện đúng; chờ nó in Waiting for GDB connection; chạy arm-none-eabi-gdb trong cửa sổ thứ hai; gõ target remote localhost:<port>; trỏ gdb vào file ELF. Khi phiên gdb kết thúc, nhớ tắt cửa sổ server. gdbrunner là một CLI nhỏ gọn lại tất cả những điều đó thành một lệnh tiền cảnh duy nhất. Nó đi kèm trong môi trường Python của OpenMV SDK, nên không cần cài đặt gì thêm; điểm vào thông thường là mục tiêu make debug của kho firmware:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
Lệnh này chạy gdbrunner với các đối số trình gỡ lỗi từ cấu hình bo mạch -- tên thiết bị J-Link và, nơi cần thiết, external flash loader của ST-Link -- với arm-none-eabi-gdb của SDK đã có trong PATH. Backend mặc định là J-Link; make DEBUGGER=STLINK debug hoạt động với đầu dò ST-Link thay thế.
gdbrunner cũng có thể được gọi trực tiếp (bên ngoài 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
Đối số vị trí đầu tiên chọn backend server (jlink, stlink, qemu); phần còn lại được chuyển tiếp đến backend đó, với các giá trị mặc định phù hợp với OpenMV cams. gdbrunner --help liệt kê danh sách cờ đầy đủ cho từng backend; bảng đối số của mỗi backend được điều khiển bởi JSON (src/gdbrunner/backends.json), nên việc thêm server mới là chỉnh sửa cấu hình chứ không phải mã.
Những gì gdbrunner làm cho công việc dòng lệnh:
Một tiến trình, vòng đời sạch sẽ. Server khởi động, gdb gắn vào khi cổng mở, server được kết thúc gọn gàng khi gdb thoát. Không có
JLinkGDBServerbỏ lại sau phiên, không có hai terminal cần quản lý.Tự động phát hiện STM32CubeProgrammer. Backend
stlinktìm kiếm các vị trí cài đặt thông thường (~/STM32CubeProgrammer/,/opt/st/, cây plugin STM32CubeIDE) để tìm công cụ STM32CubeProgrammer, nên không cần gõ đường dẫn--cube-progdài mỗi lần. SDK đi kèm bản sao riêng tại~/openmv-sdk-<version>/stcubeprog/bin-- trỏ--cube-progvào đó nếu không có cài đặt hệ thống.Tôn trọng gdbinit theo dự án. Một
.gdbinittrong thư mục hiện tại được nạp với-ix-- ghi đè~/.gdbinittoàn cục -- nên các script gdb theo dự án (pretty-printer, macro dành cho bo mạch, tập điểm dừng) hoạt động bằng cách có mặt trong thư mục làm việc.make debugchạy từ gốc kho lưu trữ, nên.gdbinitở đó được áp dụng.Chạy thử.
--dryrunin ra lệnh server mà không chạy nó, hữu ích để điều chỉnh lệnh gọi thành script bao ngoài, sao chép vào cấu hình launcher IDE, hoặc chỉ để kiểm tra những đối số mà gdbrunner đang tổ hợp.Hiển thị đầu ra server.
--show-outputgiữ stdout / stderr của server hiển thị. Mặc định ẩn nó (để giao diện gdb giữ sạch sẽ); bật cờ này khi chính server đang gặp sự cố.Backend QEMU.
qemu-system-armgỡ lỗi một bản dựng firmware mà không cần cắm bo mạch vào. Mục tiêuMPS2_AN500chọn backend này trong cấu hình bo mạch, nênmake TARGET=MPS2_AN500 DEBUG=1 debugxây dựng cho máymps2-an500của QEMU và chạy từng bước mã độc lập nền tảng -- mọi thứ không chạm đến ngoại vi dành riêng cho cam -- trên không trung. (qemu-system-armlà cài đặt máy chủ, không phải một phần của SDK.)
Để chạy từng bước mức mã nguồn với rãnh điểm dừng và xem thanh ghi ngoại vi, thiết lập VS Code Cortex-Debug ở trên là công cụ tốt hơn; gdbrunner là công cụ phù hợp cho mọi thứ hoạt động ở dòng lệnh.
14.1.1.4.7. Sử dụng trình gỡ lỗi¶
Sau khi phiên đang chạy (bộ xử lý đã dừng tại main):
Điểm dừng -- nhấp vào rãnh cạnh dòng C, hoặc trong Debug Console
break <file>:<line>/break <function>. Nhân Cortex-M có một số lượng nhỏ bộ so sánh điểm dừng phần cứng (thường 6--8 trên M7 / H7, 8 trên M55). Vượt quá số đó trên mã trong flash sẽ thất bại âm thầm -- hãy giữ số điểm dừng đang hoạt động ở mức vừa phải.Chạy từng bước -- F10 bước qua (
next), F11 bước vào (step), Shift+F11 bước ra (finish), F5 tiếp tục. Chạy từng bước ở mức lệnh làstepi/nextitrong Debug Console.Biến / watch / call stack -- khung Variables và Call Stack hiển thị biến cục bộ và backtrace; thêm biểu thức vào Watch. Di chuột qua một biến trong mã nguồn để xem giá trị của nó. Bất kỳ thứ gì hiển thị
<optimized out>có nghĩa là bạn không đang dùng bản dựngDEBUG=1.Điểm giám sát (data breakpoint) --
watch <expr>dừng khi một biến bị ghi,rwatchkhi đọc,awatchkhi cả hai. Đơn vị DWT Cortex-M hỗ trợ ~4 điểm giám sát phần cứng -- vô giá để bắt ai đã làm hỏng một biến.Thanh ghi và ngoại vi -- xem Cortex Registers hiển thị các thanh ghi nhân; khi
svdFileđược đặt, xem Peripherals giải mã mọi thanh ghi và trường bit ngoại vi (DMA, bộ định thời, giao diện camera / CSI, XSPI, v.v.) -- cách nhanh nhất để xem tại sao một driver đang hoạt động sai.Bộ nhớ -- sử dụng trình xem bộ nhớ Cortex-Debug hoặc
x/của gdb để kiểm tra bộ đệm khung hình, DMA buffer và các cấu trúc dữ liệu trực tiếp.printf không cần dừng (SWO/RTT) -- với các vấn đề nhạy cảm về thời gian, Segger RTT hoặc SWO cho phép
printfvới chi phí gần bằng không trong khi mục tiêu đang chạy. Xây dựng vớiDEBUG_PRINTF=1và thêmrttConfig(RTT) hoặcswoConfig(SWO, cần xung nhịp nhân) của Cortex-Debug. Đây là công cụ phù hợp khi một điểm dừng sẽ thay đổi thời gian bạn đang cố quan sát.Ngắt kết nối -- Stop trong phiên
launchdừng mục tiêu; Disconnect trong phiênattachđể camera tiếp tục chạy. Tắt nguồn camera để nó trở lại hoạt động bình thường sau đó.
14.1.1.4.8. Các cạm bẫy khi gỡ lỗi¶
Biến bị tối ưu hóa mất. Tất cả đều hiển thị
<optimized out>-- bạn đã xây dựng vớiDEBUG=0. Xây dựng lại vớiDEBUG=1."GDB executable not found" --
gcc/bincủa SDK không có trongPATH; đặtarmToolchainPath/gdbPath."Cannot connect" / bản đồ bộ nhớ sai -- tên
devicesai hoặc thiếu; dùng chính xác chuỗi trong bảng.Điểm dừng âm thầm không được kích hoạt -- quá nhiều điểm dừng phần cứng trên mã cư trú trong flash; hãy giảm bớt chúng.
Đường dẫn nguồn không khớp (ELF xây dựng bằng Docker) -- xây dựng với mục tiêu Docker
build-firmware-dev(cùng đường dẫn tuyệt đối bên trong và bên ngoài container) hoặc đặt gdbset substitute-path.