14.1.1.4. ファームウェアのデバッグ¶
オンハードウェアデバッグとは、プロセッサを停止し、Cソースにブレークポイントを設定し、シングルステップ実行を行い、変数、メモリ、レジスタ、ペリフェラルを検査することを意味します。すべてVS Code内から行えます。これには3つのものが必要です。デバッグビルド、SWDデバッグプローブ(Segger J-Link)、そしてJ-Link GDBサーバーに対して arm-none-eabi-gdb を駆動する Cortex-Debug 拡張機能です。
14.1.1.4.1. デバッグ用にビルドする¶
ターゲットは常に DEBUG=1 で再ビルドしてください:
make -j$(nproc) TARGET=<TARGET> DEBUG=1
リリース(DEBUG=0)イメージは -O2 でコンパイルされます。デバッガでは多くの変数について <optimized out> が表示され、インライン化された関数は呼び出し元に統合され、ステップ実行は予測できない形で飛び回ります。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. ハードウェア: SWD経由のJ-Link¶
Segger J-Linkをカメラの SWD ピン(SWDIO、SWCLK、GND、および参照用のターゲットVCC。カメラは通常どおりUSB経由で給電されます)に接続します。J-Link EDU / Base / Proのいずれも動作します。デバッグピンがどこに出ているかはカメラごとに異なります。多くのボードには専用のJTAG/SWDコネクタがあり、他のボードではI/OヘッダーやテストパッドにSWDが出ています。そのため、どのピンを配線するかについては、OpenMVハードウェアドキュメントでそのボードのピン配置図と回路図を確認してください。プローブを物理的に接続するマシンに、segger.com から J-Link Software and Documentation Pack をインストールしてください。比較的新しいバージョンを保ってください。古いJ-Linkソフトウェアは新しいデバイス名(STM32N6、MIMXRT、Alif)を認識しません。
各MCUには、プローブが正しいフラッシュローダーとメモリマップを読み込めるように、正確なJ-Link デバイス名 が必要です:
カメラ( |
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 を作成します。最も単純なケース、つまりVS Code、J-Link、ビルドがすべて 同じ Linux / macOSマシン上にある場合は servertype: "jlink" を使用します。これによりCortex-Debug自身がJ-Link GDBサーバーを起動します:
{
"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
デバッグを開始するたびに自動的に再ビルドするには、.vscode/tasks.json にビルドタスクを追加し、起動構成から "preLaunchTask" で参照します。例えば、make -j$(nproc) TARGET=OPENMV4 DEBUG=1 を実行する "build-firmware" という名前のタスクと、上記の構成に "preLaunchTask": "build-firmware" を追加すると、F5 で再ビルド、フラッシュ、デバッガの起動が1ステップで行われます。
警告
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、カメラインターフェースなど)を名前とビットフィールド単位でデコードしたビューが得られます:
"svdFile": "/path/to/STM32H743.svd"
STM32とMIMXRTの場合、SVDはST / NXPのCMSISパックまたはCortex-Debug SVDレジストリから入手します。Alif SVDはファームウェアリポジトリの lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ にベンダリングされています(AE3 HPコアには ..._CM55_HP_View.svd を使用します)。
14.1.1.4.5. Windows: WSL ↔ Windows J-Linkブリッジ¶
WSL 2はJ-LinkのUSBデバイスを直接認識できないため、構成は次のように分割されます。Windowsがプローブを提供し(プローブが接続されている場所)、VS Code + gdbはWSL内で動作し、TCP経由でプローブにアクセスします。
Windows側で、Segger J-Linkパックをインストールし、J-LinkをWindowsのUSBポートに接続します。
Windows側で、J-Link Remote Server(J-Linkパックに同梱)を起動します。J-Linkを接続した状態で起動し、OK をクリックします。プロンプトが表示されたら、Windowsファイアウォールを通過させます。ウィンドウにはプローブをサービスしている IPアドレス が表示されるので、メモしておきます。
WSL側で、
DEBUG=1でビルドし、arm-none-eabi-gdbに到達できることを確認します(上記のようにarmToolchainPathを設定)。WSL VS Code側で、
servertype: "jlink"のままにします。GDBサーバーはWSL内で動作し、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サーバーブリッジの代替手段: usbipd-win。 Windows上でサーバーを実行する代わりに、usbipd-win を使ってJ-LinkのUSBデバイスを直接WSLにアタッチできます。管理者権限のPowerShellから:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> は usbipd list から得られるJ-LinkのバスIDです。)これでプローブがWSLの 内部 に現れ、VS Code Cortex-Debug setup の通常の同一マシン用 servertype: "jlink" 構成を、IPアドレスや別途のWindowsサーバーなしで使用できます。GDBサーバーブリッジは時々使う分にはセットアップが少なくて済み、usbipd-winは日常的な開発により便利です。
Tip
ファームウェアをリセットや再フラッシュをせずに すでに実行中の状態のまま デバッグするには "request": "attach" を使用します。これは現場でのハングを捕捉するのに最適です。リセットしてELFをフラッシュし、runToEntryPoint から新たに開始するには "request": "launch" を使用します。
14.1.1.4.6. gdbrunnerによるコマンドラインデバッグ¶
組み込みターゲットに対してGDBセッションを手作業でセットアップするのは5ステップの作業です。正しいデバイス、ポート、インターフェースフラグを指定して1つのウィンドウでJ-Link / ST-Link GDBサーバーを起動し、Waiting for GDB connection が表示されるのを待ち、2つ目のウィンドウで arm-none-eabi-gdb を実行し、target remote localhost:<port> と入力し、gdbにELFを指定します。gdbセッションが終了したら、サーバーウィンドウを終了させるのを忘れないようにします。gdbrunner は、これらすべてを1つのフォアグラウンドコマンドにまとめる小さなCLIです。OpenMV SDKのPython環境に同梱されているため、インストールするものはありません。通常のエントリポイントはファームウェアリポジトリの make debug ターゲットです:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
これはボードの構成からデバッガ引数(J-Linkデバイス名、および必要に応じてST-Link外部フラッシュローダー)を使ってgdbrunnerを実行し、SDKの arm-none-eabi-gdb がすでに PATH 上にある状態にします。デフォルトのバックエンドはJ-Linkです。make DEBUGGER=STLINK debug を使えば、代わりにST-Linkプローブで動作します。
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
最初の位置引数でサーバーバックエンド(jlink、stlink、qemu)を選択し、残りはそのバックエンドに転送されます。デフォルト値はOpenMVカメラで動作するように設定されています。gdbrunner --help でバックエンドごとの完全なフラグ一覧が表示されます。各バックエンドの引数テーブルはJSON駆動(src/gdbrunner/backends.json)なので、新しいサーバーの追加はコードではなく設定の編集で済みます。
gdbrunnerがコマンドライン作業のために行うこと:
1つのプロセス、クリーンなライフサイクル。 サーバーが起動し、ポートが開くとgdbがアタッチし、gdbが終了するとサーバーがクリーンに終了します。セッションを生き延びる孤児の
JLinkGDBServerも、管理すべき2つのターミナルもありません。STM32CubeProgrammerの自動検出。
stlinkバックエンドはSTM32CubeProgrammerツールを通常のインストール場所(~/STM32CubeProgrammer/、/opt/st/、STM32CubeIDEプラグインツリー)から検索するので、長い--cube-progパスを毎回入力する必要がありません。SDKは独自のコピーを~/openmv-sdk-<version>/stcubeprog/binにバンドルしています。システムインストールが存在しない場合は--cube-progをそこに指定してください。プロジェクトごとのgdbinitを尊重。 カレントディレクトリの
.gdbinitは-ixで読み込まれ、ユーザー全体の~/.gdbinitを上書きします。そのため、プロジェクトごとのgdbスクリプト(プリティプリンタ、ボード固有のマクロ、ブレークポイントセット)は作業ディレクトリに存在させるだけで適用されます。make debugはリポジトリのルートから実行されるので、そこにある.gdbinitが適用されます。ドライラン。
--dryrunは実行せずにサーバーコマンドを表示します。呼び出しをラッパースクリプトに適応させたり、IDEの起動構成にコピーしたり、gdbrunnerが構成している引数を確認したりするのに便利です。サーバー出力の表示。
--show-outputはサーバーのstdout / stderrを表示し続けます。デフォルトではこれを抑制します(gdbのUIをきれいに保つため)。サーバー自体が誤動作している場合はこのフラグを切り替えてください。QEMUバックエンド。
qemu-system-armは、ボードを接続せずにファームウェアビルドをデバッグします。MPS2_AN500ターゲットはボード構成でこのバックエンドを選択するので、make TARGET=MPS2_AN500 DEBUG=1 debugはQEMUのmps2-an500マシン向けにビルドし、プラットフォーム非依存のコード、つまりカメラ固有のペリフェラルに触れないすべてのコードを、飛行機の中でもステップ実行できます。(qemu-system-armはホストにインストールするもので、SDKの一部ではありません。)
ブレークポイントのガター表示とペリフェラルレジスタビューを伴うソースレベルのステップ実行には、上記のVS Code Cortex-Debugセットアップの方が優れたツールです。gdbrunnerは、コマンドラインで完結するすべての作業に適したツールです。
14.1.1.4.7. デバッガの使用¶
セッションが実行中(プロセッサが main で停止している状態)になったら:
ブレークポイント -- Cの行の横のガターをクリックするか、Debug Consoleで
break <file>:<line>/break <function>を実行します。Cortex-Mコアには少数の ハードウェア ブレークポイントコンパレータがあります(M7 / H7では通常6~8個、M55では8個)。フラッシュ上のコードでこれを超えると静かに失敗するので、アクティブなブレークポイントの数は控えめに保ってください。ステップ実行 -- F10 ステップオーバー(
next)、F11 ステップイン(step)、Shift+F11 ステップアウト(finish)、F5 続行。命令レベルのステップ実行はDebug Consoleでのstepi/nextiです。変数 / ウォッチ / コールスタック -- Variables と Call Stack ペインはローカル変数とバックトレースを表示します。式は Watch に追加します。ソース内で変数にカーソルを合わせるとその値が表示されます。
<optimized out>と表示されているものは、DEBUG=1ビルドではないことを意味します。ウォッチポイント(データブレークポイント) --
watch <expr>は変数が書き込まれたときに停止し、rwatchは読み込み時、awatchはいずれの場合にも停止します。Cortex-MのDWTユニットは約4個のハードウェアウォッチポイントをサポートします。誰が 変数を破壊したのかを捕捉するのに非常に有用です。レジスタとペリフェラル -- Cortex Registers ビューはコアレジスタを表示します。
svdFileが設定されていると、Peripherals ビューはすべてのペリフェラルレジスタとビットフィールド(DMA、タイマー、カメラ / CSIインターフェース、XSPIなど)をデコードします。ドライバがなぜ誤動作しているのかを見る最速の方法です。メモリ -- Cortex-Debugのメモリビューアまたはgdbの
x/を使って、フレームバッファ、DMAバッファ、構造体を直接検査します。停止せずにprintf(SWO/RTT) -- タイミングに敏感な問題には、Seggerの RTT または SWO が、ターゲットの実行中にほぼゼロオーバーヘッドの
printfを提供します。DEBUG_PRINTF=1でビルドし、Cortex-DebugのrttConfig(RTT)またはswoConfig(SWO、コアクロックが必要)を追加します。これは、ブレークポイントが観測しようとしているタイミングを変えてしまうような場合に適したツールです。切断 --
launchセッションでの Stop はターゲットを停止します。attachセッションでの Disconnect はカメラを実行したままにします。その後、カメラを電源再投入して通常動作に戻してください。
14.1.1.4.8. デバッグの落とし穴¶
最適化で消えた変数。 すべてが
<optimized out>と表示される場合、DEBUG=0でビルドしています。DEBUG=1で再ビルドしてください。"GDB executable not found" -- SDKの
gcc/binがPATH上にありません。armToolchainPath/gdbPathを設定してください。"Cannot connect" / 誤ったメモリマップ --
device名が間違っているか欠落しています。表の正確な文字列を使用してください。ブレークポイントが静かにヒットしない -- フラッシュ常駐コード上のハードウェアブレークポイントが多すぎます。減らしてください。
ソースパスが一致しない(DockerでビルドしたELF) -- Dockerの
build-firmware-devターゲットでビルドする(コンテナ内外で同じ絶対パスになる)か、gdbのset substitute-pathを設定してください。