GR-PEACH × RustでLチカしてみよう【とにかくやってみる編】

# はじめに
組込みソフトウェア向けプログラム言語というと`C言語`か`C++言語`を思い浮かべる人が多いのではないでしょうか。13年間組込みソフトウェアエンジニアとして働いてきましたが、**C++言語を使った開発は1回だけ**で他は全部C言語で開発してきました。たまにアセンブリ言語を使用することもあります。組込み業界とはそういうところだと思います。Qiitaの記事は毎日チェックしていますが、WEB業界やゲーム業界はモダンなプログラミング言語が使えて羨ましいなと思っていました。
しかし近年組込み業界の救世主となるかもしれない`Rust`というプログラミング言語が現れたようです。2015年頃からいたようなのですが、お恥ずかしながらチェックが遅れて半年前ぐらいに気付きました…この言語、ざっくりいうとC言語で好き勝手できていたことをできなくし、**ビルドを通すハードルを上げる**代わりにビルドが通ったら**メチャクチャ安全**なプログラムが出来上がるという、組込みにはうってつけの言語のようです。またC言語にはないモダンな言語仕様が実装されていたり、実行速度がC言語並みだったりとかなり魅力的です。今後組込み業界にジワジワと侵食してくるのではないかと個人的には予想しています。
前置きが長くなりましたが、思い立ったが吉日ということで私も`組込みRust`に入門してみることにしました。既にQiita等技術ブログで`STM` `Arduino` `ラズパイ`等で組込みRustを扱った記事がありますので、私は**国産ボード**を動かしてみようと思い`mbedボード`である**[GR-PEACH](https://os.mbed.com/platforms/Renesas-GR-PEACH/)**をチョイスしてみました。

# 環境
開発・動作環境は以下の通りです。

- OS
    - Windows 10 64bit
- rustc・cargo
    - 1.48.0-nightly
- rustup
    - 1.22.1
- GNU Tools ARM Embedded
    - 6 2017-q2-update
- e<sup>2</sup>studio
    - 2020-07

# 作戦
`GR-PEACH`は`mbedボード`です。PCとUSBで接続すればドライブとして認識し、そこにバイナリを放り込むだけで書込みが完了するという手軽さがあります。しかし詳しいことはわかっていないのですが、書込まれるバイナリがmbedのバイナリかどうかをチェックしているようで、適当なバイナリを書き込もうとしても書込まれません(試しに適当なファイルをドライブに放り込んでみてください)。またICEで書込もうにも`JTAG`の回路はありますがデフォルトではピンが立っていませんし、私のようなニワカはICEなんて持っていません。
そこで`main()`が呼ばれるまではmbed(C++)に任せそこからRustのソースで用意した`rust_main()`関数を呼んであげることにしました。
こんなイメージです。

```C++:C++
extern "C" void rust_main();

int main()
{
	rust_main();
}
```

```Rust:Rust
#[no_mangle]
pub extern "C" fn rust_main() {
	// Lチカ
}
```

# Rustのインストール
Rustのインストール方法に関する記事はたくさんありますので軽くにしておきます。

1. [公式サイト](https://www.rust-lang.org/tools/install)から`rustup-init.exe`をダウンロード
2. 実行すると選択肢が表示されるので`1)`を選ぶ

詳しくは「[Windows 10 で Rust のインストール](https://qiita.com/reki2000/items/5693abde190b8149743d)」辺りをご参考ください。この記事にかかれている**パスの追加**はインストーラが勝手にやってくれたので不要でした。

# ターゲットの追加
以下のコマンドでターゲットを追加します。
GR-PEACHには`RZ/A1H`マイコンが載っています。`RZ/A1H`のコアは`Cortex-A9`でアーキテクチャは`v7-A`なので`armv7a-none-eabi`を追加します。

```shell
> rustup target add armv7a-none-eabi
```

もし`error: no override and no default toolchain set`と表示されるようなら

```shell
> rustup update stable
```

を実行後再度試してください。
またツールチェイン追加中に`error: could not download file from ...`のようなエラーが出る場合は`c:\Windows\System32\drivers\etc\hosts`に

```
13.32.244.72 static.rust-lang.org
```

を追加してください。

# e<sup>2</sup>studioのインストール
こちらもインストール方法に関する記事はたくさんありますので軽くにしておきます。

1. [統合開発環境e<sup>2</sup>studio](https://www.renesas.com/jp/ja/products/software-tools/tools/ide/e2studio.html)からインストーラをダウンロード
2. インストーラの指示に従いインストール

詳しくは「[GR-PEACHオフライン開発環境の構築[On Windows 10]](https://qiita.com/spiderx_jp/items/8170c8c4538140317b6c)」辺りをご参考ください。
また本章とは別件ですがRustでクロスコンパイルするときにGCCのリンカを使用するため、このタイミングで`C:\Program Files (x86)\GNU Tools ARM Embedded\6 2017-q2-update\bin`にパスを通しておきます。

# プロジェクトを作成する
適当なディレクトリ(日本語は入っていないほうがいいかもです)で以下のコマンドでRustのプロジェクトを作成します(ついでに作成したディレクトリ内に入ります)。

```shell
> cargo new blinky --lib
> cd blinky
```

``Created library `blinky` package``と表示されれば作成完了です。`--lib`はライブラリ用テンプレートを用いてプロジェクトを作りますという意味です。後述しますが**実行バイナリ**ではなく**静的ライブラリ**を作成します。
**以降このパス(適当なディレクトリ\blinky)を作業ディレクトリとします。**

# Cargo.tomlを編集する
作業ディレクトリ直下に`Cargo.toml`というファイルがあると思います。勉強中のため断言できませんがプロジェクトの設定ファイルだと思っていただければと思います。
このファイルを以下のように書き換えます(authorsはご自分の名前で…)。

```ini:Cargo.toml
[package]
name = "blinky"
version = "0.1.0"
authors = ["yagisawa"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[lib]
crate-type = ["staticlib"]
```

追加したのは`[lib]`以降です。静的ライブラリを作成しますという意味になります。

# コードを書く
いよいよLチカのコードを書きます。
`src`ディレクトリ内に`lib.rs`というファイルがありますので以下のように書き換えます。

```Rust:lib.rs
#![feature(llvm_asm)]
#![no_std]

const PORTN_BASE: usize	= 0xFCFE_3000;
const P6: *mut usize	= (PORTN_BASE + 6 * 4) as *mut usize;
const PM6: *mut usize	= (PORTN_BASE + 0x300 + 6 * 4) as *mut usize;
const PMC6: *mut usize	= (PORTN_BASE + 0x400 + 6 * 4) as *mut usize;

#[no_mangle]
pub extern "C" fn rust_main() {
	let mut flg: bool = false;
	
	write(PM6, 0);
	write(PMC6, 0);

	loop {
		if flg {
			write(P6, 1 << 12);
		}
		else {
			write(P6, 0);
		}

		flg = !flg;
		
		for _ in 0..200_000_000 {
			unsafe { llvm_asm!("" :::: "volatile")}
		}
	}
}

fn write( addr: *mut usize, data: usize ) {
	unsafe {
		core::ptr::write_volatile(addr, data);
	}
}

use core::panic::PanicInfo;
#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
	loop {}
}
```

Rustの知識は皆無なので詳しい説明はできませんが、要点をまとめると

- `#![feature(llvm_asm)]`
    - llvm_asm!を使いますという意味 
- `#![no_std]`
    - 標準ライブラリではなくコアライブラリをリンクしますという意味
- `PORTN_BASE` `P6` `PM6` `PMC6`
    - GR-PEACHの`LED_USER`(P6_12)にアクセスするためのレジスタアドレス
- `#[no_mangle]`
    - この関数はマングリングしませんという意味
    - マングリングとは名前空間や多重定義などで同じ名前の関数が定義された場合にコンパイラが区別できるようにシンボルに付加情報を埋め込むこと
- `pub extern "C" fn rust_main()`
    - Rustの関数ではなくCの関数として定義しますという意味
- `for _ in 0..200_000_000`
    - GR-PEACHのクロックは`400Mhz`で、空のfor文は大体2命令ぐらいになりそうなので2億回ぐらい回せば500msになるんじゃないかなーという適当なループ
- `unsafe { llvm_asm!("" :::: "volatile")}`
    - 最適化によりループが消えないようにする(命令の意味は…すみませんわかりません。何もしない命令か？)
- `core::ptr::write_volatile(addr, data)`
    - 最適化されないように`volatlie`アクセスにする
- `#[panic_handler]` `fn panic(_panic: &PanicInfo<'_>)`
    - プログラムが暴走したときにコールされる関数

となります。

# リリースチャネルの変更
Rustには

- Nightly
- Beta
- Stable

の3つのリリースチャネルがあります。
先程のコードで`llvm_asm!`というマクロを使用したのですが、このマクロは`Nightly`でしか使用できないためリリースチャネルを変更する必要があります。
以下のコマンドで変更できます。

```shell
> rustup install nightly
> rustup override add nightly
```

以下のように表示されれば変更完了です。

```shell
info: using existing install for 'nightly-x86_64-pc-windows-msvc'
info: override toolchain for '適当なディレクトリ\blinky' set to 'nightly-x86_64-pc-windows-msvc'

  nightly-x86_64-pc-windows-msvc unchanged - rustc 1.49.0-nightly (98edd1fbf 2020-10-06)
```

# カスタムターゲットの作成
[ターゲットの追加](#ターゲットの追加)で既にターゲットは追加しましたが一点問題があります。mbed環境のライブラリは`Float ABI`が`hard`で作成されているのですが、Rustターゲットの`armv7a-none-eabi`は`soft`で作成されています。hard-floatとsoft-floatの混在はできないようなのでこのままだとビルドが通りません。そこでhard-floatに設定したカスタムターゲットを作成します。
まず以下のコマンドで`armv7a-none-eabi`のターゲット仕様を表示します。カスタムターゲット名は`armv7a-none-eabihf`にしようと思うのでそのままjsonファイルとしてリダイレクトすると楽ちんです。

```shell
> rustc +nightly -Z unstable-options --print target-spec-json --target armv7a-none-eabi > armv7a-none-eabihf.json
```
ファイルをみてみると

```json
"features": "+v7,+thumb2,+soft-float,-neon,+strict-align"
```
という行がありsoft-floatになっていることがわかります。
これを以下のように書き換えます。

```json:armv7a-none-eabihf.json
{
  "arch": "arm",
  "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
  "disable-redzone": true,
  "emit-debug-gdb-scripts": false,
  "env": "",
  "executables": true,
  "features": "+v7,+thumb2,-neon,+strict-align",
  "is-builtin": true,
  "linker": "rust-lld",
  "linker-flavor": "ld.lld",
  "llvm-target": "armv7a-none-eabihf",
  "max-atomic-width": 64,
  "os": "none",
  "panic-strategy": "abort",
  "relocation-model": "static",
  "target-c-int-width": "32",
  "target-endian": "little",
  "target-pointer-width": "32",
  "unsupported-abis": [
    "stdcall",
    "fastcall",
    "vectorcall",
    "thiscall",
    "win64",
    "sysv64"
  ],
  "vendor": ""
}
```

# Xargoのインストール
`Xargo`は`Cargo`のサブコマンドで**標準のクレートのバイナリリリースを持たないターゲットにおいて、Rustクレートを簡単にクロスコンパイル**してくれます。どういうことかというとRustでサポートされている`armv7a-none-eabi`等のターゲットは追加したときに標準のクレート(libcore, liballoc等)がライブラリ(バイナリ)の状態でインストールされます。しかし先程作ったカスタムターゲットはJSONファイルを作っただけなのでそれがありません。そこでXargoは`rust-src`(上記ライブラリ等のソース)をビルドしてカスタムターゲット向けのバイナリを用意してくれちゃうわけです。
以下のコマンドでインストールできます。

```shell
> rustup component add rust-src
> cargo install xargo
```

# ビルド
いよいよビルドです。初回は先程の標準クレートのビルドが一緒に行われます。
以下のコマンドでビルドできます。

```shell
> xargo build --release --target armv7a-none-eabihf
```

`Finished release [optimized] target(s) in 0.01s`と出ていればビルド完了です。`--release`はリリースビルドという意味です。`--target`で先程作ったカスタムターゲットを指定します。
ビルドが完了すると`blinky\target\armv7a-none-eabihf\release`に`libblinky.a`というファイルができていると思います。

# GR-PEACHのオフライン開発環境構築
冒頭に書いた[作戦](#作戦)の詳細ですがmbed環境をビルドするときに静的ライブラリにしたRustプログラムを一緒にリンクしようと考えています。
こちらは本題ではないので軽くにしておきます。

1. `mbed compiler`で新規プロジェクトを作成する(雛形はmbed-os-example-mbed5-blinkyで)
2. e<sup>2</sup>studioプロジェクトとしてエクスポートする
3. e<sup>2</sup>studioにインポートする
4. main関数を[作戦](#作戦)で書いたコードに書き換える
5. `libblinky.a`をe<sup>2</sup>studioプロジェクトのルートディレクトリ辺りにコピーする
6. `プロジェクトのプロパティ → C/C++ビルド → 設定 → ツールの設定 → Cross ARM C++ Linker → Libraries`に`blinky`ライブラリを追加する(Library search pathの追加も忘れずに)

![e2studio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469259/bc178c82-82e7-e65e-6efd-2ac8d453897b.png)
以上で環境構築は完了です。無事にビルドが通ることを願います。

# 動作確認
ビルドが終わると`Debug`ディレクトリに`mbed-os-example-mbed5-blinky.bin`というファイルができていると思います。これを`MBED`ドライブに放り込みます。
書込みが終わりリセットボタンを押すと…
![Lチカ！](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469259/0b3aa040-8085-bf44-2636-db7e9780d262.gif)
Lチカしました。点滅周期もまぁまぁよさそうです。

# 失敗の数々
今までスムーズに作業手順を書いてきましたがここまで来るのに様々な失敗をしてきました。特にhard-floatのバイナリが作れなかった時は「もう無理か…」と諦めかけたこともありました。
ここではそんな失敗についてご紹介したいと思います。他のマイコン等で作業していて同じような現象に出くわした時等参考になればと思います。

### ターゲットがない
Rustをインストールして一発目にWindowsの`Hello World`を作ろうとしました。いざビルドしようとすると

```shell
error: no override and no default toolchain set
```

と出て出鼻をくじかれました。

```shell
> rustup update stable
```

を実行することで解決できました。
ちなみにこの現象が発生しているとバージョン確認すらできません。

### ターゲットが追加できない
色々とクロスコンパイル環境の作り方がわかってきてさぁターゲットを追加するぞというところで

```shell
error: could not download file from ...
```

と出て追加できませんでした。
どうやらDL先の名前解決ができないみたいなので、`c:\Windows\System32\drivers\etc\hosts`に

```
13.32.244.72 static.rust-lang.org
```

を追加することで解決できました。

### プロジェクトを作っていない
興味がある情報だけを調べていって`cargo build --target`すればクロスコンパイルできる事がわかりました。そこで適当なディレクトリに`main.rs`を作ってビルドしたところ

```
error: could not find `Cargo.toml`
```

となりました。
[プロジェクトを作成する](#プロジェクトを作成する)を参考にプロジェクトを作成しましょう、という話です。

### `armv7a-none-eabi`でhard-floatビルドできない
[カスタムターゲットの作成](#カスタムターゲットの作成)で書いた話です。soft-floatとhard-floatが混在しているとリンク時に

```
... /ld.exe: error: mbed-os-example-mbed5-blinky.elf uses VFP register arguments, ...
```

というエラーが出ます。
なんとかRustコンパイラに引数を渡そうといろいろ試しましたがうまく行かず、最終的にカスタムターゲットを作るに至りました。

### プロジェクトを複製するとビルドが通らなくなる
試行錯誤のあのバージョン、このバージョンとプロジェクトを複製していたら

```
ERROR: the sysroot can't be built for the Stable channel. Switch to nightly.
```

と出てビルドできなくなりました。[リリースチャネルの変更](#リリースチャネルの変更)ですが今回の方法ではRust全体が変更されるのではなく**ディレクトリごと**に変更されるようで`rustup`がそのディレクトリを管理しています。`C:\Users\ユーザー名\.rustup\settings.toml`をみてみると

```ini
default_host_triple = "x86_64-pc-windows-msvc"
default_toolchain = "stable-x86_64-pc-windows-msvc"
profile = "default"
version = "12"

[overrides]
"\\\\?\\適当なディレクトリ\\blinky" = "nightly-x86_64-pc-windows-msvc"
```

こんな感じで管理されていました。

# おわりに
なんとかLチカまでこぎつけました。
Rust人口はまだまだ少なく、組込み人口ともなればもっと少ないので情報があまり出回っておらずかなり苦労しました。この記事が組込みRustユーザのお役に立てれば光栄です。
今回は【とにかくやっている編】ということもあり

* ほとんどmbedじゃない？
* 全然組込みっぽくない(割込みを使用していない等)
* 全然Rustっぽくない(と、勝手に思ってる)

等課題がたくさんあります。

* mbed依存部をブートローダだけにする
* ICEを買ってスタートアップからスクラッチで書いてみる
* 点滅周期にタイマーを使う
* ペリフェラルのアクセスに[rza1](https://docs.rs/rza1/0.2.0/rza1/)クレートを使ってみる

等色々挑戦していきたいと思います。
そのうち`C`で書いてるその実装、`Rust`ならこう(簡潔に・安全に)書けるよ、的な記事も書けたらなと思っています。

# 参考
本記事を書くにあたり以下のサイトを参考にしました。

* [Windows 10 で Rust のインストール](https://qiita.com/reki2000/items/5693abde190b8149743d)
* [GR-PEACHオフライン開発環境の構築[On Windows 10]](https://qiita.com/spiderx_jp/items/8170c8c4538140317b6c)
* [RustでLチカする2種類の方法（Arduino編）](https://qiita.com/tobisako/items/08f158df08304f167a55)
* [rustup: the Rust toolchain installer](https://github.com/rust-lang/rustup/blob/060192bd8089b4866f1571d85e85ca0264323233/README.md)
* [Rust の crate_type をまとめてみた](https://qiita.com/etoilevi/items/4bd4c5b726e41f5a6689)
* [コンパイラサポートに関する覚書](https://tomoyuki-nakabayashi.github.io/embedonomicon/compiler-support.html)
* [[Rust]インストールからHello world, Cargoを使ったプロジェクト管理入門](https://qiita.com/u1and0/items/062edec94ac1aebe6bf7)
* [How can I install Rust using rustup on Windows when behind a firewall?](https://stackoverflow.com/questions/42843329/how-can-i-install-rust-using-rustup-on-windows-when-behind-a-firewall/42846168#42846168)