【PlatformIO】Raspberry Pi PicoをSWDでデバッグする

SWDを使えば、いちいちBootselボタンを押しながらUSBを差し直すとかいう面倒なことをしなくて済むようになります（そして、SWD以外でデバッグしたくなくなる）。PlatformIOでRaspberry Pi Picoを弄ろうと思った時にSWDを使おうと思ったのですが、[公式が用意している](https://docs.platformio.org/en/latest/plus/debug-tools/raspberrypi-swd.html)`raspberrypi-swd`を指定する方法では全くうまくいきませんでした。試行錯誤した結果、以下の方法でうまくいったので、調べた内容の整理も兼ねて共有したいと思います

## 環境

以下の環境で成功しています

| 項目 | バージョン等 |
|:-:|:-:|
| ホストハードウェア  |  Raspberry Pi 4 Model B 4GB |
| OS  | Debian GNU/Linux 12 (64bit) |
| PlatformIO Core  | 6.1.11  |
| OpenOCD  | 0.12.0  |
| VSCode  | 1.83.0  |

## 前提

この記事の説明は以下を前提としています

- 環境にもある通り、Raspberry Pi 4BからRaspberry Pi PicoのSWDを使用したデバッグを行おうとしている
- [Raspberry Pi Pico をセットアップしよう](https://datasheets.raspberrypi.com/pico/getting-started-with-pico-JP.pdf)の6章までを完了している
    - SWDによるデバッグまでできていればよいです（検索すれば、Raspberry Pi Picoのセットアップをわかりやすく説明しているサイトが出てくると思うので、そちらに従ってやってもおそらく問題ないと思います）
- VSCodeに[PlatformIOをインストール](https://docs.platformio.org/en/latest/integration/ide/vscode.html#installation)している

## 設定方法

### 結論

プロジェクト設定ファイル`platformio.ini`を以下のようにして、[VSCodeのデバッグ](https://docs.platformio.org/en/latest/integration/ide/vscode.html#debugging)を行えばよいです

```ini:platformio.ini
[platformio]
default_envs = pico-raspberrypi4

[env:pico-raspberrypi4]
platform = raspberrypi
framework = arduino
board = pico
; バージョンが1.12*, 1.10*のときは、buid_type = debugがビルドエラーになる
platform_packages =
  platformio/toolchain-gccarmnoneeabi@~1.90301.0

; build configuration
build_type = debug

; upload configuration
; -fフラグの順番はこの通りに指定しないとエラーになる
upload_protocol = custom
upload_flags = 
  -s
  /usr/local/share/openocd/scripts
  -f
  interface/raspberrypi-swd.cfg
  -f
  target/rp2040.cfg
  -c
  program $BUILD_DIR/firmware.elf verify reset exit
upload_command = /usr/local/bin/openocd $UPLOAD_FLAGS

; debug configuration
debug_tool = custom
debug_server =
  /usr/local/bin/openocd
  -s
  /usr/local/share/openocd/scripts
  -f
  interface/raspberrypi-swd.cfg
  -f
  target/rp2040.cfg
```

ここで、`debug_server`、`upload_command`、`upload_flags`で使用している以下のパスについては、`openocd`がインストールされているパスに適宜変更してください
```console
/usr/local/bin/openocd
/usr/local/share/openocd/scripts
```

つまり、以下コマンドで該当のパスを確認できますが、
```console
whereis openocd
# 結果例
openocd: /path/to/bin/openocd
```

以下のように、`/usr/local`部分を実際に出てきたパス（`/path/to`）に置き換えてください
```console
/path/to/bin/openocd
/path/to/share/openocd/scripts
```

（Raspberry Pi OSを想定しているので、変更必要ないと思いますが一応、、、）

### 説明

コードのそれぞれについて、どんな意味か軽く説明していこうと思います。興味がなければ読み飛ばしてもらって大丈夫です

#### デバッグに使う環境を指定

デバッグに使われる環境は、`default_envs`が指定されていない場合、[一番上に書かれた環境が選ばれる](https://docs.platformio.org/en/latest/integration/ide/vscode.html#debugging)ので、ここで明示的に指定しています（結論のファイルでは、1環境しかないのであまり意味はないですが、複数環境入る場合を想定しています）

```ini
[platformio]
default_envs = pico-raspberrypi4
```

#### 使用パッケージの指定

これは別問題ですが、[デフォルトで指定されているバージョン](https://github.com/platformio/platform-raspberrypi/blob/8f22e5ef126f71ab8fa04d4c288771df9fad825e/platform.json#L29-L33)（`1.90201.191206`）が、`linux_aarch64`で取得できないので、`~1.90301.0`に変更しています。また、手元で確認したところ、最新のバージョン（`~1.120301.0`）だとデバッグ用のビルドに失敗するので、このバージョンを使うのが良いようです。

```ini
; バージョンが1.12*, 1.10*のときは、buid_type = debugがビルドエラーになる
platform_packages =
  platformio/toolchain-gccarmnoneeabi@~1.90301.0
```

#### デバッグ用にビルドする

これを行わない場合、デバッグに失敗すると思います（バイナリをアップロードしたいだけなら不要です）

```ini
; build configuration
build_type = debug
```


#### アップロードのコマンドを独自のものに変える

- `upload_protocol`に`custom`を指定することで、`upload_command`（独自のコマンド）を[使用できるようにしています](https://docs.platformio.org/en/latest/projectconf/sections/env/options/upload/upload_protocol.html)
- `upload_flags`にコマンドのフラグを指定して、`upload_command`内の`$UPLOAD_FLAGS`でフラグを[参照しています](https://docs.platformio.org/en/latest/projectconf/sections/env/options/upload/upload_command.html)。コメントにあるような`upload_flags`の注意事項は後述します
    - `upload_flags`の`-c`フラグに`$BUILD_DIR`が使われていますが、これは、[環境がビルドされるディレクトリに変換されます](https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/directory/build_dir.html)（`$SOURCE`という変数はビルドオブジェクト名に変換されますが、`.elf`ファイル名ではなく、`.bin`ファイル名に変換されるので使用できなかったです）
- `upload_command`にて、絶対パスで`openocd`を実行しています。`openocd`単体で指定すると、手元では依存関係に指定されている`tool-openocd-raspberrypi/bin/openocd`が実行されたので、絶対パスにしています

```ini
; upload configuration
; -fフラグの順番はこの通りに指定しないとエラーになる
upload_protocol = custom
upload_flags = 
  -s
  /usr/local/share/openocd/scripts
  -f
  interface/raspberrypi-swd.cfg
  -f
  target/rp2040.cfg
  -c
  program $BUILD_DIR/firmware.elf verify reset exit
upload_command = /usr/local/bin/openocd $UPLOAD_FLAGS
```

`upload_flags`での注意事項は以下の2点となります
- `-f`フラグについて、`interface/raspberrypi-swd.cfg`を指定してから、`target/rp2040.cfg`を指定する（そうしないとアップロードは失敗します）
- フラグとフラグに指定した値を1行にしない
    - 以下の書き方はNGとなります
        ```ini
        upload_flags = 
          -s /path/to/scripts
          ...
        ```
    - 実行されるコマンドが以下のように、`"`で囲われてしまって、意図したものではなくなっていしまいます
        ```console
        /usr/local/bin/openocd "-s /path/to/scripts" ...
        ```

`upload_flags`を使わずとも、`upload_command`にフラグを直接書き下すこともできますが、以下の違いがあるため見やすさのために、`upload_flags`を使っています
- `upload_flags`：改行可能（複数行にわたって書ける）
- `upload_command`：改行不可

ちなみに実行しているコマンドは、[Raspberry Pi Pico をセットアップしよう](https://datasheets.raspberrypi.com/pico/getting-started-with-pico-JP.pdf)の5.3節のものとほとんど同じです

#### デバッグ時のサーバーを独自のものに変える

アップロードのコマンドを変えた時と同様に、`debug_tool`を`custom`にして、`debug_server`を独自のコマンドに変えています

```ini
; debug configuration
debug_tool = custom
debug_server =
  /usr/local/bin/openocd
  -s
  /usr/local/share/openocd/scripts
  -f
  interface/raspberrypi-swd.cfg
  -f
  target/rp2040.cfg
```

こちらも使用しているコマンドは、[Raspberry Pi Pico をセットアップしよう](https://datasheets.raspberrypi.com/pico/getting-started-with-pico-JP.pdf)の6.3節のものとほとんど同じです

## なんで公式の方法ではうまくいかなかったのか

完全に予想ですが、以下のいずれかかと思っています
- バージョンの相性が悪かった
    - `platformio/toolchain-gccarmnoneeabi`のデフォルトで指定されているバージョンが取得できず、別バージョンを入れたが、他のパッケージとの相性が良くなかった可能性がある
- 使用しているバージョンが古くなってもう使えなくなった

特に、[公式の方法で使われるOpenOCDのパッケージ](https://registry.platformio.org/tools/platformio/tool-openocd-raspberrypi)は3年前から更新されていないので、後者が原因ではないかと考えています

## その他

### プロジェクトの初期設定の罠

[使用パッケージの指定](#使用パッケージの指定)で軽く触れたのですが、`linux_aarch64`でデフォルトパッケージが取得できないことが原因、[VSCodeでプロジェクトを初期構築](https://docs.platformio.org/en/latest/integration/ide/vscode.html#quick-start)がうまくいかずにタイムイートされました（VSCodeでは、パッケージの取得がすべてうまくいかない限り、初期構築が失敗してプロジェクトが作成されないようであった）

プロジェクトの初期設定は、以下のCLIコマンドで行うのが良さそうです。パッケージの取得に失敗しても、プロジェクトの作成自体は成功するので、作成された骨組みのうち、`platformio.ini`のパッケージ指定変えればよいからです

```console:プロジェクト初期設定のコマンド
mkdir my_project
cd my_project
pio project init -b pico
```

## 参考

- [Raspberry Pi Pico をセットアップしよう](https://datasheets.raspberrypi.com/pico/getting-started-with-pico-JP.pdf)
- PlatformIOの公式ドキュメント
    - https://docs.platformio.org/en/latest/plus/debug-tools/raspberrypi-swd.html
    - https://docs.platformio.org/en/latest/integration/ide/vscode.html#debugging
    - https://docs.platformio.org/en/latest/projectconf/sections/env/options/upload/upload_protocol.html
    - https://docs.platformio.org/en/latest/projectconf/sections/env/options/upload/upload_command.html
    - https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/directory/build_dir.html