Rust: Xargoとarm-binutils無しでCortex-Mターゲットのファームウェアをビルドする

2018年4月、Rustでの組み込みプログラミング環境に2つの大きなニュースが舞い込んできた。

Xargo と arm-none-eabi-lld 無しでCortex-M向けのビルドが可能に

1つ目は、rustupのターゲットに thumbv*m-none-eabi* (Cortex-MのTarget Triple)が追加されたというニュース。

今まではrustupでは、thumbv*m-none-eabi* ターゲットがサポートされておらず、Xargo というツールを使ってlibcoreを thumbv*m-none-eabi* 用にビルドしなくてはならなかった。 しかし、nightly (nightly-2018-04-08) 以降のRustでは thumbv*m-none-eabi* が追加されたため、Xargoを使って自前でlibcoreをビルドする必要はなくなる。

nightly 2018-04-09で確認したところ、確かにターゲットリストに thumbv*m-none-eabi* が追加されていた。

$ rustup show
Default host: x86_64-unknown-linux-gnu
...
active toolchain
----------------
nightly-x86_64-unknown-linux-gnu (default)
rustc 1.27.0-nightly (4b9b70c39 2018-04-09)

$ rustup target list | pr -w 90 --columns=2
aarch64-apple-ios                            mips64el-unknown-linux-gnuabi64
aarch64-linux-android                        mipsel-unknown-linux-gnu
aarch64-unknown-fuchsia                      mipsel-unknown-linux-musl
aarch64-unknown-linux-gnu                    powerpc-unknown-linux-gnu
aarch64-unknown-linux-musl                   powerpc64-unknown-linux-gnu
arm-linux-androideabi                        powerpc64le-unknown-linux-gnu
arm-unknown-linux-gnueabi                    s390x-unknown-linux-gnu
arm-unknown-linux-gnueabihf                  sparc64-unknown-linux-gnu
arm-unknown-linux-musleabi                   sparcv9-sun-solaris
arm-unknown-linux-musleabihf                 thumbv6m-none-eabi
armv5te-unknown-linux-gnueabi                thumbv7em-none-eabi
armv7-apple-ios                              thumbv7em-none-eabihf
armv7-linux-androideabi                      thumbv7m-none-eabi
armv7-unknown-linux-gnueabihf                wasm32-unknown-emscripten
armv7-unknown-linux-musleabihf               wasm32-unknown-unknown
armv7s-apple-ios                             x86_64-apple-darwin
asmjs-unknown-emscripten                     x86_64-apple-ios
i386-apple-ios                               x86_64-linux-android
i586-pc-windows-msvc                         x86_64-pc-windows-gnu
i586-unknown-linux-gnu                       x86_64-pc-windows-msvc
i586-unknown-linux-musl                      x86_64-rumprun-netbsd
i686-apple-darwin                            x86_64-sun-solaris
i686-linux-android                           x86_64-unknown-cloudabi
i686-pc-windows-gnu                          x86_64-unknown-freebsd
i686-pc-windows-msvc                         x86_64-unknown-fuchsia
i686-unknown-freebsd                         x86_64-unknown-linux-gnu (default)
i686-unknown-linux-gnu                       x86_64-unknown-linux-gnux32
i686-unknown-linux-musl                      x86_64-unknown-linux-musl
mips-unknown-linux-gnu                       x86_64-unknown-netbsd
mips-unknown-linux-musl                      x86_64-unknown-redox
mips64-unknown-linux-gnuabi64

2つ目は、Cortex-Mバイナリ用のリンクにlldを使えるようになったというニュース。

今までは最後のリンクにはarm-binutilsのarm-none-eabi-ldを使ってリンクを行なっていたが、それをlldに置き換えられるようになった。 これはcoretex-m-rtクレートに含まれているリンカスクリプトの記述をlldに対応させたことで可能になったようだ。

この2つの変更によって、RustでのCortex-Mターゲット向けの開発のために必要なツールが減り、開発環境構築のハードルが大きく下がった印象だ。 (あとはgdbをlldbに置き換えることができれば、arm-binutilsは一切不要になる)

