はじめに
前の記事> STM32CubeMXで生成したFreeRTOSとD言語を連携する方法
前の記事はとにかくプロジェクトを作ってビルドできるしLEDチカらせられるってところまで書いたが、続いてデバッグできるようにしていく。
主に以下について説明していく。
- Visual Studio Codeでのビルド環境を整える
- cortex-debug 使ってブレークポイント張りながらデバッグできるようにする
- Semihosting使ってprintfデバッグできるようにする
必要なもの
-
Visual Studio Code : IDEです
- code-d : D言語のハイライトや補完ができます
-
cortex-debug : ARMマイコンのデバッグを行うための拡張機能です
- STM32F401.svd : デバッグ時のレジスタ定義用のファイルです
- OpenOCD : NucleoボードにくっついているST-Link(デバッガ)をgdbで使えるようにします。
- Zadig : ST-LinkをOpenOCDで使うためのWindows用ドライバをインストールするためのツールです。
※前回の記事で紹介したビルドツール等はインストールされている前提です。
※インストール作業の解説は省略します。
Visual Studio Codeでのビルド環境を整える
インストールだの起動方法だのはもちろん省略します。
ここでは、D言語でARMマイコン開発する際のtasks.json設定やlaunch.json設定について説明していきます。
まずはtasks.jsonです。 ldcはC:/ldc
に、GNU Arm Embedded Toolchainは C:/gcc-arm-none-eabi
に、clang(LLVM)はC:/llvm
に、msys2はC:/msys64
にそれぞれインストールされていて、msys2ではmingw32-makeが使えるようなインストールはあらかじめ済ませている前提でお話しします。
以下のようなtasks.jsonを作成することで、build-os
でC言語側の*.a
ファイル、build-debug
でD言語側をビルドし、そのままdubのpostGenerateCommandsで*.elf
を作成します。なお、llvmはなくても、すべてGNU Arm Embedded Toolchainで代替可能です。頭いい最適化かけてくれそうな気がするのでなんとなくllvm使ってますが、必須じゃありません。
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build-debug",
"type": "dub",
"archType": "arm-none-eabi",
"buildType": "debug",
"compiler": "C:/ldc/bin/ldc2.exe",
"cwd": "${workspaceFolder}"
},
{
"label": "build-os",
"type": "process",
"command": "C:/msys64/mingw64/bin/mingw32-make.exe",
"args": [
"lib",
"-j",
"8",
"CC=C:/llvm/bin/clang.exe --sysroot=C:/gcc-arm-none-eabi/arm-none-eabi --target=arm-none-eabi -fshort-enums -nodefaultlibs ",
"AS=C:/llvm/bin/clang.exe --sysroot=C:/gcc-arm-none-eabi/arm-none-eabi --target=arm-none-eabi -fshort-enums -nodefaultlibs -x assembler-with-cpp",
"LD=C:/gcc-arm-none-eabi/bin/arm-none-eabi-gcc.exe",
"CP=C:/llvm/bin/llvm-objcopy.exe",
"SZ=C:/llvm/bin/llvm-size.exe",
"AR=C:/llvm/bin/llvm-ar.exe",
"ASMOPTS=",
"MKDIR=cmd /C mkdir"
],
"problemMatcher": "$gcc"
}
]
}
code-dのおかげで、tasksで "type": "dub"
が使えます。
"archType": "arm-none-eabi"
をするとdub build --arch=arm-none-eabi
になります。
"buildType": "debug"
としているのは、今回はデバッグすることも目標にしているからで、リリース用のビルド設定ではここが"buildType": "release"
になります。
もちろん、 "type": "dub"
を使わずに、 "type": "process"
にしてcommandやargsの設定でdubを起動するようにしてもいいです。その場合は "problemMatcher": "$dmd"
とするといいでしょう。
build-osのほうはふつうのmakeです。設定内容は前回記事参照。
cortex-debug 使ってブレークポイント張りながらデバッグできるようにする
デバッグするには、launch.jsonが必要です。
ここではD言語はあまり関係がなくて、単純にelfファイルの中にデバッグ情報が含まれていればあとはcortex-debugの設定に従って項目を埋めていくだけでデバッグできるようになります。せいぜい、ちゃんとdub build -b=debug
でビルドされていて、あとは一応dub設定でdebugInfoCを選んだくらいでしょうか。debugInfoCじゃなくてもうまくいくかも。
以下の例はOpenOCDをC:/openocd
にインストールしている前提で書かれています。
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"armToolchainPath": "C:/gcc-arm-none-eabi/bin",
"preLaunchTask": "build-debug",
"executable": "${workspaceFolder}/application.elf",
"device": "STM32F401RE",
"svdFile": "${workspaceRoot}/STM32F401.svd",
"breakAfterReset": true,
"runToEntryPoint": "D_main",
"serverpath": "C:/openocd/bin/openocd.exe",
"configFiles": [
"interface/stlink.cfg",
"target/stm32f4x.cfg"
//"board/st_nucleo_f4.cfg"
],
"postLaunchCommands": ["monitor arm semihosting enable"],
}
]
}
要点をざっくり解説します。
-
"request": "launch"
: すでにOpenOCDが起動しているならattach
としますが、launch
とすることで、勝手にOpenOCDを起動してくれます。 -
"armToolchainPath": "C:/gcc-arm-none-eabi/bin"
: 要するにgdbとobjcopyがどこにあるのかを教えています。パスが通っていれば不要だと思います。 -
"preLaunchTask": "build-debug"
: tasks.jsonでbuild-debugを実行すると*.elf
が作成されるので、これを事前実行するように指示しています。毎回ビルドするのが時間かかっていやだなっていう場合はここを指定しないという手もあります。 -
"executable": "${workspaceFolder}/application.elf"
: build-debugを実行するとこのファイルが生成されます。このファイルを指定することで、デバッグ開始時に自動的にダウンロード1してくれます。 -
"device": "STM32F401RE"
: Nucleo-F401REの使っているマイコンを指定しています。ボードによって指定を変える必要があります。 -
"svdFile": "${workspaceRoot}/STM32F401.svd"
: レジスタの定義ファイルを指定します。この内容は有志が作成していて ココ から自分のマイコンにあったものを選んでダウンロードしておきます。なければあきらめます。なくてもブレークした時にレジスタの状態がわからない程度なので、printfデバッグなどで代替できます。 -
"serverpath": "C:/openocd/bin/openocd.exe"
: OpenOCDをどこにインストールしたか教えてあげます。パスが通っていれば不要だと思います。 -
"configFiles": [ ... ]
: OpenOCDに対して、デバッガとマイコンについてどんな設定をすればいいかをまとめたファイルを指定してあげます。この場合、C:/openocd/scripts/target/stm32f4x.cfg
にSTM32F401RE(STM32F4x系マイコン)の設定を、C:/openocd/scripts/interface/stlink.cfg
にST-Linkのデバッガ設定を記載したファイルがある(OpenOCDインストールだけであらかじめ用意されている)ので、これを指定しています。ちなみに、今回はマイコンとST-Linkが一体化しているNucleoボードを使用しており、C:/openocd/scripts/board/st_nucleo_f4.cfg
などというファイルも実は用意されているので、これでもいいです。 -
"runToEntryPoint": "D_main"
: 実行時、D言語側のD_main
関数にブレークを張ってスタートします。もちろんC言語側のmain
とかにしてもいいですし、スタートアップから見たい場合はアセンブラのReset_Handler
とかにしてもいいでしょう。 -
"postLaunchCommands": ["monitor arm semihosting enable"]
後述のSemihostingを有効にする場合にこれを記載します。
Semihosting使ってprintfデバッグできるようにする
デバッグを行うのに、コードの途中にprintfで標準出力に現在の状態を書き出してみる、というprintfデバッグは昔も今も強力なデバッグ方法の一つです。
Nucleo-F401REボード(ARMマイコンやST-Linkの機能を使う)では、これを実現するのにいくつかの方法があります。
- Semihostingを使う
- UART(シリアル通信)を使う
- SWOを使う
今回は、D言語Wikiに載っている、Semihostingの方法を使用しました。
なお、このやり方は過去にkubo39さんがやっていて、ライブラリ化しているので、それを使うのも手かもしれません。 …が、せいぜい数十行程度のコード追加で済むので、私は適当にD_main関数のうしろあたりに追加しました。
extern (C) void SendSemihostCommand(int command, void* message) @trusted
{
version(LDC)
{
import ldc.llvmasm;
__asm("mov r0, $0; mov r1, $1; bkpt #0xAB", "r,r,~{r0},~{r1}", command, message);
}
else static assert(0);
}
extern (C) int _write_r(void* ptr, int fd, const char* buf, uint cnt)
{
uint[3] message = [
1,
cast(uint)buf,
cnt
];
SendSemihostCommand(0x05, &message[0]);
return cnt;
}
_write_r
というのが、printfやputsの出力先を決定づけるための関数で、これを定義することでprintfやputsといった標準出力への出力関数が使用できるようになります。_write
という関数もありますが、どうやら今回作成したアーキテクチャでは_r版(リエントラント版)が呼ばれるようでした。ホントはリエントラント可能2なように、割禁なりクリティカルセクションなり使用してスレッドセーフで作るのが安全ですが、デバッグ用だしまあいいかなと。
なお、cortex-debugを使ってデバッグをする場合、出力タブの"Adapter Output"というログのところにprintfした内容が出力されます。
まとめ
VSCodeでは、必要な拡張機能(code-d, cortex-debug)をインストールし、tasks.json, launch.jsonを記載することでビルドしてダウンロードしてブレークポイントを張りながらデバッグすることができるようになります。
さらに、Semihostingを使用することで、printfの出力先を設定して、printfデバッグすることもできるようになります。
ビルドしてダウンロードしてデバッグしてprintfする動画を撮影しましたので、ご覧ください。
なお、今回の記事で使用したソースコードは以下のリポジトリ(タグ=v0.2.0)です。
https://github.com/shoo/Nucleo-F401RE-FreeRTOS-Demo/tree/v0.2.0