STM32 Rust halでSPI (halを0.9から0.13にバージョンアップしてみた )

  • 2022.04.30
  • SPI
STM32 Rust halでSPI (halを0.9から0.13にバージョンアップしてみた )

以前halを使ってSPIを操作しました。
少し前にhal(stm32f4xx-hal)のバージョンが 0.13 に上がったので、今回は 0.13 で動作確認を行いました。

作業内容は STM32 Rust halでSPIを使ってみる と全く同じですから、詳細はそちらの記事を参考にしてください。

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

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

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

Rustのバージョンアップ

こちらの環境では今まで、rustc のバージョンが 1.57 でした。
halのバージョンを上げた後にビルドしたところ「バージョンアップせよ」と怒られました。

必要な方は以下を参考にしてバージョンアップを行ってください。

コマンドプロンプトを起動し、以下のコマンドを入力します。

rustup update

rustup update

Cargo.toml

以下のように記述します。

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
[dependencies.stm32f4xx-hal]
features = ["stm32f401", "rt"]
version = "0.13"

ソースコード

#![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::*;  // Hz()
use stm32f4xx_hal::gpio::*;

use stm32f4xx_hal::rcc::RccExt;
use stm32f4xx_hal::spi::*;
use stm32f4xx_hal::pac::*;

const WHO_AM_I: u8 = 0x0f;  // デバイス確認用のコマンド
const CTRL_REG1: u8 = 0x20; // コントロールレジスタ1
const WAKE_UP: u8 = 0x90;   // デバイスを起こすためのコマンド
const P_ADRS: u8 = 0x28;    // 気圧読み込み用のアドレス
const LPS25HB_DEVICE_CODE: u8 = 0xbd;

#[entry]
fn main() -> ! {

    let dp = stm32f4xx_hal::pac::Peripherals::take().unwrap();
    let gpioa = dp.GPIOA.split();   // GPIOAのclockも有効にしてくれる (AHBENRレジスタ)
    let mut cs = DigitalOut::new(gpioa.pa4);

    let rcc = dp.RCC.constrain();   // RCCの取得
    let clks = rcc.cfgr.freeze();   // 各clockの設定
    let mode = Mode { polarity: Polarity::IdleHigh, phase: Phase::CaptureOnSecondTransition };  // SPIのモード
    let hz = 1000_000u32.Hz();

    lps25hb_deselect(&mut cs);  // CS=Highにしておく
    let mut spi = Spi::new(
        dp.SPI1,
        (gpioa.pa5, gpioa.pa6, gpioa.pa7),
        mode,
        hz,
        &clks,
    );

    lps25hb_init(&mut spi, &mut cs);    // LPS25HBの初期化
    loop {

        let mut data: [u8; 4] = [P_ADRS | 0xc0, 0, 0, 0];

        lps25hb_select(&mut cs);
        lps25hb_send_buf(&mut spi, &mut data);
        lps25hb_deselect(&mut cs);
        let mut press = (data[3] as u32) << 16_u32 | (data[2] as u32) << 8_u32 | data[1] as u32;
        press >>= 12_i32;   // 1/4096
    }
}

fn lps25hb_init(spi: &mut Spi<SPI1, (PA5, PA6, PA7), TransferModeNormal>, cs: &mut DigitalOut) -> bool {

    lps25hb_select(cs);
    lps25hb_send(spi, WHO_AM_I | 0x80);     // WHO_AM_I コマンドを送る
    let res = lps25hb_send(spi, 0u8);   // 返事を読む
    lps25hb_deselect(cs);

    lps25hb_select(cs);
    lps25hb_send(spi, CTRL_REG1);           // CTRLREG1
    lps25hb_send(spi, WAKE_UP);             // 起床を指示
    lps25hb_deselect(cs);
    if res == LPS25HB_DEVICE_CODE { // デバイスコードが返ること
        return true;
    }
    false
}

