🤯

「基礎から学ぶ組込みRust」をRust 2021で書いてみました with Wio Terminal

2023/07/17に公開

はじめに

組込みRustの書籍として有名な「基礎から学ぶ組込みRust」に掲載されているプログラムを一通り書いた結果、僅かながらハードウェア制御が分かったような気がしました。
とても為になりましたが下記の点で苦労しました。

Rustは比較的新しい言語のため日々進歩している分過去の情報を適用できない状況があるようです。
この記事では表題の書籍に掲載されているプログラムをRust 2021で書いてみて情報更新を図りたいと思います。
皆様のお役に立てれば幸いです。

追記: v0.7.0 でのプログラム作成(2023年8月1日)

記事を書く少し前にwio_terminalクレートがv0.7.0へアップデートされましたが破壊的変更が良くわからなくてこの記事ではv0.6.1で書きました。
最近やっとv0.7.0が示している内容が分かってきましたので作例を基にv0.7.0でプログラムを書きました。
今度こそ最新版です。

https://github.com/Tremendous1192/wio_v0_7_0_reference

修正: 2023/8/14

より簡単に環境構築ができるように修正しました。

  • VS Code の拡張機能の追加
  • GitHub上のリポジトリをローカルフォルダに保存する方法を Git: Clone コマンドに変更
  • 実行コマンドをcargo hf2 --release に変更。Release ビルドにしないと遅い
  • コンパイルではなくプログラム実行に語彙を修正
  • Wio Terminal の開発環境インストール一覧から cargo install cargo-generateを削除
  • 最小構成リポジトリのみをテンプレート化

https://github.com/Tremendous1192/wio_terminal_template

2023/8/28

  • hf2のコマンド修正

結論: GitHubリポジトリ

作成したプログラムを下記のリポジトリに保存しています。
プログラム毎にcargo newしているので普通にcargo hf2 --releaseでコンパイルできます。
https://github.com/Tremendous1192/rust-wio-terminal-exercise

筆者の環境

  • Windows 10 Home
  • 64 ビット オペレーティング システム、x64 ベース プロセッサ
  • Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz 2.59 GHz
  • 実装RAM 16.0 GB

組込みRustの準備

書籍の第2章「開発環境の準備」に沿って説明します。

必要なハードウェアと購入先

この記事ではWio Terminal本体の購入が必要になります。

機材名 数量 URL
Wio Terminal 1 秋月電子通商
USB Type-Cケーブル 1本 (最近のスマホはType-Cに置き換わりつつあると思うので、お持ちのスマホ用ケーブルで良いと思います)

Rustの開発環境の準備

  1. Rustのコンパイラに関わるC++ツールをインストールします
  • 個人の方はVisual Studio Communityから下記のアイテムをインストールします
    • NET デスクトップビルドツール
    • C++ によるデスクトップ開発
    • ユニバーサル Windows プラットフォームビルドツール
  1. Rustのビルドツールをインストールします
  • Rustのインストールページからパソコンに合わせて64ビット版あるいは32ビット版をインストールします。
      1. Proceed with installation (default) で進めてください
  1. エディタ(VS Code)をインストールします
  • ここからダウンロードします
    • 左側の拡張機能欄から次の拡張機能をダウンロードします
      • rust-analyzer
      • CodeLLDB
      • GitHub Pull Requests and Issues
      • Choose a License
  1. Rustの開発環境の準備が完了したかを確認します
  • VS CodeのTerminalタブからNew Terminalを選択して生成したTerminalから下記3つのコマンドを入力してください。
    • cargo new hello_check
    • cd hello_check
    • cargo run
  • Terminalに"Hello, world!"が表示されていたらここまでの準備は問題ありません

もう少し丁寧な説明が知りたい方は素晴らしいインストラクションをご参照ください。

  1. Wio Terminalの開発環境をインストールします
  • VS CodeのTerminalタブからNew Terminalを選択して生成したTerminalから下記3つのコマンドを入力してください。
    • rustup target add thumbv7em-none-eabihf
    • cargo install hf2-cli
    • cargo install cargo-hf2

コンパイルを通すことができる最小限の構成

初心者の最初の壁は環境構築です。
本記事の場合Rustの環境構築に加えてWio Terminal向けのも必要になるので難易度が跳ね上がります。
そこで最初はコンパイルを通すことができる最小限の構成を紹介します。

