STM32 Rust モジュール化してファイルを分ける

STM32 Rust モジュール化してファイルを分ける

前回GPIOを抽象化し、SwとLed構造体をつくりました。
今までソースコードは全て main.rs に記述してきましたが、規模が大きくなるとファイルを分ける必要が出てきます。

規模は小さいけれど、SwとLed構造体の部分だけを別のファイルに分けてみます。

Sw(スイッチ)の方は Nucleoボードに対して追加の回路が必要です。
回路については 前々回の記事 をご覧になってください。

開発環境は以下の通りです。

PC:Windows10 OS
Board:STM32Nucleo-F401RE
デバイス:STM32F401RE
エディタ:VSCode
言語:Rust

ボードの情報は こちら からご覧いただけます。
環境構築については こちら をご覧になってください。

プロジェクトをつくる

コマンドプロンプトを起動し、cargo generate と git を使ってSTM32F401用のプロジェクトを作成します。

cargo generate –git https://github.com/rust-embedded/cortex-m-quickstart.git と入力しプロジェクト名を聞いてくるので stm32f401-gpio4 と入力します。

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-gpio4

C:\Users\xxxxx\stm32f401-gpio4 というフォルダができていることを確認します。

xxxxx は皆さまのユーザー名です。

VSCodeを起動する

以下のようにコマンドを入力することで、stm32f401-gpio4をカレントディレクトリとしてエディタ(VSCode)が起動します。

cd stm32f401-gpio4
code .

C:\Users\xxxxx>cd stm32f401-gpio4
C:\Users\xxxxx\stm32f401-gpio4>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-gpio4\.vscode に保存します。

ライブラリクレートを追加する

ファイルを分割するのに必要な作業になります。
ファイル分割の方法は、いろいろあると思うのですが今回はライブラリクレートを使ってみます。
(正直、この方法の長所もわかっていないのですけれど・・・)

まず、カレントディレクトリを stm32f401-gpio4 にします。
例.

C:\Users\xxxxx>cd stm32f401-gpio4
C:\Users\xxxxx\stm32f401-gpio4>

cargo new –lib stm32lib と入力してライブラリクレートをつくります。

C:\Users\xxxxx\stm32f401-gpio4>cargo new --lib stm32lib
    Created libral `stm32lib` package

stm32libディレクトリがつくられ、その下に src/lib.rs というファイルがつくられます。

lib.rs を以下のように編集して保存します。

#![no_std]
pub mod gpio;

このファイルは、単に橋渡しをするためだけのものです。

Cargo.toml

ライブラリクレートをつくったので、Cargo.toml は2つ存在します。

まず上の階層の Cargo.toml にstm32libのパスを指定し、その存在を教えます。

[dependencies]
stm32lib = { path = "stm32lib" } 

次に stm32libにある Cargo.toml には以下のように hal を追加します。
gpioモジュールで使うために必要です。

[dependencies.stm32f4xx-hal]
features = ["stm32f401", "rt"]
version = "0.9"

モジュール gipo.rs

次に lib.rsと同じ階層(ディレクトリ)に gpio.rs というファイルをつくり以下のように編集して保存します。
各構造体とメソッドには pub キーワードをつけて、外からも使えるようにしておきます。

use stm32f4xx_hal::{prelude::*, gpio::*};
use stm32f4xx_hal::gpio::gpioa::PA5;
use stm32f4xx_hal::gpio::gpioa::PA6;

pub struct Sw {
    pin: PA6<Input<Floating>>
}

impl Sw {
    pub fn new(pin: PA6<Input<Floating>>) -> Sw {
        Sw { pin: pin.into_floating_input() }
    }
    pub fn is_pressed(&self) -> bool {
        self.pin.is_low().unwrap()
    }
    pub fn is_released(&self) -> bool {
        self.pin.is_high().unwrap()
    }
}

pub struct Led {
    pin: PA5<Output<PushPull>>
}

impl Led {
    pub fn new(pin: PA5<Input<Floating>>) -> Led {
        Led { pin: pin.into_push_pull_output() }
    }
    pub fn turn_on(&mut self) {
        self.pin.set_high().unwrap();
    }
    pub fn turn_off(&mut self) {
        self.pin.set_low().unwrap();
    }
}

main.rs

構造体を引っ越ししたので、見通しが良くなりました。

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

use stm32lib::gpio::Sw; // (1)
use stm32lib::gpio::Led; // (2)

#[entry]
fn main() -> ! {
    let dp = stm32::Peripherals::take().unwrap();
    let gpioa = dp.GPIOA.split();
    let mut led = Led::new(gpioa.pa5);
    let sw = Sw::new(gpioa.pa6);

    loop {
        if sw.is_pressed() {
            led.turn_on();
        } else {
            led.turn_off();
        }
    }
}

コード概要

(1),(2)useを使ってSwとLedを簡単に記述できるようにしておきます。
gpio.rsはlib.rsと同じフォルダにありますが、パスはこのように stm32lib::gpio という階層になります。

動作させてみる

それでは F5キーでプログラムを起動した後、entry で停止したらもう一度F5キーを押してプログラムを動作させてみます。
スイッチを押すと、LD2(緑色)が点灯し、離すと消えます。

デバッグモードは OpenOCD に設定する必要があるので、エラーが出る場合には確認してみてください。

いかがでしたか?
皆さんの環境では、うまく動作しましたか?

今回のプロジェクトを stm32f401-gpio4 におきましたので、よろしければ参考になさってください。

お疲れさまでした。

GPIOカテゴリの最新記事