LoginSignup
3
1

More than 1 year has passed since last update.

Rustで組み込みHello World

Posted at

目的

①組み込み環境でRustを使うにはどうすればいいかを知る。
(今回は実機がないのでエミュレータになってしまった)
②Rustを組み込み環境で使うメリットを把握する。

方法

自宅にRustで動く組み込み環境がなかったため、プロセッサエミュレータであるQEMUを使用した。
環境としては
CPUコア:Cortex-M4
命令セット:ARMv7E-M
チップ:STM32F407VGT6
をエミュレートする。

そもそもなぜ、Rustが組み込み環境で注目されているのか?

理由①ガベージコレクションがない。リアルタイム性を確保しやすい言語であるため。
理由②メモリ安全、スレッド安全
理由③C/C++同様の処理速度
という理由からRustは組み込み環境でも注目されている。

クロスコンパイル環境構築

コンパイルする環境と実行する環境が異なる環境向けのバイナリ生成をクロスコンパイルという。
Rustでのクロスコンパイル

ARMv7E-Mに対応したツールチェーンを追加します。

rustup target add thumbv7em-none-eabi

その他必要なツールをインストール

cargo install cargo-binutils
rudtup component add llvm-tools-preview

いざ、Hello World

いつものコマンドでRustのプロジェクトを作成。

cargo new hello

image.png

ターゲットの設定
どの環境をターゲットにするか設定する必要があります。デフォルトでターゲットを設定するにはプロジェクトディレクトリ直下に.cargo/configファイルを作成します。
その中で、targetとしてtarget = "thumbv7em-none-eabi"を設定します。

build.rsの作成
ビルド中の中間ファイルをどこに作成するか、メモリの配置を記述したmemory.xファイルを読み込んで、新たにoutフォルダに作成する。
build.rs中ではprintIn!()でCargoにオプションを伝えることができる。リンク時に参照するフォルダパスとmemory.xが変更されたときに際コンパイルされるように指示されている。

build.rs
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main(){
    let out=PathBuf::from(env::var_os("OUT_DIR").unwrap());
    File::create(out.join("memory.x"))
        .unwrap()
        .write_all(include_bytes!("memory.x"))
        .unwrap();
    println!("cargo:rustc-link-search={}",out.display());

    println!("cargo:return-if-changed=memory.x");
}

memory.xファイルにはメモリの開始アドレスと大きさが記載している。
この情報がリンカーにも渡される。

memory.x
MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 1024k
  RAM : ORIGIN = 0x20000000, LENGTH = 128k
}

confiファイル内でコンパイラに与えるフラグを指定する
link時にcortex-m-rtクレートが自動生成するlink.xをリンクするように追加している。
runnerでcargo run時に実行するファイルを指定している。

config.toml
[build]
target = "thumbv7em-none-eabi"

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

runner = "./runner.bat"

参考
参考

mainの実装
![no_std]は
Rustの標準ライブラリを使用しないことを意味する。標準ライブラリはOSが存在しない状態では使用できない。
![no_main]はmain関数を使用しないことを意味する。
代わりに[entry]で開始箇所を伝えている。

main.rc

#![no_std]
#![no_main]

extern crate panic_halt;

use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hprintln};

#[entry]
fn main() -> !{
    let _=hprintln!("Hello World!");
    debug::exit(debug::EXIT_SUCCESS);

    loop{}
}

cargo run時はbuild.rs内に記述したrunner.batが実行されdockerが起動されるdocker内からローカルの生成されたバイナリにアクセスできるようにマウントし、QEMUEを起動している。

runner.bat
@echo off

set "f=%1"
call set "f=%%f:\=/%%"
call set "f=%%f:C:=%%"

set "d=%CARGO_MANIFEST_DIR%"
call set "d=%%d\=/%%"
call set "d=%%d:C%%"

docker run --rm -v %CARGO_MANIFEST_DIR%:/%d% ^
    qemu ^
    sh -c ^"cd /%d%; ^
    qemu-system-gnuarmeclipse ^
        -cpu cortex-m4 ^
        -machine STM32F4-Discovery ^
        -nographic ^
        -semihosting-config enable=on,target=native ^
        -kernel %f%"



cargo runで実行すると以下のようにHello Worldが表示される
image.png

Rustの組み込み向けの機能

当初の目的②「Rustを組み込み環境で使うメリットを把握する」の観点からいえば、下手なミスはコンパイラが防いでくれるように言語的に設計されている点が今一番感じているメリットである。また、ハードウェアの抽象化やモジュールの抽象化が綺麗で使いやすところも挙げられる。まだ詳しく、触り切れていないので何とも言えないというのが正直なところ。