この章ではコンパイルまでの流れを紹介してからプログラム構成の説明に移ります。

プログラム実行までの流れ

プログラムをダウンロードする

  1. 画面上側のViewタブからCommand Palleteをクリックします。

  2. Git: Clone コマンドをクリックします。

  3. リポジトリのURLを入力します。

  4. VS codeのデフォルトフォルダにプログラムセットを解凍します。

  • 私はC:\Users\あなたのユーザー名\source\RustAppsに保存しています
  1. VS Codeを起動してコンパイルを通すことができる最小限の構成のプログラムのフォルダに移動します~~
  • Terminalでlsコマンドを入力すると現在のディレクトリに含まれるフォルダ一覧が出てきます
  • cd rust-wio-terminal-exerciseコマンドでプログラムセットのフォルダ内に入ります
  • 再びlsコマンドを入力するとプログラム一覧がでてきます
  • cd ch00_minimum_componentコマンドでコンパイルを通すことができる最小限の構成プログラムのフォルダに移動します

Wio Terminalをブートローダーモードに切り替える

ブートローダーモードとはコンパイルできる状態のことらしいです。
Wio TerminalのWikiを見ながらパソコンとWio TerminalをUSBケーブルでつないで左側のスライドスイッチを下側に素早く2度動かして下さい。

プログラム実行

  • Terminalでcargo hf2 --releaseコマンドを入力するとプログラム実行コンパイルが完了します

プログラム構成

下記の3点が必要です。

  1. Cargo.tomlファイルに必須クレートをインポートします
  • wio_terminal = "0.6.1" # 必須クレート
  • panic-halt = "0.2" # 必須クレート
  • クレートとはまあ大体ライブラリのことを指しています
  1. .cargoフォルダをCargo.tomlファイルと同じフォルダにコピペします
  • rootディレクトリと呼ぶそうです
  1. main.rsファイルの中身に必須項目が書きます(以降の節で説明)。

Cargo.tomlファイル

Wio Terminalを制御したいのでwio_terminal = "0.6.1"クレートをインポートします。
さらに組込みRustの世界で例外処理を扱いたいのでpanic-halt = "0.2"クレートをインポートします。

Cargo.toml
[package]
name = "ch00_minimam_component"
version = "0.1.0"
edition = "2021"

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

[dependencies]
wio_terminal = "0.6.1" # 必須クレート
panic-halt = "0.2" # 必須クレート

.cargoフォルダ

Wio TerminalにはArm社のCortex-M4FというCPUが搭載されています。
このCPU向けのプログラムを作成するためにtarget = "thumbv7em-none-eabihf"という宣言が必要です。
rustflagsは何らかの調整だと思います。

.carog/config
# 必須ファイル
# .cargoフォルダごとCargo.tomlファイルがあるディレクトリにコピペする
[build]
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
rustflags = [
  "-C", "link-arg=-Tlink.x",
]

main.rsファイル

  • WindowsやmacOS等の通常のOS以外でのプログラムを作成するという宣言のために#![no_std]#![no_main]アトリビュートが必要になります。
  • use panic_halt as _;use wio_terminal;rust:Cargo.tomlでインポートしたクレートを使用する宣言です。
  • 画面幅と画面高さの定数はよく使うので実質必須の定数です。
  • #[wio_terminal::entry]は組込みRustにおけるmain関数につける宣言です。
    • 組込みRustのmain関数は終了させない宣言-> !をつけます
  • 組込みの場合終了させないためにloopが必要です
    • 冷蔵庫が勝手に止まったら嫌ですよね?
main.rs
//! 組込みRustのおまじない
#![no_std] // 必須アトリビュート
#![no_main] // 必須アトリビュート
use panic_halt as _; // 必須クレート
use wio_terminal; // 必須クレート
const DISPLAY_WIDTH: u32 = 320; // 画面幅の定数. ほぼ必須で良いだろう
const DISPLAY_HEIGHT: u32 = 240; // 画面高さの定数. ほぼ必須で良いだろう

#[wio_terminal::entry] // 必須アトリビュート
fn main() -> ! {
    // 初期化
    // ここまで 初期化
    
    // 組込みはloop必須
    loop
    {}
    // ここまでloop処理
}
// ここまでmain関数