このニュースを報告しているJaparicさんはXargoの作者でもあり、Rustでの組み込み開発環境をずっとリードし続けている。 この方がいなかったら、今のRustはここまで組み込み分野を視野に入れたものになっていただろうか…。

nightly-2018-04-08 以降でのCortex-Mターゲットのファームウェアビルド方法

Xargo無し、arm-binutils無しでのビルド手順についてメモしておく。
動作確認には、STM32F401 Nucleo-64ボード(Cortex-M4F)を使用する。

大まかな手順は次のとおり。

  1. rustupを最新にする
  2. rustupから対象となるarm MCUthumbv*m-none-eabi* ターゲットを追加する
  3. Rustプロジェクトを作成する。
  4. .cargo/config にリンカの設定を記述する
  5. 必要なクレートを Cargo.toml追記する
  6. コードを書いてビルドする

rustupを最新にする

$ rustup update

また、ビルドにはnightlyが要求されるので、デフォルトをnightlyに変更しておく。

$ rustup default nightly

thumbv*m-none-eabi* をターゲットに追加する

今回動作確認を行うMCUはCortex-M4Fなので、rustupから thumbv7em-none-eabihf をターゲットとして追加する。

$ rustup target add thumbv7em-none-eabihf
info: downloading component 'rust-std' for 'thumbv7em-none-eabihf'
info: installing component 'rust-std' for 'thumbv7em-none-eabihf'

自分が使うCortex-M MCUがどのTarget Tripleを選べばよいかについては次のとおり。

Target Triple Target MCUs
thumbv6m-none-eabi Targets the Cortex-M0, Cortex-M0+ and Cortex-M1 processors (ARMv6-M architecture)
thumbv7em-none-eabi Targets the Cortex-M4 and Cortex-M7 processors (ARMv7E-M)
thumbv7em-none-eabihf Targets the Cortex-M4F and Cortex-M7F processors (ARMv7E-M)
thumbv7m-none-eabi Targets the Cortex-M3 processor (ARMv7-M)

Rustプロジェクトを作成する

LEDを点滅させる簡易プログラムのリポジトリを作成したため、この手順ではこちらを使う。
ryochack/Rust_NUCLEO-F401RE_Led_Blink

$ git clone https://github.com/ryochack/Rust_NUCLEO-F401RE_Led_Blink.git

一から作る場合には、 cargo new でプロジェクトを作成する。

.cargo/config にリンカの設定を記述する

.cargo/config にターゲット毎にリンカの設定を記述する。 ここではリンカにlldを指定している。

.cargo/config

[target.thumbv7em-none-eabihf]
runner = 'arm-none-eabi-gdb'
rustflags = [
  "-C", "link-arg=-Tlink.x",
  "-C", "linker=lld",
  "-Z", "linker-flavor=ld.lld",
  "-Z", "thinlto=no",
]

必要なクレートを Cargo.toml追記する

thumbv*m-none-eabi* のビルドに必要なクレートを Cargo.toml にそれぞれ追加する。

Cargo.toml

[package]
name = "nucleo-f401re_led_blink"
authors = ["ryochack"]
version = "0.1.0"

[dependencies]
cortex-m = "0.4.0"
cortex-m-rt = "0.4.0"
panic-abort = "0.1.1"
volatile-register = "0.2.0"

コードを書いてビルドする

コードが書けたら(ここではRust_NUCLEO-F401RE_Led_Blinkリポジトリのコードを使う想定)、次のコマンドでターゲットを指定してビルドする。

$ cargo build --target thumbv7em-none-eabihf

これでSTM32F401用のELFファイルが target/thumbv7em-none-eabihf/debug/nucleo-f401re_led_blink として出力される。

$ file nucleo-f401re_led_blink
nucleo-f401re_led_blink: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped

このELFファイルを次の記事の内容に従ってSTM32F401 Nucleo-64ボードに書き込むことで動作が確認できる。
OpenOCD + ST-LinkでFirmware書き込み - ryochack.blog