YU2TA7KA's BLOG ~take one step at a time~

派生開発、組み込み開発周りのこと。

【組込みRust】RustのLチカコードを解読する

はじめに

こちらの記事でRustのLチカをしました。そのコードを解読していきます。誤った理解による情報があるかもしれません。
f:id:yuji-tanaak:20210612135558p:plain

ソースコード

github.com

#![deny(unsafe_code)]
#![no_main]
#![no_std]

// Halt on panic
use panic_halt as _; // panic handler

use cortex_m;
use cortex_m_rt::entry;
use stm32f4xx_hal as hal;

use crate::hal::{prelude::*, stm32};

#[entry]
fn main() -> ! {
    if let (Some(dp), Some(cp)) = (
        stm32::Peripherals::take(),
        cortex_m::peripheral::Peripherals::take(),
    ) {
        // Set up the LED. On the Nucleo-446RE it's connected to pin PA5.
        let gpioa = dp.GPIOA.split();
        let mut led = gpioa.pa5.into_push_pull_output();

        // Set up the system clock. We want to run at 48MHz for this one.
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(48.mhz()).freeze();

        // Create a delay abstraction based on SysTick
        let mut delay = hal::delay::Delay::new(cp.SYST, clocks);

        loop {
            // On for 1s, off for 1s.
            led.set_high().unwrap();
            delay.delay_ms(1000_u32);
            led.set_low().unwrap();
            delay.delay_ms(1000_u32);
        }
    }

    loop {}
}

解読

Attributes

Attributes - The Rust Reference
Attributes(アトリビュート)はクレート、モジュール、アイテムにメタデータを付与します。Lチカコードには4つのアトリビュートが利用されています。それぞれ見ていきます。

#![deny(unsafe_code)]

denyはLint check attributesの一つです。ここではunsafe_codeに対して、Cの違反コードがあるとエラーを通知します。Lチカコードにはunsafeコードがないので、使っていませんね。
Diagnostics - The Rust Reference

#![no_main]

no_mainはRust標準のmain()関数インタフェースを使用しないことをコンパイラに指示します。main()関数を使うにはOSの機能が必要ですが、ターゲットには非搭載のためno_mainを指定する必要があります。
Crates and source files - The Rust Reference

#![no_std]

no_stdはこのプログラムが通常のOS(Windows, macOSなど)上で動作するプログラムではないことを表します。具体的には、stdクレートを使わずにcoreクレートを利用するようになります。
Preludes - The Rust Reference

#[entry]

entryアトリビュートで指定した関数がそのアプリケーションのエントリポイント(実行開始点)であることを意味します。no_mainアトリビュートでmain()関数が最初に呼ばれなくなっているため、このentryアトリビュートで指定するということですね。
entry in cortex_m_rt - Rust

Crates

Packages and Crates - The Rust Programming Language
Crates(クレート)は既存のプログラムがパッケージされたものです。useなんたらと指定すれば該当のクレートが利用できます。ライブラリと呼ぶのが一般的でしょうか。

panic_halt

パニックハンドラのクレートの一つです。panic_haltはパニックが発生すると、無限ループに入ることで停止します。なので、最低限のパニック対応と思います。できれば何がしか発生要因のメッセージを出して欲しいですし。

パニック(panic)はプログラムの異常終了処理を安全に行う仕組みです。異常終了と言っていますが、Rustの処理系では定義された挙動です。パニックが発生するとパニックハンドラが呼ばれ、プロセスが強制終了します。
https://crates.io/crates/panic-halt
パニック - The Embedded Rust Book

cortex_m

Cortex-Mプロセッサを利用するするためのクレートです。具体的には、コアのペリフェラルやレジスタへのアクセス機能、割り込み機能などを提供します。*1
cortex_m - Rust

cortex_m_rt

Cortex-M マイクロコントローラの起動コードと最小のランタイムを提供します。#[entry]アトリビュートはこのランタイムクレートが提供します。また、メモリレイアウトの管理(memory.x)、静的変数の初期化、FPU(浮動小数点数ユニット)の初期化なども含みます。
cortex_m_rt - Rust
STM32CubeF4/STM32CubeF4GettingStarted.pdf at master · STMicroelectronics/STM32CubeF4 · GitHub

stm32f4xx_hal

STMicro STM32F4 シリーズ マイクロコントローラ向けペリフェラルアクセスAPIのクレートです。具体的には、embedded_halクレートで定義されているトレイトを実装しています。このクレートのおかげで抽象化された命令でプログラミングすることができます。
GitHub - stm32-rs/stm32f4xx-hal: A Rust embedded-hal HAL for all MCUs in the STM32 F4 family

