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

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

RustでST Nucleo Board STM32F411RET6をLチカさせる

はじめに

先日Raspberry Pi Picoを購入した際に、送料無料にするためにST Nucleo Board STM32F411RET6も合わせて購入しました。これをRustでLチカさせたので、手順をまとめます。実装は以下GitHubにまとめています。
github.com

概要

  • RustでSTM32F4シリーズ向けのクロスコンパイル環境構築手順を書きました。
  • stm32f4xx-halクレートのREADMEに沿ってLチカ手順を書きました。
  • STM32F4シリーズのマイコンに向けてRustでLチカしたい人向けの記事です。

ST Nucleo Board STM32F411RET6とは

www.switch-science.com

  • STM32F4シリーズに属するマイコンボードです。
  • Nucleoボードで安価でプロトタイプ向けです。
  • 高性能マイコンと謳われています。

STM32とは?F4シリーズとは?Nucleoとは?といろいろな特徴があります。詳しくは下記のような記事が参考になります。今回のポイントは安価(2,000円)でプロトタイプ向けという点です。
ARMコアベースのSTM32F4シリーズ製品|32bitのARMコア搭載マイコン
www.kumikomi.jp

開発環境の構築

Rustのインストール

OSはUbuntu 20.04を使用しています。

 sudo apt update
 sudo apt upgrade
 sudo apt install gcc
 sudo apt install curl
 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
 ls ~/.cargo/bin
 source $HOME/.cargo/env
 cargo new hello
 cd hello
 cargo run

以上で、ホストマシン側でHello World!します。

Rustのクレートの追加

クロスコンパイル環境の構築に向けて、cargo-editを追加します。

sudo apt-get install libssl-dev
cargo install cargo-edit

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

ターゲットボード(ST Nucleo Board STM32F411RET6)のコア(ARM Cortex-M4)に合わせたクロスコンパイル環境を準備します。ARM Cortex-M4 向け(FPU搭載)のターゲットトリプル*1はthumbv7em-none-eabihfになります。

rustup target add thumbv7em-none-eabihf
cargo install cargo-binutils
rustup component add llvm-tools-preview

以上が開発環境の構築になります。ホスト側でHello World!は確認できているはずですが、クロスコンパイル環境の動作確認はできず、以降のLチカで確認することになります。また、Rustを初めてインストールする場合は、エディタ環境の構築もあっても良いかもしれませんが、今回はほぼコードのコピペなので省略します。

Lチカプロジェクトの構築

いよいよLチカに向けてプロジェクトを構築していきます。STM32F4向けにはすでにHALクレートが用意されており、これを利用する方法を採用します。HALとは、Hardware Abstraction Layerの略称でハードウェア制御を抽象化し、ハードウェアごとの違いを吸収してくれているレイヤーです。ここらへんのファームウェア構造については基礎から学ぶ 組込みRustのPage145 embedded-halの説明がわかりやすいです。

まずは、新しいプロジェクトを作成します。

cargo new LED-blink
cd LED-blink/
cargo run

Lチカコードのコピペ

ここから実装になっていくのですが、stm32f4xx-halクレートのREADMEに沿ってほぼコピペです。今回のターゲットはST Nucleo Board STM32F411RET6ですが、STM32F4シリーズであれば、他のボードでもターゲットに合わせてCargo.tomlのターゲット指定とmemory.xのメモリマップ指定を変更すれば、あとは以下同様の手順でLチカができるはずです。

以下をstm32f4xx-halのリポジトリからコピペします。

  • examples/delay-blinky.rs
  • Cargo.toml
  • .cargo/config
  • memory.x

 注:Cargo.tomlでターゲット指定の部分のみ修正します。

examples/delay-blinky.rs

main.rsに下記をコピペします。これがLチカの核となるソースコードです。

#![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 {}
}

Cargo.toml

以下をCargo.tomlに追記でコピペします。ただし、featuresの部分はターゲットに合わせてf411に修正が必要です。これらが追加するクレートになります。すでに[pakage]の情報がありますが、それは残したままで良いです。

[dependencies]
embedded-hal = "0.2"
nb = "0.1.2"
cortex-m = "0.6"
cortex-m-rt = "0.6"
# Panic behaviour, see https://crates.io/keywords/panic-impl for alternatives
panic-halt = "0.2"

[dependencies.stm32f4xx-hal]
version = "0.8"
features = ["rt", "stm32f411"] # replace the model of your microcontroller here

.cargo/config

新規にフォルダを作成してファイルを格納します。ここでは、ビルドのターゲットを指定します。

[target.thumbv7em-none-eabihf]
runner = 'probe-run --chip stm32f411'
rustflags = [
  "-C", "link-arg=-Tlink.x",
]

[build]
target = "thumbv7em-none-eabihf"

memory.x

プロジェクトの直下に新規ファイルとして作成しコピペします。これは対象のCPUがどのようなメモリアドレス空間を持っているかを示すファイルです。cortex-m-rtクレートで動作するため必要です。メモリアドレス空間の記述はサンプルとターゲットが合致しているため修正は不要です。

MEMORY
{
  /* NOTE K = KiBi = 1024 bytes */
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K 
  RAM : ORIGIN = 0x20000000, LENGTH = 32K
}

/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

Lチカコードのビルド

以上で、必要な実装(コピペ)は完了です。これをビルドします。ビルドに必要なターゲット指定は.cargo/configで行っているため、オプション不要で下記のコマンドでOKです。

cargo build

ターゲットへ実行ファイルを送る

ビルドした実行ファイルをターゲットへ書き込みます。書き込みにはOpenOCDを利用します。

OpenOCDのインストール
sudo apt install openocd
OpenOCDの.cfgファイル

プロジェクトの直下に下記ファイルを作成します。これはnucleo-f411reのものを利用させてもらっています。これを実行時に指定します。

source [find interface/stlink-v2-1.cfg]

transport select hla_swd

source [find target/stm32f4x.cfg]

reset_config srst_only

OpenOCDの実行

以下コマンドでターゲットへ実行ファイルを送ります。プロジェクト名はLED-blinkの想定です。

openocd -f nucleo.cfg -c"program target/thumbv7em-none-eabihf/debug/LED-blink verify reset exit"

Verified OK
Resetting Target
と出ていれば書き込みは成功しており、1秒間隔でLチカしているはずです。お疲れ様でした。

おわりに

RustでLチカやりたい!と思っていろいろ調べていたのですが、適切な情報になかなかたどり着けず、四苦八苦していました。そんな中で、個人的にはstm32f4xx-halクレートのREADME.mdが一番わかりやすかったです。今回のターゲットにはボタンが着いていたり、他にもたくさんのインタフェースがあるので、今後はそれらも使っていってみたいと思います。また、コピペの紹介だけったのでその中身の解説もできたらと思います。


参考

マニュアルのメモリマップ

https://www.st.com/resource/en/datasheet/stm32f411ce.pdf

f:id:yuji-tanaak:20210525153516p:plain
ターゲットのメモリマップ
f:id:yuji-tanaak:20210525153530p:plain
Flash MemoryとSRAMのメモリマップ部分拡大

*1:ターゲットプラットフォームを表す形式で、プロセッサアーキテクチャ-OS-ABIの順番で表記されます。ターゲットプラットフォーム一覧はPlatform Support - The rustc bookに記載されています。