Mbed StudioでLPC11U35をビルドする

このドキュメントは、Mbed StudioでLPC11U35用のプログラムをビルドする手順をまとめたものです。2019年10月時点での情報なので、ご注意ください。

動作を確認するまで少し手間がかかったので、私がどのようにデバッグして修正したのかも記録として合わせて書いておきます。参考になれば幸いです。

面倒な方は、「[色々と修正するのが面倒な方向け](https://qiita.com/toyowata/items/68ca7411493c3da5446a?fbclid=IwAR1KGe-Zp2Gt3xQfJ0EF7h7Yy1xDzSN5nYR70d3NmXTI86AiQqud8YJos7k#%E8%89%B2%E3%80%85%E3%81%A8%E4%BF%AE%E6%AD%A3%E3%81%99%E3%82%8B%E3%81%AE%E3%81%8C%E9%9D%A2%E5%80%92%E3%81%AA%E6%96%B9%E5%90%91%E3%81%91)」まで読み飛ばしてください。

## Mbed Studioのインストール

こちらからダウンロードします。最新版は0.7です。
https://os.mbed.com/studio/

## サンプルコードのインポート

Mbed OSの最新版を使いつつ、ビルドサイズを最適化したサンプルコード`mbed-os-example-blinky-baremetal`を使用します。RTOS機能は使えないのでご注意ください。

https://github.com/ARMmbed/mbed-os-example-blinky-baremetal

Mbed Studioのメニュー[File]-[Import Program...]を選択し、上記URLを指定します。
ライブラリ全体がダウンロードされるまで、少し待ちます。

Targetを`EA LPC11U35 QuickStart Board (LPC11U35_401)`に設定します。

## ビルドする

ターゲットを変更して、ビルドするとリンクエラーが出ます。まず、これを何とかします。

表示されたエラーメッセージ

```
Link: mbed-os-example-blinky-baremetal
[Warning] @0,0: L3912W: Option 'legacyalign' is deprecated.
[Warning] @0,0: L6320W: Ignoring --keep command. Cannot find argument 'os_cb_sections'.
[Error] @0,0: L6218E: Undefined symbol _scanf_mbtowc (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/platform/source/ATCmdParser.o).
[Error] @0,0: L6218E: Undefined symbol us_ticker_irq_handler (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/targets/TARGET_NXP/TARGET_LPC11UXX/us_ticker.o).
Warning: L3912W: Option 'legacyalign' is deprecated.
Warning: L6320W: Ignoring --keep command. Cannot find argument 'os_cb_sections'.
Error: L6218E: Undefined symbol _scanf_mbtowc (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/platform/source/ATCmdParser.o).
Error: L6218E: Undefined symbol us_ticker_irq_handler (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/targets/TARGET_NXP/TARGET_LPC11UXX/us_ticker.o).
Finished: 0 information, 2 warning and 2 error messages.
[ERROR] Warning: L3912W: Option 'legacyalign' is deprecated.
Warning: L6320W: Ignoring --keep command. Cannot find argument 'os_cb_sections'.
Error: L6218E: Undefined symbol _scanf_mbtowc (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/platform/source/ATCmdParser.o).
Error: L6218E: Undefined symbol us_ticker_irq_handler (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/targets/TARGET_NXP/TARGET_LPC11UXX/us_ticker.o).
Finished: 0 information, 2 warning and 2 error messages.
```

エラーメッセージは、2つ表示されています。まず、ひとつ目。

```
[Error] @0,0: L6218E: Undefined symbol _scanf_mbtowc (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/platform/source/ATCmdParser.o).
```

_scanf_mbtowcシンボルが定義されていない（見つからない）というエラーです。

これに関しては心当たりがなかったので、GitHubのリポジトリのissuesで検索してみました。Open issue（未解決）には見つかりませんでしたが、Closeした項目がありました。

https://github.com/ARMmbed/mbed-os/issues/10878

>【小ネタ】
エラーメッセージなどは、mbed-osのGitHubリポジトリのIssuesやPull requestsで検索するのが有効です。Issuesは、解決するとデフォルトでは検索できないので、closeした項目も中身を見ることをお勧めします。

microlibで発生する問題のようです。このissueはすでに対策済みで、Mbed OS 5.13.1でリリースされているようです。

https://github.com/ARMmbed/mbed-os/pull/10704

今回使用しているサンプルコードのリビジョンは、Mbed Studioのメニュー[View] - [Libraries] で確認することが出来ます。Mbed OSのリビジョンは、5.14.0であるため、別の原因のようです。この修正は、rtosフォルダの配下のファイルに対して行われているので、RTOSを使用しないbaremetal版では別の対応が必要なると思われます。

とりあえず、（対処療法になってしまいますが）microlibを使用しない方向で修正します。mbed-os/targets/targets.jsonを修正し、 "default_toolchain": "uARM" の記述を削除します。

https://github.com/ARMmbed/mbed-os/blob/8637069b36e92e34932dd25d0dc8da09b432c0db/targets/targets.json#L357

つづいてふたつ目。

```
[Error] @0,0: L6218E: Undefined symbol us_ticker_irq_handler (referred from BUILD/LPC11U35_401/ARMC6/mbed-os/targets/TARGET_NXP/TARGET_LPC11UXX/us_ticker.o).
```

これは、Ticker関連のシンボルが見つからないというエラーです。関数の実装はこちらにあります。

https://github.com/ARMmbed/mbed-os/blob/85c477c1dc0123fd883470fbded3364826a48da0/hal/mbed_us_ticker_api.c#L98

>【小ネタ】
>関数シンボル名の検索は、GitHubの検索ボックス（一番左上の "Search or jump to..."と書いているボックス）から入力します。シンボル名だけだと大量のエントリが表示されてしまうので、絞り込みやすい名前を使用します。今回は割込みハンドラなので、戻り値と引数がvoidであると予想し、以下の文字列を入力して検索しました。
>
>"void us_ticker_irq_handler(void)"
>
>ダブルクォーテーションで括るのがポイントです。

さて、この関数が見つからないということなので、何らかの原因でコンパイル対象になっていない事が考えられます。このファイル（mbed_us_ticker_api.c）は、先頭に

```
#if DEVICE_USTICKER
```

という記述があり、ここで`DEVICE_USTICKER`が定義されていないターゲットではコンパイル対象にならないことが分かります。

ちなみに、GitHubでこのファイルのヒストリーを見てみる以下のコミットで追加されたようです。

https://github.com/ARMmbed/mbed-os/commit/568feaf7da49580be4aeea1dd8e970b75a36887f#diff-b1d4fa8055b47dfc99318c810b6f9a90

LPC11U35_401ターゲットは、Mbed 2のみに対応しており、Mbed OS 5には未対応です。Mbed 2のポーティング時点では（かなり昔）、ターゲットでサポートされている機能を厳密に定義していなかったため、今回のエラーが出てしまったようです。

なので、`DEVICE_USTICKER`の定義を追加すれば、`us_ticker_irq_handler`が正しくリンクされるはずです。

Mbed OSのターゲット設定では、targets.jsonの`device_has`の部分に機能（例：USTICKER）を追加すると、ビルドスクリプトによって、プリフィックス`DEVICE_`が付加されたシンボル定義が追加されてビルドされます。

https://os.mbed.com/docs/mbed-os/v5.14/reference/adding-and-configuring-targets.html

以下の部分に`USTICKER`を追加します。

https://github.com/ARMmbed/mbed-os/blob/8637069b36e92e34932dd25d0dc8da09b432c0db/targets/targets.json#L364-L377

また、他のターゲットのコードと同様に、ターゲット依存部分のソースにも念のため`#if DEVICE_USTICKER`を追加します。修正するファイルはこちらです。

https://github.com/ARMmbed/mbed-os/blob/master/targets/TARGET_NXP/TARGET_LPC11UXX/us_ticker.c

先頭に`#if DEVICE_USTICKER`を、最後に`#endif`を追加します。

再度ビルドを行います。今度はエラーなくビルドが出来ました。

```
Post-Build: mbed-os-example-blinky-baremetal
| Module              |      .text |  .data |     .bss |
|---------------------|------------|--------|----------|
| [lib]/c_p.l         |  11169(+0) | 12(+0) |  280(+0) |
| [lib]/fz_ps.l       |     32(+0) |  0(+0) |    0(+0) |
| [lib]/libcppabi_p.l |     44(+0) |  0(+0) |    0(+0) |
| [lib]/m_ps.l        |     44(+0) |  0(+0) |    0(+0) |
| anon$$obj.o         |     32(+0) |  0(+0) | 4096(+0) |
| main.o              |    291(+0) |  0(+0) |   24(+0) |
| mbed-os/drivers     |    286(+0) |  0(+0) |    0(+0) |
| mbed-os/hal         |   1802(+0) |  4(+0) |   67(+0) |
| mbed-os/platform    |   4525(+0) | 64(+0) |  234(+0) |
| mbed-os/targets     |  2703(+24) |  4(+0) |   16(+0) |
| Subtotals           | 20928(+24) | 84(+0) | 4717(+0) |
Total Static RAM memory (data + bss): 4801(+0) bytes
Total Flash memory (text + data): 21012(+24) bytes
Image: BUILD/LPC11U35_401/ARMC6/mbed-os-example-blinky-baremetal.bin
Building project mbed-os-example-blinky-baremetal (LPC11U35_401, ARMC6)
```

## 実行する

ターゲットボードにバイナリを書き込んで実行します。実行時にクラッシュしてしまいましたので、これを修正します（ここが一番大変でした）。

クラッシュ情報は、シリアル出力されます。

```

++ MbedOS Fault Handler ++

FaultType: HardFault

Context:
R0   : 00000013
R1   : 0000383D
R2   : 00000002
R3   : 00000002
R4   : 00000001
R5   : 40018004
R6   : 00000000
R7   : 00004153
R8   : 29010068
R9   : 10000144
R10  : 00004174
R11  : 00004174
R12  : 00000004
SP   : 10001F88
LR   : 00003821
PC   : 000020C8
xPSR : 21000000
PSP  : 84EE3428
MSP  : 10001F68
CPUID: 410CC200
Mode : Thread
Priv : Privileged
Stack: MSP

-- MbedOS Fault Handler --



++ MbedOS Error Info ++
Error Status: 0x80FF013D Code: 317 Module: 255
Error Message: Fault exception
Location: 0x2D27
Error Value: 0x20C8
For more info, visit: https://mbed.com/s/error?error=0x80FF013D&tgt=LPC11U35_401
-- MbedOS Error Info --

```


HardFaultは、色々な原因が考えられるので手掛かりなしで解決するのは中々厄介です。
まず、クラッシュ時のスタックポインタ（SP）ですが、特に問題ないアドレスにあります。

>【小ネタ】
>スタックポインタの値が妥当だと判断した根拠は、リンカスクリプトで定義したスタック用のリージョンに入っていて、かつワード境界にアラインされているからです。リージョンの設定はこちら。
>
>https://github.com/ARMmbed/mbed-os/blob/master/targets/TARGET_NXP/TARGET_LPC11UXX/device/TOOLCHAIN_ARM_STD/TARGET_LPC11U35_401/LPC11U35.sct#L20
>
>(0x100000C0+0x1F40)からマイナス0x400の領域、つまり0x10001C00:0x10002000です。なので、SP: 10001F88 は特に問題ないと判断しました。

次に、プログラムカウンタを調べてみます。

```
PC   : 000020C8
```

となっています。実際のコードのリロケーション情報は、ビルドされたmapファイルから取得できます。
MbedStudioのMbed Programsのビューからビルドディレクトリ（BUILD）配下を探します。

mbed-os-example-blinky-baremetal\BUILD\LPC11U35_401\ARMC6\mbed-os-example-blinky-baremetal.map

ダブルクリックでオープンし、該当するアドレスにリロケーションされた部分を探してみます。

```
    0x000020a8   0x0000000c   Code   RO         3049    .text.$Sub$$main    BUILD/LPC11U35_401/ARMC6/mbed-os/platform/source/mbed_sdk_boot.o
    0x000020b4   0x00000020   Code   RO         3189    .text.NVIC_SetVector  BUILD/LPC11U35_401/ARMC6/mbed-os/targets/TARGET_NXP/TARGET_LPC11UXX/device/cmsis_nvic.o
    0x000020d4   0x000000c0   Code   RO         3204    .text.SystemInit    BUILD/LPC11U35_401/ARMC6/mbed-os/targets/TARGET_NXP/TARGET_LPC11UXX/device/system_LPC11Uxx.o
```

アドレス 0x000020C8 が含まれるのは、cmsis_nvic.oモジュールの.text.NVIC_SetVector が配置されている範囲です。なので、この部分でクラッシュしていることが考えられます。

https://github.com/ARMmbed/mbed-os/blob/8637069b36e92e34932dd25d0dc8da09b432c0db/targets/TARGET_NXP/TARGET_LPC11UXX/device/cmsis_nvic.c#L56-L72

今回のクラッシュは、GCC_ARMでのビルドでは発生せず、ARM (ARMC6)のみ発生しています。コンパイラの違いで挙動が変わる事は通常ではあまりありませんが、可能性がゼロではありません。私の経験では、以下のようなケースがあります。

* 最適化方法の違いによって動作が変わる
* 未初期化の領域へのアクセスでたまたま動く場合がある（これはプログラム側のバグ）

コードを見てみたところ特に問題は無いようなので、どの部分でクラッシュしているのかを調べてみました。私がよく使う方法としては、`#if 0`と`#endif`で実行されるコードを削除して絞り込みます。今回は、まずクラッシュ発生個所だけを特定したかったので、この方法を使いました。

printfが使える場合は、以下のコードを挿入しても良いかもしれません。

```cpp
printf("[INFO] %s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
```

これを実行すると、以下のような情報が表示されるので、便利です。

```
[INFO] ./mbed-os/targets/TARGET_NXP/TARGET_LPC11UXX/device/cmsis_nvic.c, NVIC_SetVector, 61
```

色々と試してみたところ、変数old_vectorsへのアクセスでクラッシュしているようでした。

```cpp
uint32_t *old_vectors = (uint32_t *)0; // FLASH vectors are at 0x0
```

この変数定義に`volatile`装飾子を付加して最適化対象から外すことで、実行時にクラッシュすることがなくなりました。


```cpp
volatile uint32_t *old_vectors = (uint32_t *)0; // FLASH vectors are at 0x0
```

実際にどのようなことが原因でクラッシュしているかは、コンパイラが生成したアセンブラコードを見る必要がありますが、今回はそこまでやってはいません。このコードのようにループ回数がコンパイル時点で決まっている場合は、割とコンパイラがアグレッシブな最適化をしてしまう場合があるので、注意が必要かもしれません。

## 色々と修正するのが面倒な方向け

上記修正を施したプログラムを以下に登録しました。Mbed Studioのメニュー[File]-[Import Program...]を選択し、URLを指定します。

https://github.com/toyowata/mbed-os-example-blinky-baremetal

以上です。
## 2019/12/20 追記

_scanf_mbtowcシンボル未定義問題は、こちらで解決していました。
https://github.com/ARMmbed/mbed-os/pull/11564

その他の問題については、こちらにプルリクエストを投げました。
https://github.com/ARMmbed/mbed-os/pull/12134