Rust、書きたいですよね?マイコンでもRustしましょう!組み込みRustには便利なクレート(ライブラリ)がすでにありますが、本記事では構造の理解のために、外部クレートを使わずにCH32V203を動かしてみます。このサンプルプロジェクトをざっと眺めるだけでも、実際の開発における各クレートの役割がわかりやすくなると思います。
本記事はアドベントカレンダー「CH32V203」に参加しています。
概要
Rustは組み込みRISC-Vに公式に対応しています。
もう少し細かく言うと、OSのない環境での(ベアメタルな)RISC-VがTier 2でサポートされています。Tier 2サポートは公式にバイナリ配布がされているので、環境構築がカンタンです。
本記事で紹介するサンプルコードはこちらにあります。
準備する
Rustとcargoを使える環境を用意してください。
以下のコマンドでコンパイラとツールチェーンをインストールします。
rustup target add riscv32imac-unknown-none-elf
cargo install cargo-binutils
rustup component add llvm-tools-preview
ビルドする
ソースコードを手元にダウンロードしましょう。
git clone https://github.com/verylowfreq/example_rust_ch32v203/tree/main
makeが使える環境では make するだけです。
cargoを通じてビルドすることもできます。
cargo build
ビルドしたelfファイルが深い階層にあるので、プロジェクトのルートにコピーします。
cargo objcopy -- -O elf32-littleriscv firmware.elf
書き込むツールによってはbinファイルのほうが便利なので、ファイル形式を変換します。
cargo objcopy -- -O binary firmware.bin
動作確認
起動すると、シリアル通信 115200bps でメッセージを送出しつつ、LEDが点滅します。PA9がシリアル通信のTXです。
解説
Rustでベアメタル(OSのない環境)を取り扱うのは no_std と呼びます。OSがないので、CPUの初期化も、クロックの設定も、main()の呼び出しも、すべて自分でやらなければなりません。メモリレイアウトの標準もないので、ビルドしたバイナリをメモリ上・FlashROM上のどこに配置するかも、自分で指定しなければなりません。
※通常の開発であれば、そのようなベアメタル開発用の便利なクレートが処理してくれるので意識する必要はありません。
メモリレイアウトはリンカースクリプト link.x で指定します。224K, 20Kという数字が冒頭に見えますが、これはFlashROMとRAMのサイズを指定しています。その後ろの SECTIONS で実際の配置順序やアドレスが指定されます。
CH32V203はリセットされるとメモリの0番地から実行をはじめます。スタートアップコードはこの0番地付近に配置され、Rustが動くための最低限の初期化をしてからRustコードのmain()を呼び出します。
今回は外部クレートを使っていないため、マイコンのペリフェラルの操作はすべてレジスタを直接操作しています。補助関数として read_u32() write_u32() modify_u32() (あとu64版も)を registers.rs にて定義しています。
まとめ
ここまで書きましたが、実際に開発するときにこのレベルでのプリミティブなプロジェクトを作ることはないと思います。CH32V203向けのクレートもあるので、そちらを使いましょう。
本当は割り込みも扱いたかったのですが、ちょっと込み入った感じになりそうだったので、今回は見送りです。