Lチカ

組込みのHello worldことLチカの説明をします。
今回は200ミリ秒毎にLEDの点灯・消灯を切り替えます。

プログラム実行に関してはrust-wio-terminal-exercise\ch04_p132_01_lchikaフォルダに移動して頂いてcargo hf2 --releaseコマンドを入力するとプログラム実行できます。

プログラム構成

Cargo.tomlファイル

  • 最小構成と同じクレートをインポートします。
    • wio_terminal = "0.6.1" # 必須クレート
    • panic-halt = "0.2" # 必須クレート

.cargoフォルダ

最小構成と同じです。

main.rsファイル

最小構成と異なる内容を説明します。

  • use wio_terminal::prelude::*;
    • LEDを点滅させるための遅延処理のトレイトが入っています。ほぼ必須
  • let mut peripherals = wio_terminal::pac::Peripherals::take().unwrap();
    • CPUコアとメモリ以外の周辺機器を制御するための宣言のようです
  • let sets = wio_terminal::Pins::new(peripherals.PORT).split();
    • 入出力関係を扱う宣言
  • let mut clocks = 略
    • 内部の時計を使用する宣言
    • 遅延処理などに使用します
  • let mut core = wio_terminal::pac::CorePeripherals::take().unwrap();
    • Peripheralsとの違いが分かりません
  • let mut delay = wio_terminal::hal::delay::Delay::new(core.SYST, &mut clocks);
    • 遅延処理の宣言
  • let mut user_led = sets.user_led.into_push_pull_output();
    • LED出力の初期化
    • wio_terminal = "0.7.0"ではsetsのポートにアクセスできないので書籍と異なる呼び出し方をする
    • user_led.set_low().unwrap();はLED消灯(?)の初期化
  • loop内部のコードは200ミリ秒ごとにLEDの点灯・消灯を切り替えるコード
main.rs
//! 組込みRustのおまじない
#![no_std] // 必須アトリビュート
#![no_main] // 必須アトリビュート
use panic_halt as _; // 必須クレート
use wio_terminal; // 必須クレート
use wio_terminal::prelude::*; // よく使うtraitやstructureが収められているようだ。ほぼ必須。
                              //const DISPLAY_WIDTH: u32 = 320; // 画面幅の定数. ほぼ必須で良いだろう
                              //const DISPLAY_HEIGHT: u32 = 240; // 画面高さの定数. ほぼ必須で良いだろう

#[wio_terminal::entry] // 必須アトリビュート
fn main() -> ! {
    // 初期化
    // 制御関係(wio_terminal)の初期化
    let mut peripherals = wio_terminal::pac::Peripherals::take().unwrap(); // 周辺機器(CPUコアとメモリ以外)
    let sets = wio_terminal::Pins::new(peripherals.PORT).split(); // 入出力

    // 画面表示の描画間隔を設定するために時計インスタンス
    let mut clocks = wio_terminal::hal::clock::GenericClockController::with_external_32kosc(
        peripherals.GCLK,
        &mut peripherals.MCLK,
        &mut peripherals.OSC32KCTRL,
        &mut peripherals.OSCCTRL,
        &mut peripherals.NVMCTRL,
    );

    // 制御関係(wio_terminal)の初期化
    let mut core = wio_terminal::pac::CorePeripherals::take().unwrap(); // 周辺機器(CPUコアとメモリ以外)
    // 画面表示の描画間隔
    let mut delay = wio_terminal::hal::delay::Delay::new(core.SYST, &mut clocks);

    // LEDの初期化
    // 書籍の内容だとエラーになる
    // Githubの内容を参考にすること
    // https://github.com/atsamd-rs/atsamd/blob/0820f0df58eb8705ddfa6533ed76953d18e6b992/boards/wio_terminal/examples/blinky.rs
    let mut user_led = sets.user_led.into_push_pull_output();
    user_led.set_low().unwrap();

    // 組込みはloop必須
    loop {
        user_led.toggle().ok();
        delay.delay_ms(200u8);
    }
    // ここまでloop処理
}
// ここまでmain関数

終わりに

Wio TerminalのプログラムをRustで開発できる最小限の構成とLチカを紹介しました。
続きはGitHubにコードがあるのでそちらをご覧ください。
お役に立てれば幸いです。

Discussion