fn lps25hb_select(cs: &mut DigitalOut) {    // CS=Low
    cs.select();
}

fn lps25hb_deselect(cs: &mut DigitalOut) {  // CS=High
    cs.deselect();
}

fn lps25hb_send(spi: &mut Spi<SPI1, (PA5, PA6, PA7), TransferModeNormal>, data: u8) -> u8 {
    while !spi.is_txe() {}
    spi.send(data).unwrap();    // 送って
    while !spi.is_rxne() {}
    spi.read().unwrap() // 読む
}

fn lps25hb_send_buf(spi: &mut Spi<SPI1, (PA5, PA6, PA7), TransferModeNormal>, data: &mut [u8]) {
    spi.transfer(data).unwrap();    // 送って読む
}

struct DigitalOut { // GPIO出力用の構造体
    pin: PA4<Output<PushPull>>
}

impl DigitalOut {
    fn new(pin: PA4) -> DigitalOut {
        DigitalOut { pin: pin.into_push_pull_output() }
    }
    fn deselect(&mut self) {
        self.pin.set_high();
    }
    fn select(&mut self) {
        self.pin.set_low();
    }    
}

コード概要

要所にコメントを入れたので参考にしてください。
DigitalOutというGPIO出力用の構造体を定義してみました。

気圧センサーの初期化:
lps25hb_init()

デバイスに対してコマンドを書きますが、コマンドに0x80を OR した値を書くとリード動作になり、次にダミーデータ(0)を送りながら値を読むことができます。
コマンドに0xc0を OR した値を書くと、やはりリード動作で、かつ読み込むレジスタのアドレスが自動的にインクリメントします。

戻り値で判定はしていませんが lps25hb_init()で true を返せは通信できていると判断して良さそうです。
WHO_AM_I に 0x80 を OR した値を送り、デバイスコードの読み込み指示を行っています。
その後 CTRL_REG1 には WAKE_UP(0x90) を書きパワーダウンを解除し、1Hzの出力データレートを設定しています。

loop{}内では P_ADRS に 0xc0 を OR した値を送っています。
これで3バイトの値をオートインクリメントで読み出しています。

Spi::transfer()というメソッドを使うと、読み込み用のコマンドを送信した結果を受信することができます。
配列の戻り値 data[0]は意味のない値です。
data[1],[2],[3]に要求した値が返ってきます。

press >>= 12; した値が気圧[hPa]として求められます。

本日の測定結果:

3バイトの合成値: 4182094(dec) = 3FD04E(hex)
これを 12ビット右シフトすると 3FD(hex) = 1021[hPa]

となりました。

halのバージョンが上がったことで全体的にコードがシンプルに書けるようになったと思います。

ビルドエラー対策

原因が良くわかっていないのですが、こちらの環境では stm32f401xx-hal のバージョンを0.13に上げてビルドするとエラーが出るようになってしまいました。
たまたま バージョン0.13 でうまく動作した stm32f401-i2c3 プロジェクトがあるので、そのプロジェクトの Cargo.lock ファイルを本プロジェクトのルートディレクトリにコピーしたところエラーが解消されました。

クレート間の相性があるようです。
プロジェクトを構成する側に問題があるように思いますが、ひとまずこれを回避策としておきます。
ご自身で書いたソースコードに関係のないエラーが出る場合には試してみてください。

今回のプロジェクトを stm32f401-spi3 におきましたので、Cargo.lockファイルをこちらからダウンロードしてお使い頂く方法でも良いと思います。

動作させてみる

それでは F5キーでプログラムを起動した後、entry で停止したらもう一度F5キーを押してプログラムを動作させてみます。

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

プログラムを動作させた後、停止させてステップ実行しながら press の値を確認してみます。
こちらでは press の値を 1021 で読むことができました。

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

今回のプロジェクトを stm32f401-spi3 におきましたので参考になさってください。

お疲れさまでした。

SPIカテゴリの最新記事