stm32f4xx_hal::prelude::*

use crate::hal::{prelude::*};にあたる部分のクレートです。preludeは対象クレート(ここではstm32f4xx_hal)のうち必要最小限な分をimportするように設定されたリストです。*2
stm32f4xx_hal::prelude - Rust

crate::hal::{stm32};

use crate::hal::{stm32};にあたる部分のクレートです。 STM32F4シリーズに実装されているハードウェアのインターフェースへアクセスするためのモジュールが定義されている、のだと思います(推測)。
stm32f4xx_hal::stm32 - Rust

main()

ようやくmain()関数に入りました。

main() -> ! { loop {} }

main()関数の戻り値は「!」です。これは発散する関数(diverging function)と呼ばれ、決してその関数が戻らないことを意味します。これは#[entry]アトリビュートによって要求される実装になります。また、戻らないようにするためには、例えばloop{}を実装します。
Diverging functions - Rust By Example

Peripheralsの取得

stm32とCortex-M processorsのPeripheralsのインスタンスを取得して、GPIOなどを操作できるようにします。これらのインスタンス取得はtake()で一度しか取得できないようになっていて、2回目以降に取得するとNoneが返ってきます。つまり、シングルトンをRustの所有権で保証してくれている、誤った実装をしたらコンパイルエラーで教えてくれる、ということでRust素晴らしいですね。

    if let (Some(dp), Some(cp)) = (
        stm32::Peripherals::take(),
        cortex_m::peripheral::Peripherals::take(),
    ) {

stm32f3xx_hal::stm32::Peripherals - Rust
cortex_m::peripheral - Rust

LEDオブジェクトの取得

LEDへはGPIOを介して接続されているので、GPIOの取得、LEDオブジェクト(PA5ピン)の取得という順番になります。またオブジェクト取得時にそのピンをどのような設定で利用するかを指定します。今回はinto_push_pull_outputを指定しています。これは、high or low駆動する出力ピンとしてピン設定することを意味します。

        // Set up the LED. On the Nucleo-446RE it's connected to pin PA5.
        let gpioa = dp.GPIOA.split();
        let mut led = gpioa.pa5.into_push_pull_output();

stm32f1xx_hal::gpio - Rust

clockオブジェクトの取得

LEDをチカチカさせるためには、一定時間間隔の処理、wait処理が必要です。そのためにclockオブジェクトを取得します。手順は、RCCオブジェクト取得、クロック凍結という流れになります。RCCはReset and Clock Controllerの略称のようです。クロック凍結は、クロック設定を確定し、以降変更しないことを意味します。この場合48MHzで設定しています。48MHzが何に起因して設定しているかは未確認です。

        // Set up the system clock. We want to run at 48MHz for this one.
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(48.mhz()).freeze();

https://www.st.com/content/ccc/resource/training/technical/product_training/group0/c8/9e/ff/ac/7a/75/42/d1/STM32F7_System_RCC/files/STM32F7_System_RCC.pdf/jcr:content/translations/en.STM32F7_System_RCC.pdf

delayオブジェクトの取得

clockオブジェクトが取得できたので、そこからdelayオブジェクトを取得します。このdelayにはcp.SYSTとあるようにCortex-M processors側のSysTickも必要なようです。詳細未確認です。

        // Create a delay abstraction based on SysTick
        let mut delay = hal::delay::Delay::new(cp.SYST, clocks);

stm32f4xx_hal::delay - Rust

Lチカ

これまででLチカに必要な準備が完了しました。長かったですね笑。準備のおかげでLチカ部分の実装は非常にシンプルです。LEDを1秒間隔でONとOFFに切り替える処理を無限ループしています。

        loop {
            // On for 1s, off for 1s.
            led.set_high().unwrap();
            delay.delay_ms(1000_u32);
            led.set_low().unwrap();
            delay.delay_ms(1000_u32);
        }
    }

おわりに

とても長い記事になりましたが、Lチカの解読をしました。Lチカを行っている核部分はシンプルでしたが、シンプルにするための準備部分の解読が大変でした。が、STM32F4向けに必要な最低限のAttributesとCratesについては触れることができました。また、各機能ごとにドキュメント化されているので、適宜詳細を追っていければと思います。
ようやく組込みRustに入門できた気がします。ここからさらに組込みRust本を参考に進んでいきたいと思います。今回の記事作成にあたってもたくさん参照させていただきました。組込みRust本素晴らしいです。

*1:このクレートのおかげでRustの安全性の恩恵を受けて低レイヤのプログラミングができるようになる、と私は理解しています。

*2:これを指定しておくことで規模の大きいクレートをいい感じに利用開始できる、と私は理解しています。