Rustは組み込み用途にも使えると聞き、実際にRustでSTM32を動かしてみた。
windowsでの環境構築で少し詰まったところがあったのでメモ。
目次
必要なソフト
cargo-binutils
: LLVMツールを簡単に使うためのサブコマンド一式、ビルドしたバイナリを解析したりもできる。
qemu-system-arm
: armシステムのエミュレータ(今回は使わなかった。ボードがないけど試したいときは必要。)
openocd
: デバッガ、マイコンのメモリにアクセスしたりできる。マイコンのFLASHメモリに書き込むこともできる ★
gdb(arm aupported)
: デバッガ、ボードにプログラムをロードしたりもできる。 ★
git
もしくは cargo-generate
: テンプレートをダウンロードするため 今回はgit
を使う
**★のついた2つが重要 これらがPCとSTM32を接続してくれる。**
今回使用したボード
Nucleo Board STM32F446RE
画像:秋月電子通商より
https://akizukidenshi.com/catalog/g/gM-10176/
Cortex-M4
アーキテクチャのCPUとFPU
が搭載されているのが特徴。
必要なソフトウェアのインストール
なお、通常のRustの環境は構築されているものとする。
1, ARM Cortex-Mアーキテクチャ用にターゲットを追加
rustup target add thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf
これによって今回のボード用にバイナリをビルドすることができるようになる。
2, cargo-binutilsのインストール
cargo install cargo-binutils
rustup component add llvm-tools-preview
これをインストールすることで、バイナリの解析等が行えるようになる。
3, arm-none-eabi-gdbのインストール
こちらより、gcc-arm-none-eabi-10.3-2021.07-win32.exe
をダウンロード、及びインストールを行う。
インストールするとき、環境変数を追加することを忘れないように。(あとで自分で追加しても問題ないとは思うが。)
環境変数を追加したうえで、ターミナル上でarm-none-eabi-gdb.exe
を実行してみると
GNU gdb (GNU Arm Embedded Toolchain 10.3-2021.07) 10.2.90.20210621-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=i686-w64-mingw32 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)
といった出力になればOK。
4, open-ocdのインストール
他のOSであれば、コマンドでインストールが可能であるが、Windowsではできないため、
こちらのリリースを使用する。
ここからxpack-openocd-0.11.0-1-win32-x64.zip
をダウンロード、解凍を行う。
解凍したフォルダは、
C:\Users\ユーザー名\AppData\Roaming\xPacks\OpenOCD\xpack-openocd-0.11.0-1
となるように配置する。(理由等は下記を参考に。)
配置したら、bin
フォルダ内にopenocd.exe
があるので、そのフォルダのパスを環境変数に追加する。
追加したら、ターミナルでopenocdを実行してみる。
xPack OpenOCD, x86_64 Open On-Chip Debugger 0.11.0-00155-ge392e485e (2021-03-15-16:44)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
embedded:startup.tcl:26: Error: Can't find openocd.cfg
in procedure 'script'
at file "embedded:startup.tcl", line 26
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Error: Debug Adapter has to be specified, see "adapter driver" command
embedded:startup.tcl:26: Error:
in procedure 'script'
at file "embedded:startup.tcl", line 26
といった出力になればOK。(エラーが出ているのは、ボードをつないでいないため。)
5, ST-LINK USBドライバのインストール
こちらより、windows用のドライバをダウンロードする。
ただし、ダウンロードするためにはメールアドレスが必要になる。
名前は適当でOK。メールアドレスにダウンロードのリンクが送られるので、そこからダウンロードする。
解凍したら、フォルダにあるdpinst_amd64.exe
を実行する。
各種ドライバのインストールができたら、PCとボードを接続しテストする。
つないだ状態で
openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg
を実行する。(ボードによってcfgファイルの指定が違うのだが、動作確認するだけなのでこれで行く。)
xPack OpenOCD, x86_64 Open On-Chip Debugger 0.11.0-00155-ge392e485e (2021-03-15-16:44)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
WARNING: interface/stlink-v2-1.cfg is deprecated, please switch to interface/stlink.cfg
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 1000 kHz
Info : STLINK V2J33M25 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.251968
Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f3x.cpu on 3333
Info : Listening on port 3333 for gdb connections
といった出力になればOK。これで今回必要なドライバのインストールは完了。
(必要であれば)QEMUのインストール
こちらからダウンロード、インストールを行う。
テンプレートの用意をする
今回テンプレートを使ってコードを書くため、下のものをcloneする。
クローンしたら、各設定ファイルを編集する。
1, Cargo.tomlを編集
[package]
authors = ["winzu44"] # 自分の名前
edition = "2018"
readme = "README.md"
name = "LED_test" # プロジェクト名
version = "0.1.0"
# 途中省略
[[bin]]
name = "LED_test" # 生成されるバイナリの名前
test = false
bench = false
上記3つを編集する。
name = ...
で設定した名前は、これ以降も出てくるのでメモしておく。
2, config.tomlを編集
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# uncomment ONE of these three option to make `cargo run` start a GDB session
# which option to pick depends on your system
runner = "arm-none-eabi-gdb -q -x openocd.gdb" # windowsなのでここをコメントアウトする
# runner = "gdb-multiarch -q -x openocd.gdb"
# runner = "gdb -q -x openocd.gdb"
# 途中省略
[build]
# Pick ONE of these compilation targets
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU)
target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) # 使用するボードに合わせてコメントアウトする
# target = "thumbv8m.base-none-eabi" # Cortex-M23
# target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU)
# target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU)
上記2つを編集する。
runner = ...
の部分は、cargo run
を実行するだけで、自動的にボードとの接続を行ってくれるようにしてくれる部分。
``target = ... ``` の部分で、ビルドターゲットを設定する。使うボードによって違うので注意。特にFPUの有無には気を付ける。
3, memory.xを編集
実際にボードに書き込む上で、メモリのどこからどこまでがFLASHメモリ、SRAMであるかを明示してやらないといけない。
こちらより、データシートを取得する。
このようなメモリマップがあった。ここより、どこがFLASHメモリ、SRAMの開始地点かを読み取れる。
今回の場合、
開始地点 | サイズ | |
---|---|---|
FLASH | 0x08000000 | 512KB |
SRAM | 0x20000000 | 128KB |
となったので(サイズはデータシートの他の部分に書いてあった)、
MEMORY
{
/* NOTE 1 K = 1 KiBi = 1024 bytes */
/* TODO Adjust these memory regions to match your device memory layout */
/* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 128K
}
のように編集する。
4, openocd.cfgを編集する
# Sample OpenOCD configuration for the STM32F3DISCOVERY development board
source [find interface/stlink-v2-1.cfg]
source [find target/stm32f4x.cfg]
cargo run
で実行するときに、どのcfgファイルを参考にするかを明示する。
特に重要なのが find target ...
の部分で、ここでどのボードを使っているか指定しないといけない。
cfgファイルは C:\OpenOCD\openocd-xpack\tcl\target
に入っているので、ここにあるものを指定する。
5, コードをビルドして実行してみる
ここからはターミナルを2つ使う(gdbとopenocdで)
カレントディレクトリをcortex-m-quickstartにしておく
まず、コードを編集する。
テンプレートのままだと、特に何も起こらないので、src\main.rs
を編集する
// デバッグログに"Hello World"を出力するプログラム。
# ![no_main] // mainインターフェースを使わないことを明示 使うにはnightlyが必要になるため
# ![no_std] // ベアメタル環境、つまりOSがないような環境であることを明示
extern crate panic_halt; // プログラムがパニック時の挙動を定義するためのクレート
use core::fmt::Write;
use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hio};
# [entry] // プログラムのエントリポイント
fn main() -> ! {
let mut stdout = hio::hstdout().unwrap();
writeln!(stdout, "Hello, world!").unwrap();
loop {}
}
いよいよプログラムをボードにロードする。 **ボードとPCは接続した状態にしておく。**
まず1つ目のターミナルでopenocd.exe
を実行する。
出力の最後のほうに
Info : starting gdb server for stm32f4x.cpu on 3333
Info : Listening on port 3333 for gdb connections
と出ていればOK。
次に2つ目のターミナルでcargo run
を実行する。
するとプログラムがビルドされ、自動的にgdb
が起動、ボードと接続される。
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x548 lma 0x8000400
Loading section .rodata, size 0x180 lma 0x8000948
Start address 0x08000400, load size 2760
Transfer rate: 5 KB/sec, 920 bytes/write.
halted: PC: 0x08000402
0x08000402 in cortex_m_rt::Reset ()
at C:\Users\ユーザー名\.cargo\registry\src\github.com-1ecc6299db9ec823\cortex-m-rt-0.6.15\src\lib.rs:497
497 pub unsafe extern "C" fn Reset() -> ! {
(gdb)
cargo run
を実行した後の出力
Info : accepting 'gdb' connection on tcp/3333
Info : device id = 0x10006421
Info : flash size = 512 kbytes
Info : flash size = 512 bytes
semihosting is enabled
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000400 msp: 0x20020000, semihosting
Info : Unable to match requested speed 8000 kHz, using 4000 kHz
Info : Unable to match requested speed 8000 kHz, using 4000 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000400 msp: 0x20020000, semihosting
Info : halted: PC: 0x08000402
openocd
を立ち上げていたターミナルの出力
うまく接続できたら、2つのターミナル両方でこのような反応があるはずである。
そしたらgdb
のターミナルで load
コマンドを実行する。
(gdb) load
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x1b28 lma 0x8000400
Loading section .rodata, size 0x580 lma 0x8001f28
Start address 0x08000400, load size 9384
Transfer rate: 15 KB/sec, 3128 bytes/write.
といった出力が出てきて、ボードにプログラムが読み込まれる。
同じくgdbのターミナルで continue
コマンドを実行すると
(gdb) continue
Continuing.
Breakpoint 4, LED_test::__cortex_m_rt_main_trampoline ()
at src\main.rs:11
11 #[entry]
このように main.rs
内の #[entry]
までスキップすることができる。
この状態で next
コマンドを実行すると...
(gdb) next
halted: PC: 0x080004de
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000400 msp: 0x20020000, semihosting
Info : Unable to match requested speed 8000 kHz, using 4000 kHz
Info : Unable to match requested speed 8000 kHz, using 4000 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000400 msp: 0x20020000, semihosting
Info : halted: PC: 0x08000402
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000400 msp: 0x20020000, semihosting
Info : Unable to match requested speed 8000 kHz, using 4000 kHz
Info : Unable to match requested speed 8000 kHz, using 4000 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000400 msp: 0x20020000, semihosting
Info : halted: PC: 0x080004de
Hello, world!
といったように、openocd側のターミナルでHello, world!が出力される!
これがリモートデバッグの手順となる。
光らせる
マイコンボードでのHello WorldといえばやはりLチカなのでやってみる。
STM32は本来であれば
- GPIOピンへのクロックを有効化
- GPIOピンを出力に設定
- 対応するレジスタに書き込みを行う
という手順を踏まないと、GPIOの出力を行えないが、embedded-hal
によってハードウェアを抽象化することによって、レジスタ操作など複雑な要素を隠し、アプリケーション開発に集中できるようになる。
embedded-halについてはこちらを参照。
1, クレートを追加する (Cargo.tomlを編集)
今回は以下のクレートを使った。ほかのボードでも同じようなクレートがある。
このクレートを使うため__Cargo.toml__を編集する。
[package]
authors = ["winzu44"]
edition = "2018"
readme = "README.md"
name = "LED_test"
version = "0.1.0"
[dependencies]
cortex-m = "0.6.0"
cortex-m-rt = "0.6.10"
cortex-m-semihosting = "0.3.3"
panic-halt = "0.2.0"
# 以下を追加
embedded-hal = "0.2"
nb = "1"
# Uncomment for the panic example.
# panic-itm = "0.4.1"
# Uncomment for the allocator example.
# alloc-cortex-m = "0.4.0"
# Uncomment for the device example.
# Update `memory.x`, set target to `thumbv7em-none-eabihf` in `.cargo/config`,
# and then use `cargo build --examples device` to build it.
# [dependencies.stm32f3]
# features = ["stm32f303", "rt"]
# version = "0.7.1"
# 以下を追加
[dependencies.stm32f4xx-hal]
version = "0.9"
features = ["rt", "stm32f446"]
# this lets you use `cargo fix`!
[[bin]]
name = "LED_test"
test = false
bench = false
[profile.release]
codegen-units = 1 # better optimizations
debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations
[dependencies]
の部分と、[dependencies.stm32f4xx-hal]
の部分に追加をする。
halを使ってコードを書くために、バージョン情報などを明記する。
2, Lチカするコードに書き換える (main.rsを編集)
これで準備が整ったのでLチカできるようにコードを編集する。
# ![no_main]
# ![no_std]
extern crate panic_halt;
use core::fmt::Write;
use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hio};
use stm32f4xx_hal as hal;
use crate::hal::{pac, prelude::*};
// 上の2文でhalを使う宣言をする
# [entry]
fn main() -> ! {
let mut stdout = hio::hstdout().unwrap();![Something went wrong]()
writeln!(stdout, "LED test").unwrap();
if let (Some(dp), Some(cp)) = (pac::Peripherals::take(),
cortex_m::peripheral::Peripherals::take(), )
// 上でペリフェラル等の初期化を行う
{
let gpioa = dp.GPIOA.split();
// 初期化ができたらGPIOを操作できる変数を作成
let mut led = gpioa.pa5.into_push_pull_output();
// PA5のポートを出力設定する
led.set_high();
// ここで出力をHIGHにする
}
loop {}
}
見て分かるように、複雑なレジスタ操作などは全くない。
ポートを設定し、その変数をいじるだけで出力を操作できる。
Arduinoほど簡単には書けないが、それでもかなり楽になっている。
3, ボードにLEDを接続
必要な配線を行う。
ボードに同梱されていたピンの配置が書かれた紙より、接続する場所をチェックする
今回は__PA5__から出力されるので、そこに適当な制限抵抗をつけて、LEDとつなげる。
4, プログラムをロードし、実行
コードが書けたら、先ほどのデバッグの手順と同じようにしてプログラムをロードする。
- ターミナルを二つ立ち上げ、片方で__openocd.exe__を、もう片方で__cargo run__を実行する。
- 接続ができたら、gdbが動いているターミナルで__continue__コマンドを打ち、main文までスキップする。
- スキップされたら__next__コマンドを打つと...
右側のターミナルで**"LED test"**と出力されると同時にLEDが点灯した!
まとめ
STM32をRustで動かすとなると、ハードルが高いように思えたが、用意されたテンプレートや、embedded-halのおかげである程度楽に動かせた。
gdbなど、デバッガの存在を知ることができてよかったと思う。
次はI2C通信でセンサの値などを読み取ってみたい。
参考
環境構築の流れ
embedded-halのクレート