現段階で、上記のように思った理由を挙げていく

ハードウェアを抽象化したクレートembeded-hal

GPIOなどのアドレスを知らなくても、簡単にGPIOの操作ができるようになっている。
STM32F407VGT6チップであれあば、stm32f4xx-halクレートが対応している。
tomlファイルに具体的にどのチップを使うか指定すればチップに対応する操作が可能となる。
image.png

stm32::Peripherals::take()でPripheralのインスタンスを取得し、
let mut led = gpiod.pd15.into_push_pull_output();でGPIOの15番ピンのモードSetと使用が可能となる。
チップが変わっても、使用するクレートを変えればよいだけなので楽。

main.rc
#![no_std]
#![no_main]


extern crate panic_halt;

use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hprintln};

use stm32f4xx_hal::{delay::Delay, prelude::*, stm32};

#[entry]
fn main() -> ! {
    if let (Some(dp), Some(cp)) = (stm32::Peripherals::take(), stm32::CorePeripherals::take()) {
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(48.mhz()).freeze();

        let gpiod = dp.GPIOD.split();
        let mut led = gpiod.pd15.into_push_pull_output();

        let mut delay = Delay::new(cp.SYST, clocks);

        for _ in 0..5 {
            led.set_high().unwrap();
            delay.delay_ms(100_u32);

            led.set_low().unwrap();
            delay.delay_ms(100_u32);
        }
    }

    debug::exit(debug::EXIT_SUCCESS);

    loop {}
}

安全に対するRustの思想、embeded-halの思想

それぞれの機能の詳細は後述するが、全体として「設定が矛盾してしまう状態を発生させない」ような思想になっている。
この思想の基にインスタンスは常に1つしか存在しないような設計になっている箇所が多い。
具体的には、Copyはコンパイルエラー、moveでの所有権を常に譲渡するような設計になっている。
例を挙げていく。

CPUの周辺回路を設定するPeripheralのインスタンスは常に1つしか存在できない

GPIOの設定をするためのstm32::Peripherals::take()で取得したインスタンスの型はOption型(値がないかもしれないことを示す型)になっていて、一度しか取得できないようになっている。
・Copyやcloneはできない。
・スレッド間の転送は可能
→マルチスレッドであっても任意のタイミングでPripheralsが1つしかないことをコンパイラが保証してくれる。

main.rc

#[entry]
fn main() -> ! {
    if let (Some(dp)) = (stm32::Peripherals::take()) {
  }

クロックを管理するRCC(Reset and clock control)のインスタンスも常に1つしか存在できない

クロックの周波数などを制御するRCCのインスタンスも1つしか存在しないことが保証されている。
また、周波数をSetするときはfreeze()で周波数が書き換えられないようにしないとクロック周波数のインスタンスは取り出せない。
このようにすることで、クロック周波数とそのクロック周波数を基に設定した周辺回路の設定に矛盾が起きないようにしてある。

main.rc

#[entry]
fn main() -> ! {
    if let (Some(dp)) = (stm32::Peripherals::take()) {
        let rcc = dp.RCC.constrain();#複数生成するとエラーになる
        let clocks = rcc.cfgr.sysclk(48.mhz()).freeze();
  }

型制約されたピン設定

set_high()などの関数はPD15>にしか実装されていないため、OUTPUT用のメソッドgpiod.pd15.into_push_pull_output()でのインスタンス取得ではなく、into_floating_input()などでInput用のインスタンスを取得しInput用でset_high()をするとコンパイルエラーになる。

main.rc

#[entry]
fn main() -> ! {
    if let (Some(dp)) = (stm32::Peripherals::take()) {
        let gpiod = dp.GPIOD.split();
        let mut led = gpiod.pd15.into_push_pull_output();

        for _ in 0..5 {
            led.set_high().unwrap();
            delay.delay_ms(100_u32);

            led.set_low().unwrap();
            delay.delay_ms(100_u32);
        }
  }

image.png

メモリ管理

詳しく把握できていない。アロケータやヒープ用のクレートがあることは確認したが、どのように安全使えるように設計されているか把握できていない。
以下リンクにある参考文献などを読んでから再度まとめたい。
https://tomoyuki-nakabayashi.github.io/embedded-rust-techniques/03-bare-metal/allocator.html
https://garasubo.github.io/embedded-book/misc/bib.html

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1