BluePillのmbed VSCode開発環境を構築してみた

## はじめに

趣味で宇宙開発を行う団体「[リーマンサット・プロジェクト](https://www.rymansat.com)」がお送りする[新春アドベントカレンダー](https://qiita.com/rsp-dgkz/items/497225739b88c817a90c)です。
この記事は8日目です。

リーマンサット・プロジェクトは「普通の人が集まって宇宙開発しよう」を合言葉に活動をしている民間団体です。
他では経験できない「宇宙開発プロジェクト」に誰もが携わることができます。
興味を持たれた方は https://www.rymansat.com/join からお気軽にどうぞ。

## この記事は

この記事ではWEB開発や組み込み開発などのシステム開発をしたことがある方（シェルやプログラムが使えること）を前提にしています。

僕はリーマンサットで[超小型人工衛星である「RSP-00」と「RSP-01」](https://www.rymansat.com/satellites)の無線通信のソフトウェア開発を担当しています。
RSP-01の無線通信制御には ```STM32（arm）マイコン``` とオープンソースな組み込み開発環境である ```mbed``` を採用しました。 ```STM32（arm）マイコン``` はマイコンの中でも比較的スペックが高く、 ```mbed``` のAPIがハードウェアを抽象化してくれるためソフトウェア開発が進めやすいといった利点がありました。また、使用したマイコンは宇宙環境を想定した恒温槽試験や放射線試験で問題がなかったことも選定に至った理由です。

リーマンサット・プロジェクトは文理問わず様々な職業の方が参加しており、事前知識やスキルはメンバー毎に大きく異なります。普段開発に関わることの無いメンバーでも組み込み開発を体験もらうために、メンバーの某御方が ```BluePill``` という格安の開発ボードを大量入手して教材として配布していました。そこで今回はこの ```BluePill``` 向けに ```mbed``` の開発環境を構築してみました。

### BluePillって

```BluePill``` は ```STM32F103C8T6``` を搭載した開発ボードです。この開発ボードはとても安くで200円ほどで買えてしまい、なぜかIC単体で買うよりも安いといった不思議なボードです。
また、 ```STM32F103C8T6``` はデータシート上64KByteの ```Flash Memory``` を搭載しているとありますが、なぜか ```BluePill`` では128KByte搭載されているとの噂です。不思議ですねー。記事の下部で検証してみましたが、手元のものも128KByte扱うことができました。

```BluePill``` の開発方法ですが ```Arduino IDE``` を使うことが多いようです。 ```Arduino``` は情報量が多いので、使い方を調べやすいといった利点があり、 ```BluePill``` の ```Arduino IDE``` 開発環境の構築も調べるといくつか出てきます。
一方で ```mbed``` を使用した方法はあまり情報がありませんが、 ```mbed``` の内部では ```HAL``` というベンダーが提供しているライブラリが使われているため、 ```BluePill``` の能力を最大限まで引き出すことができると考えています。
また、 ```OpenOCD``` ```PyOCD``` といったデバッグツールも用意されているため、一度環境を整えてしまえば最近流行りの ```Visual Studio Code（VSCode）``` を操作するだけで ```BluePill``` のソフト開発・ファームウェアの書き込み・ステップ実行までをおこなう事ができて非常に便利だったため記事に残してみました。

## 開発環境

### ハードウェア

- Macbook Pro(Retina, 13-inch, Late 2013)
- BluePill

![IMG_2474.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14453/a7515993-f0de-7e84-62a1-6ad9c922e74c.jpeg)

- STLink v2

![IMG_2476.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14453/39e0e5be-7fad-5b71-9aa6-1ba079bb7a70.jpeg)

### ソフトウェア

- macOS Catalina v10.15.1
- Visual Studio Code v1.41.1
- Mbed CLI v1.10.2
- gcc-arm-none-eabi 9-2019-q4-major
- OpenOCD v0.10.0

今回はmacOS上で構築しましたが、 ```VSCode``` ```Mbed Cli``` ```gcc-arm-none-eabi``` ```OpenOCD``` はWindowsやLinux上でも動作しますので、インストールすれば同様に構築可能と考えています。

### ブロック図

<img width="897" alt="スクリーンショット 2020-01-14 0.50.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14453/8793ef9a-07a6-f4b3-ac62-222ecc390825.png">

```OpenOCD``` ```arm-none-eabi-gdb``` はVSCodeから起動されます。

## 各ツールのインストール

### Visual Studio Code

[こちら](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)からインストーラー経由でインストールします。
また、 VSCode上の拡張機能の追加から ```C/C++``` のプラグインをインストールしておきます。
```C/C++``` プラグインがあると、```GDB``` を使ったデバッグ実行や、ソースコード上の ```定義``` ```宣言``` ```参照``` に素早くアクセスしたり、構文エラーにいち早く気付けます。

<img width="1680" alt="スクリーンショット 2020-01-15 2.47.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14453/aaf7d5d5-4b3d-a202-4a46-4b5bc0eb389a.png">

### Mbed CLI

Windowsの場合は[インストーラー](https://os.mbed.com/docs/mbed-os/v5.15/quick-start/offline-with-mbed-cli.html)でインストールするのが簡単でした。
macOSの場合は[インストーラー](https://os.mbed.com/docs/mbed-os/v5.15/quick-start/offline-with-mbed-cli.html)でインストールすると専用のシェルが立ち上がり、普段の環境を汚さないようになっています。サクッと試すのであればインストーラーの方が簡単ではありますが、使い慣れたシェル上で扱えない上、この後のVSCodeからの呼び出しでパスが通っていなかったりするため、個人的には[マニュアルインストール](https://github.com/ARMmbed/mbed-cli/blob/1.8.3/README.md)の方がおすすめです。

```sh
$ brew install python@2
$ pip install mbed-cli
```

### gcc-arm-none-eabi

インストーラー経由の場合、 ```gcc-arm-none-eabi``` は自動でインストールされますが、マニュアルインストールの場合は別途インストールする必要があります。

```sh
$ brew tap ArmMbed/homebrew-formulae
$ brew install gcc-arm-none-eabi
```

### OpenOCD

```sh
$ brew install OpenOCD
```

Windowsのインストール方法はまだ調べれてないです。。

## プロジェクトのセットアップ

下記のコマンドでプロジェクトをセットアップします。

```sh
$ mbed new bluepill_first_project
$ cd bluepill_first_project
$ mbed toolchain GCC_ARM
$ mbed target BLUEPILL_F103C8
```

- ```mbed new``` : プロジェクトの作成
- ```mbed toolchain``` : 使用するコンパイラ、今回はGCC_ARMを指定する
- ```mbed target``` : 開発ボードの指定

そして ```main.cpp``` ファイルを下記内容で作成します。組み込み開発の ```Hello World!``` 、Lチカのソースコードです。

```c++
#include "mbed.h" // mbed OSの読み込み

DigitalOut led1(LED1);　// mbed target BLUEPILL_F103C8でボード上のLEDがLED1と定義される

int main() {
    while (true) {
        led1 = !led1; // 出力のHIGH/LOWをトグル
        wait_us(1 * 1000 * 1000); // 1秒
    }
}
````

あとは下記コマンドでコンパイルすると、開発ボードに書き込むファームウェア（バイナリ）が生成されます。

```sh
$ mbed compile
```

正常終了時は以下のように出力されます。

```
Link: bluepill_first_project
Elf2Bin: bluepill_first_project
| Module           |        .text |      .data |        .bss |
|------------------|--------------|------------|-------------|
| [fill]           |      46(+22) |      8(+0) |      21(+9) |
| [lib]/c.a        |    17428(+0) |   2472(+0) |      56(+0) |
| [lib]/gcc.a      |     3104(+0) |      0(+0) |       0(+0) |
| [lib]/misc       |      180(+0) |      4(+0) |      28(+0) |
| main.o           |       64(+0) |      0(+0) |      28(+0) |
| mbed-os/drivers  |       74(+0) |      0(+0) |       0(+0) |
| mbed-os/hal      |     1356(+0) |      4(+0) |      66(+0) |
| mbed-os/platform |   2646(+316) |    260(+0) |    220(+10) |
| mbed-os/rtos     |  6380(+6380) |  168(+168) | 5973(+5973) |
| mbed-os/targets  |   6046(+690) |      4(+0) |     368(+0) |
| Subtotals        | 37324(+7408) | 2920(+168) | 6760(+5992) |
Total Static RAM memory (data + bss): 9680(+6160) bytes
Total Flash memory (text + data): 40244(+7576) bytes

Image: ./BUILD/BLUEPILL_F103C8/GCC_ARM/bluepill_first_project.bin
```

一般的なmbedの開発ボードであれば開発ボード上に ```mbed interface``` と呼ばれるICが乗っており、開発ボードをUSB接続するとドライブがマウントされるので、先程生成された ```./BUILD/BLUEPILL_F103C8/GCC_ARM/bluepill_first_project.bin``` をドラッグ・アンド・ドロップ（コピー）すればファームウェアの書き込みができます。
ですが、 ```BluePill``` には ```mbed interface``` が搭載されておらず、その機能がありません。

## VSCode（OpenOCD）を使った書き込みとデバッグ

```BluePill``` では ```mbed interface``` が搭載されていないため、代わりに ```STLink v2``` という書き込み機器を使用してファームウェアを書き込みます。
```BluePill``` と ```STLink v2``` の ```3.3V``` ```GND``` ```SWDIO``` ```SWCLK``` をそれぞれ接続します。

![IMG_2477.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14453/20ed60f4-6766-917c-46d3-a36d1c7cf7a1.jpeg)

下記コマンドで VSCode用の設定ファイルを出力します。

```sh
$ mbed export -i vscode_gcc_arm
```

生成された ```.vscode/launch.json``` に下記修正を加えます。

```diff
@@ -11,11 +11,11 @@
             "cwd": "${workspaceRoot}",
             "environment": [],
             "externalConsole": false,
-            "debugServerArgs": "",
+            "debugServerArgs": "-f /usr/local/Cellar/open-ocd/0.10.0/share/openocd/scripts/interface/stlink-v2.cfg -f /usr/local/Cellar/open-ocd/0.10.0/share/openocd/scripts/target/stm32f1x.cfg",
             "serverLaunchTimeout": 20000,
             "filterStderr": true,
             "filterStdout": false,
-            "serverStarted": "GDB\\ server\\ started",
+            "serverStarted": "openocd",
             "preLaunchTask": "make",
             "setupCommands": [
                 { "text": "-target-select remote localhost:3333", "description": "connect to target", "ignoreFailures": false },
@@ -41,7 +41,7 @@
             "osx": {
                 "MIMode": "gdb",
                 "MIDebuggerPath": "arm-none-eabi-gdb",
-                "debugServerPath": "pyocd-gdbserver"
+                "debugServerPath": "openocd"
             },
             "windows": {
                 "preLaunchTask": "make.exe",

```

以上で準備は完了です。
VSCodeの「デバッグと実行」からコンパイル・書き込み・デバッグを開始してみます。

![VSCode.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14453/47db9e50-7882-b8a2-cc25-ee4ecedda6bf.gif)

![Lチカ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14453/d25637f1-fc3e-dfe8-e7d2-c6a8ce9e30d0.gif)

無事 ```BluePill``` へ書き込みができ、ステップ実行やローカル変数への参照ができました！

## おまけ

### BluePillのFlashサイズ

```STM32F103C8T6``` は公式で64KByteの ```Flash Memory``` を搭載しているとありますが、128KByte搭載されているとの噂があるため、実際に検証してみました。

やり方は簡単で、 ```printf``` とかで10000文字くらいの文字列を適当に追加していきます。
Lチカのソースコードをコンパイルすると約44KByteでした。10000文字で約10KByteなので8行ほど追記してみます。（同じ文字列だとコンパイラに最適されるため少しだけ文字を変えてます。）

```c++
        printf("1aaaaaaaaaa...");
        printf("2aaaaaaaaaa...");
        ...
```

これでコンパイルして書き込めば・・・と思ったのですが、想定外にもコンパイルの時点でエラーが発生しました。

```
Link: bluepill_first_project
/usr/local/Cellar/arm-none-eabi-gcc/9-2019-q4-major/gcc/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld: ./BUILD/BLUEPILL_F103C8/GCC_ARM/bluepill_first_project.elf section `.text' will not fit in region `FLASH'
/usr/local/Cellar/arm-none-eabi-gcc/9-2019-q4-major/gcc/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld: region `FLASH' overflowed by 58112 bytes
collect2: error: ld returned 1 exit status
```

どうやら64KByteの制限がかかっているようです。
下記ファイルで64KByteの制限をかけているようなので、力技ですが直接編集してしまいます。（```mbed-os```ディレクトリは基本的にgit管理外とするため注意）
```./mbed-os/targets/TARGET_STM/TARGET_STM32F1/TARGET_BLUEPILL_F103C8/device/TOOLCHAIN_GCC_ARM/STM32F103XB.ld```

```diff
@@ -9,7 +9,7 @@

 MEMORY
 {
-  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
+  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
   RAM (rwx) : ORIGIN = 0x200000F0, LENGTH = 20K - (0xEC+0x4)
 }
```

上記修正を加えた後、コンパイルと書き込みを試したところ無事書き込むことができました！この時のバイナリサイズは126KByteでしたので、噂は本当でした！

### Mbed OS bare metalを試してみる

```Mbed OS``` では、スペックの低いマシンでも一部の機能（主にRTOS）を無効にすることで、 ```Mbed OS``` を使うことができる[bare metal](https://os.mbed.com/docs/mbed-os/v5.15/reference/mbed-os-bare-metal.html)と呼ばれる仕組みがあります。
BluePillには ```Mbed OS 5``` の記載がないため ```Mbed 2``` や ```bare metal``` での実行が必要だろうなと予想していましたが、上記で試したLチカは ```Mbed OS 5``` で問題なく動いてしまいましたｗ
ただ、せっかくなので ```bare metal``` でもコンパイルしてみたいと思います。

```mbed_app.json``` を下記のように編集して、後はいつも通りにコンパイルするだけでした。

```json
{
    "requires": ["bare-metal"]
}
```

```Mbed OS 5``` 時のバイナリサイズが44Kbyteに対し、 ```bare metal``` 時のバイナリサイズが35Kbyteと少しだけダイエットに成功です。恩恵はあまり多くありませんが、少しでもバイナリを小さくする必要があったり、RTOSを意図的に無効にしたい場合の選択肢になるのかなと思います。

## 今後の目標

### シリアル通信

今回は VSCode上で ```BluePill``` のコンパイルと書き込み・デバッグまでできることが確認できました。
これだけでも開発をすすめるには十分ですが、```printf``` といった標準入出力の対応も検討したいと考えてます。
```mbed interface``` が搭載されている開発ボードであれば、USBからシリアル通信で標準入力・出力へのアクセスが可能ですが```STLink``` には同等の機能が搭載されていないため以下の三種類の方法が検討されます。

- [FT234X 超小型ＵＳＢシリアル変換モジュール](http://akizukidenshi.com/catalog/g/gM-08461/)のような外部のUSB・シリアル通信変換モジュールを使う
- ```STLink``` の ```SWO``` 機能を使う
- ```BluePill``` 上のUSBにシリアル通信の機能を実装する

外部のUSBシリアル通信変換を用いれば他のmbed開発ボードと同様に標準入出力が利用できますが、ケーブル2本用いるためスマートとは言えません。
```SWO``` を使えば追加のモジュールやケーブルは不要となるためスッキリとしますが、環境の用意が少し手間取りそうだったため今回は深追いできてません。。

### 環境構築

開発環境の構築は既存マシンの環境に依存する箇所があるため、上記手順でうまく構築できない場合もあると思います。
そういった所をDockerで置き換えていけたら環境構築のトラブルを減らせるのかななんて考えてます。（DockerではUSB周りがうまく動作しないと思うので、 ```OpenOCD``` と ```GDB``` を ```Docker Network``` で繋げるのがいいのかななんて妄想しています。）

## 参考記事

[STM32F103C8 で遊ぶ](https://qiita.com/nanbuwks/items/ba37f049f45378da0e2b)
[Arm Mbed CLIの環境構築 for Windows](https://qiita.com/matsujirushi/items/bf4db4514a753f794643)
[mbed CLI (コマンドライン・インタフェース)を Mac OS X で使ってみる](https://os.mbed.com/users/okano/notebook/setup-mbed-cli-on-mac-os-x-JP/)
[Visual Studio Codeでmbed OSプログラムをデバッグする方法(STM32)](http://matsujirushi.hatenablog.jp/entry/2017/11/24/222847)
[STM32F103C8T6 board, alias Blue Pill](https://os.mbed.com/users/hudakz/code/STM32F103C8T6_Hello/)

## おわりに

リーマンサット・プロジェクトは「普通の人が集まって宇宙開発しよう」を合言葉に活動をしている民間団体です。
他では経験できない「宇宙開発プロジェクト」に誰もが携わることができます。
興味を持たれた方は [https://www.rymansat.com/join](https://www.rymansat.com/join) からお気軽にどうぞ。

次回は @ytakuro0926 さんの「C# & Unityで小型衛星姿勢制御ツールを作ってみた①」です。楽しみですね！