今回はhalを使ってGPIOを抽象化し、SwとLed構造体をつくってみます。
Sw(スイッチ)の方は Nucleoボードに対して追加の回路が必要です。
回路については 前回の記事 をご覧になってください。
開発環境は以下の通りです。
PC:Windows10 OS
Board:STM32Nucleo-F401RE
デバイス:STM32F401RE
エディタ:VSCode
言語:Rust
ボードの情報は こちら からご覧いただけます。
環境構築については こちら をご覧になってください。
halとは
halとは Hardware Abstraction Layer の略で日本語では「ハードウエァ抽象化層」です。
halはハードウェアごとの仕様の違いを吸収し、ユーザーが同じ方法で取り扱えるようにする役割をもつ階層になります。
ベアメタルでレジスタに書く方法に比べると抽象化されているので、コードを書きやすく見やすいので保守の面でも良さそうです。
HAL と hal : 大文字と小文字のどちらで書くべきか迷いましたが、クレート名が小文字なので hal と書くことにしました。
embedded_hal と stm32f4xx_hal
組み込み用のhalクレートして embedded_hal が基礎としてあり、その上に各メーカー等の hal があります。
こちらで今使っているマイコンに対しては stm32f4xx_hal を使います。
プロジェクトをつくる
コマンドプロンプトを起動し、cargo generate と git を使ってSTM32F401用のプロジェクトを作成します。
cargo generate –git https://github.com/rust-embedded/cortex-m-quickstart.git と入力しプロジェクト名を聞いてくるので stm32f401-gpio3 と入力します。
stm32f401-gpio-hal という名前でプロジェクトをつくろうとすると、エラーが出てしまうのでしかたなく gpio3 にしました。
(gpio, gpio2 はすでにつくっているプロジェクトなので今回は gpio3 にしました)
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart.git
Unable to load config file: C:\Users\xxxxx\.cargo\cargo-general.toml
Project Name : stm32f401-gpio3
C:\Users\xxxxx\stm32f401-gpio3 というフォルダができていることを確認します。
xxxxx は皆さまのユーザー名です。
VSCodeを起動する
以下のようにコマンドを入力することで、stm32f401-gpio3をカレントディレクトリとしてエディタ(VSCode)が起動します。
cd stm32f401-gpio3
code .
C:\Users\xxxxx>cd stm32f401-gpio3
C:\Users\xxxxx\stm32f401-gpio3>code .
config.tomlの編集
gdbの指定と Cortex-M4F をターゲットに指定します。
ツリーの >.cargo の >部分をクリックすると V .cargo となり、そのツリー下に config.toml というファイルが見えるのでこれをクリックして開きます。
(1)こちらの環境では 8行目に # runner = “arm-none-eabi-gdb -q -x openocd.gdb” があるので、先頭の # をはずします。
(2)こちらの環境では35行目に target = “thumbv7m-none-eabi” # Cortex-M3 があるので、先頭に # をつけます。
前後の行に合わせて # target = … のように記述しておきます。
(3)こちらの環境では37行目に # target = “thumbv7me-none-eabihf” # Cortex-M4F and Cortex-M7F (with FPU) があるので、先頭の # をはずします。
# のある行はコメント(無効)になります。
編集が終わったら Ctrl + s で保存しておきます。
launch.jsonの編集
ここではデバイスの指定を行います。
ツリーの >.vscode の >部分をクリックすると V .vscode となり、そのツリー下に launch.json というファイルが見えるのでこれをクリックして開きます。
“configurations”: の中に {},{} で2つの項目が区切られています。
上の方は “name” が “Debug (QEMU)” となっていて、下の “name” が “Debug (OpenOCD)” です。
QEMUはエミュレーターで、今回は OpenOCD による Debug を行いますので、下の {} 内だけを編集します。
(1)こちらの環境では35行目に #device”: “STM32F303VCT6” とあるので “STM32F401RET6” に変更します。
(2)こちらの環境では38行目に “target/stm32f3x.cfg” とあるので “target/stm32f4x.cfg” に変更します。
(3)こちらの環境では40行目に “svdFile”: “${workspaceRoot}/.vscode/STM32F303.svd” とあるので最後の 303 を 401 に変更します。
編集が終わったら Ctrl + s で保存しておきます。
Cargo.tomlの編集
ツリーの下の方に Cargo.toml があります。
23~25行を以下のように編集します。
dependencies は依存関係の意味で、こういうクレートの、このバージョンのものを使いますよ とRustに教えてあげます。
今回は hal を使うので stm32f4xx_halクレートを指定します。
新しいバージョンではエラーが出ることがあるので今回は “0.9” を選択しました。
[dependencies.stm32f4xx-hal]
features = ["stm32f401", "rt"]
version = "0.9"
編集が終わったら Ctrl + s で保存しておきます。
memory.xの編集
ツリーの下の方に memory.x があります。
デバイスによってFlashメモリーとRAMの容量が異なるので、ここで指定します。
6, 7行目を以下のように編集します。
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 96K
編集が終わったら Ctrl + s で保存しておきます。
svdファイル
svdファイルはデバッグに必要なファイルです。
svdファイルは こちら からダウンロードできます。
解凍して、data\STMicroの中にある STM32F401.svd を C:\Users\xxxxx\stm32f401-gpio3\.vscode に保存します。
ソースコード
#![no_std]
#![no_main]
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
use cortex_m_rt::entry;
use stm32f4xx_hal::{prelude::*, stm32, gpio::*}; // (1)
use stm32f4xx_hal::gpio::gpioa::PA5; // (2)
use stm32f4xx_hal::gpio::gpioa::PA6; // (3)
#[entry]
fn main() -> ! {
let dp = stm32::Peripherals::take().unwrap(); // (4)
let gpioa = dp.GPIOA.split(); // (5)
let mut led = Led::new(gpioa.pa5); // (6)
let sw = Sw::new(gpioa.pa6); // (7)
loop {
if sw.is_pressed() { // (8)
led.turn_on(); // (9)
} else {
led.turn_off(); // (10)
}
}
}
struct Sw { // (11)
pin: PA6<Input<Floating>> // (12)
}
impl Sw { // (13)
fn new(pin: PA6<Input<Floating>>) -> Sw { // (14)
Sw { pin: pin.into_floating_input() } // (15) Sw { pin: pin }
}
fn is_pressed(&self) -> bool { // (16)
self.pin.is_low().unwrap() // (17)
}
fn is_released(&self) -> bool { // (18)
self.pin.is_high().unwrap() // (19)
}
}
struct Led { // (20)
pin: PA5<Output<PushPull>> // (21)
}
impl Led { // (22)
fn new(pin: PA5<Input<Floating>>) -> Led { // (23)
Led { pin: pin.into_push_pull_output() } // (24)
}
fn turn_on(&mut self) { // (25)
self.pin.set_high().unwrap(); // (26)
}
fn turn_off(&mut self) { // (27)
self.pin.set_low().unwrap(); // (28)
}
}
コード概要
(1)~(3)useを使って簡単に記述できるようにする。
(4)Peripheralsの取得
(5)GPIOAを分割する。この中でGPIOAのクロックも有効にしている。
(6)Led構造体の生成
(7)Sw構造体の生成
(8)スイッチが押されていたら
(9)LEDを点灯
(10)LEDを消灯
型状態プログラミング
ハードウェアの状態を型で表現する手法で 基礎から学ぶ組込みRust に書かれています。
型状態プログラミングはIOピンに対してInput, Output等の型をあてはめることによって、使える機能を制限することができます。
例えば入力ピンに対して出力用トレイトのメソッドを使おうとすると、コンパイルエラーになり誤りを教えてくれます。
今回はIOピン(構造体)に対するトレイト(メソッド)を使うために hal を使いました。
ゼロから作りこむよりも embedded_hal で定義されているトレイトをベースにつくる方が楽チンできます。
(11)Sw構造体
(12)PA6の型状態の定義、スイッチは入力なので設定後(new()の後)は、この Input になるようにします。
ただし元々、GPIOピンが入力なのでかわらないだけです。
(13)ここからメソッド等の実装
(14)関連関数
お馴染みの new() ですね。関連関数なので self が出てきません。
(15)into_floating_input()していますが元々 Input なので、 Sw { pin: pin } と書いても良さそうです。
(16)押されているか確認するメソッド
(17)こちらは InputPin トレイトのメソッドになります。
トレイトは こちら で確認することができます。
Result型が返るので、unwrap()して取り出しています。
(18)離しているか確認するメソッド(今回は使っていません)
(19)こちらも同じトレイトのメソッドです。
(20)Led構造体
(21)PA5の型状態の定義、LEDにつながるポートを制御するの設定後(new()の後)は、Outputにしなければなりません。
(22)ここからメソッド等の実装
(23),(24)関連関数
初期状態が入力で、new()の後は出力にしなければなりません。
従って、引数で入力の型を受け取って、into_push_pull_output()でそれをプッシュプル出力に変換しています。
(25)LEDを点灯するメソッド
フィールドを操作するので、 &mut self が必要です。
(26)こちらは OutputPin トレイトのメソッドになります。
トレイトは こちら で確認することができます。
Result型が返るので、unwrap()して取り出しています。
(27)LEDを消灯するメソッド
(28)こちらも同じトレイトのメソッドです。
動作させてみる
それでは F5キーでプログラムを起動した後、entry で停止したらもう一度F5キーを押してプログラムを動作させてみます。
スイッチを押すと、LD2(緑色)が点灯し、離すと消えます。
デバッグモードは OpenOCD に設定する必要があるので、エラーが出る場合には確認してみてください。
いかがでしたか?
皆さんの環境では、うまく動作しましたか?
今回のプロジェクトを stm32f401-gpio3 におきましたので、よろしければ参考になさってください。
お疲れさまでした。