Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rust ハンズオン第6回 ベアメタル Rust 編

68dad178ea4fa6aa86862b3a66a15306?s=47 Yuki Toyoda
May 29, 2021
54

Rust ハンズオン第6回 ベアメタル Rust 編

68dad178ea4fa6aa86862b3a66a15306?s=128

Yuki Toyoda

May 29, 2021
Tweet

Transcript

  1. Rust ハンズオン第 6 回 ベアメタル Rust 編 1

  2. 今日の免責事項 OS / CPU / メモリ等はある程度知識がある前提で進めます。 それらに関する用語が普通に登場します。 今日はエミュレータの上でのみ動かします。 私は組み込み開発も残念ながらプロダクション経験があるわけではありません。 今日の内容は『実践

    Rust プログラミング入門』に詳しく書いてあります。まるっきり同 じです。 2
  3. 組み込み開発とは 3

  4. 組み込み開発とは 家電、車載システム、産業機器、その他マイコンなど  で使用するソフトウェア。 Web サービスのようにリソースが潤沢ということはない。 組み込み開発の場合はかなりリソースが絞られる。 4

  5. 組み込み開発 リソースが絞られる場面 = Rust 向き。 5

  6. 他の言語 C / C++ TinyGo mruby ...など。 6

  7. ベアメタルとは 7

  8. ベアメタルとは OS の機能を一切利用せずに、直接ハードウェア上にプログラムを走らせる手法のこと。 今回はハイパーバイザを使用するパターンは含みません。 OS はハードウェアを抽象化し、プロセスという単位でプログラムの実行を分離する。 が、その恩恵を一切受けないことを指す。 メモリ管理は自分でやる必要がある。malloc は使えない。スタックメモリは 1

    から 実装する必要がある。 ハードウェアへの初期化、アクセスも自分でやる必要がある。 8
  9. 準備 今回は Docker を使用するので、Docker for Mac 等をインストールしておいてくださ い。 下記リポジトリをクローンします。 https://github.com/yuk1ty/embeded-rust-handson

    9
  10. 準備 今回使用するビルドターゲットを README.md にしたがって順に上から追加していきま しょう。 10

  11. 準備 Docker 上で今回使用するコードが実行できれば完了です。 うまく実行できれば、 Hello, World と出るはず。 11

  12. QEMU エミュレータ。さまざまな CPU をターゲットにエミュレートできる。 OS 作ったりするときとかによく使う。 12

  13. 今回使用するプラットフォーム STM32F4DISCOVERY という評価基板を使用する。 コロナじゃなければ予算を組んで配りたかったです…! もし興味があれば、買ってみて試してみて欲しい。 13

  14. Hello, World のコードを眺めてみる 14

  15. コード #![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 {} } 15
  16. 見慣れないものリスト #![no_std] #![no_main] #[entry] ! 16

  17. #![no_std] no_std アトリビュートをつけると、macOS や Windows OS のような通常の OS では動 作しないプログラムを記述する、ということを宣言できる。

    Rust は標準ライブラリが std, alloc, core の 3 階層になっているが、no_std を付与する とこのうちの core を使用するようになる。 std と alloc は使用できなくなる。 たとえば Vec や println! などは使用できなくなる。 それ用に実装されたクレートから読み込む必要がある。 17
  18. no_std 環境においては下記のコードはコンパイルエラーに… #![no_std] #![no_main] #[entry] fn main() -> ! {

    println!("Hello, World!"); loop {} } 18
  19. #![no_main] no_main アトリビュートをつけると、Rust 標準の main 関数を使用しないことを宣言で きる。 main 関数を呼び出すと、Rust コンパイラが自動で付与するコードや

    std に関するコード が入ってしまうが、この処理には OS の機能が必要になる。 ベアメタル環境ではこれが使えない。 19
  20. #[entry] main 関数は使えないので、代わりにエントリポイントがここであることをコンパイラに 伝える。 これ自体は今回使用している cortex-m-rt クレートの機能にあたる。 20

  21. ! 発散型と呼ぶ。他の言語では never 型や bottom 型という名前で表現されていることが ある。 loop 式などがこの型を返す。 値を一切返さないことを示す。

    組み込みでは、main 関数が値を返したとしても返る先がなく、返ったあとに何が起きる かわからない。ので、絶対に値は返さない。 Rust の ! (ビックリマーク、エクスクラメーションマーク、感嘆符、never) 型: https://blog-dry.com/entry/2020/11/02/000313 21
  22. L チカ 22

  23. L チカ マイコンで言うところの Hello, World に当たる。 実物では LED をチカチカさせることができる。 今回は

    QEMU 上でチカチカさせる。 23
  24. LED の操作について コロナじゃなければ実物を触りながらやりたかったが… 今回使用する基板では LED は GPIO に接続されている。 そのうちの PD15

    という端子に青色の LED が接続されている。 そこと対応したメモリアドレスに対して適切な値を書くと、電圧が供給されて LED が光 るという仕組み。 今回のプログラムではそうしたペリフェラルにプログラム側からアクセスが可能。 24
  25. ペリフェラル CPU コアとメモリ以外の周辺機器を指す。 ディスプレイとかハードディスクとか。 25

  26. GPIO General Purpose Input/Output。汎用 I/O ポート。 集積回路やボードの上にあるピンのことをいう。 26

  27. Memory Mapped I/O 特定のメモリ番地はペリフェラルとマッピングされている。 その特定のメモリ番地に対して値を書くと、ペリフェラルを制御できる。 ペリフェラルとメモリ空間がマッピングされている方式を Memory Mapped I/O と呼

    ぶ。 今回使用するのは、そうしたメモリへの直接のアクセスを抽象化してくれているクレー トになる。 27
  28. クレートの説明 Cargo.toml の設定は下記のようになっている。 [dependencies] cortex-m-rt = "0.6.2" cortex-m-semihosting = "0.3.5"

    embedded-hal = "0.2.3" panic-halt = "0.2.0" stm32f4xx-hal = {version="0.7.0", features=["stm32f407"] 28
  29. クレートの説明 embeded-hal ファームウェア開発において、マイコンごとにドライバが違うとコードを書いても再利 用が難しくなる。 ハードウェア間の違いを吸収するために「ハードウェア抽象化レイヤ(HAL)」がある と便利。 組み込み開発において有用なトレイトを集めたクレートになっている。 29

  30. クレートの説明 stm32f4xx-hal: 今回使用する評価基板向けの HAL 。 cortex-m4-rt: Cortex-M4 向けのランタイムを提供するクレートで、たとえばこれによ り #[entry]

    が使える。 cortex-m-semihosting: Cortex-M シリーズ向けのデバッグ機能を提供するクレート で、 hprintln! などに使った。 panic-halt: いわゆるパニックハンドラを定義できる。 30
  31. コード 31

  32. ペリフェラルの有効化 use cortex_m_rt::entry; use cortex_m_semihosting::debug; use stm32f4xx_hal::{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(); } debug::exit(debug::EXIT_SUCCESS); loop {} } 32
  33. ペリフェラルの有効化 take でベリフェラルを取り出す。 RCC は Recent and clock control の略で、このインスタンスを通じてクロックを供給し

    て回路を有効にする。 48 MHz のクロックを供給して回路を有効化した。 constrain で操作用のインスタンスを取り出すことができる。 constrain はたとえば 2 回呼び出すことはできないようになっている。Rust の所有権シ ステムがリソース管理を行ういい例になっている。 33
  34. GPIO の取得 use cortex_m_rt::entry; use cortex_m_semihosting::debug; use stm32f4xx_hal::{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(); } debug::exit(debug::EXIT_SUCCESS); loop {} } 34
  35. LED の制御をする前準備をしている。 split は同様に操作用のインスタンスを取り出す。 -- LED の操作をする use cortex_m_rt::entry; use

    cortex_m_semihosting::debug; 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(100u32); led.set_low().unwrap(); delay.delay_ms(100u32); } } 35
  36. LED の操作をする set_high をすると LED を点灯させる。 set_low をすると LED を消灯させる。

    delay はウェイトを入れることで LED の on/off を確認できるようにしている。 36
  37. 動かす cargo run 37

  38. 動かす 38

  39. 動かす ちょっとコンソールだと味気ないですが…! LED が on/off されていることがわかる。 実機上で動かすと動くよ! 39

  40. 40