STM32 Rust halでUART (halのバージョンを0.9から0.13に)

STM32 Rust halでUART (halのバージョンを0.9から0.13に)

halを使ってUARTを抽象化してみる」の記事で使ったhalのバージョン0.9を0.13に上げてみました。
内容は同じですから、必要な情報のみ記述しておきます。
少し日が経っていますので、まず上の記事について復習してみてください。

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

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

Rustのバージョンアップ

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

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

rustup update

rustup update

Cargo.toml

このプロジェクトでは Cargo.toml が2つあります。
上層階の Cargo.toml を以下のように編集します。

ドキュメントに習って、以下のように記述します。

[dependencies]
embedded-hal = "0.2"
nb = "1"
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
stm32lib = { path = "stm32lib" }

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

stm32libにあるCargo.toml

stm32lib下にあるCargo.tomlを以下のように編集します。

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

モジュール uart.rs

次に lib.rsと同じ階層(ディレクトリ)に uart.rs というファイルをつくり以下のように編集して保存します。
lib.rsに記述した “pub mod uart;” はこのファイルを指しています。

Rustにはクラスがないので、トレイトを使って継承を実現します。
トレイトで追加したメソッドをSerial構造体のメソッドであるかのように使うことができます。
トレイトを使ってエラー処理を記述します。
traitの前に pub キーワードをつけて、外からも使えるようにしておきます。

ここでは定義だけを記述して、部品として使えるようにしておけばスッキリしますね。

use stm32f4xx_hal::serial::Serial;
use stm32f4xx_hal::serial::Instance;
use stm32f4xx_hal::serial::Pins;

pub trait ErrorDetect {
    fn is_pe(&self) -> bool;    // パリティエラー
    fn is_fe(&self) -> bool;    // フレーミングエラー
    fn is_ore(&self) -> bool;   // オーバーランエラー
}

impl<USART, PINS> ErrorDetect for Serial<USART, PINS>
where
    PINS: Pins<USART>,
    USART: Instance,
{
    fn is_pe(&self) -> bool {
        unsafe { (*USART::ptr()).sr.read().pe().bit_is_set() }
    }
    fn is_fe(&self) -> bool {
        unsafe { (*USART::ptr()).sr.read().fe().bit_is_set() }
    }
    fn is_ore(&self) -> bool {
        unsafe { (*USART::ptr()).sr.read().ore().bit_is_set() }
    }
}

ソースコード

#![no_std]
#![no_main]

use embedded_hal::prelude::_embedded_hal_serial_Write;
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
use cortex_m_rt::entry;
use stm32f4xx_hal::serial::*;
use stm32f4xx_hal::gpio::{GpioExt};
use stm32f4xx_hal::rcc::RccExt;
use stm32f4xx_hal::time::Bps;

use embedded_hal::serial::Read;
use core::fmt::Write;   // (1)write!()マクロを使えるようにする
use stm32lib::uart::ErrorDetect;    // (2)追加するトレイトを使えるようにする

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

    let dp = stm32f4xx_hal::pac::Peripherals::take().unwrap();
    let gpioa = dp.GPIOA.split();   // GPIOAのclockも有効にしてくれる (AHBENRレジスタ)
    let bps = Bps(115_200_u32); // (3)通信速度
    let seri_config = config::Config {  // (4)通信パラメーターの設定
        baudrate: bps,
        wordlength: config::WordLength::DataBits8,  // 実際には7ビット
        parity: config::Parity::ParityEven,
        stopbits: config::StopBits::STOP1,
        dma: config::DmaConfig::None,
    };

    let rcc = dp.RCC.constrain();
    let clks = rcc.cfgr.freeze(); // 初期値でクロックを生成するコード

    let mut serial = Serial::new(
        dp.USART2,
        (gpioa.pa2, gpioa.pa3),
        seri_config,
        &clks,
    ).unwrap(); // (5)Serial構造体の生成

    loop {
        while !serial.is_rx_not_empty() {}
        if serial.is_pe() {
            let _ = serial.read();  // 読み捨てる
            write!(serial, "\r\nParity error {}", "detected.\r\n").unwrap();
        }
        else if serial.is_fe() {
            let _ = serial.read();  // 読み捨てる
            write!(serial, "\r\nFraming error {}", "detected.\r\n").unwrap();
        }
        else if serial.is_ore() {
            let _ = serial.read();  // 読み捨てる
            write!(serial, "\r\nOver run error {}", "detected.\r\n").unwrap();
        }
        else if let Ok(c) = serial.read() {
            while !serial.is_tx_empty() {}
            serial.write(c).unwrap();
        }
    }
}

コード概要

各所にコメントを入れたので参考にしてください。
(1)の宣言で write!マクロが使えるようになります。
引数内に”{}”のプレースホルダを使うことができます。
(5),(6)でclock系の初期化が行われています。
ステップ実行でメソッドの中に入っていくことができますので、お勉強できそうですね。

clockは HSI , 16MHz に設定されているようです。

version0.9との大きな違い:

Serial::new()の第2引数は split()した値を加工することなく渡すことができるようになっています。
halのバージョンが上がるにつれて、GPIOのモードをオルタネート afx (xは数字) に設定するメソッド自体がなくなりました。
その代わりSerialを生成するnew()メソッドではモード設定をしないままの型を受け付けてくれるようになったようで、その分コードを書く手間が省けました。

コードはたいした量ではないので、0.9のものと見比べてみると良いかも知れません。

ビルドエラー対策

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

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

(Cargo.lockはテキストファイルになっていて、プロジェクト名ごとコピーされます。エラーは出ませんが気になる方はプロジェクト名を stm32f401-i2c3 から stm32f401-uart3 に変更しておくと良いでしょう)

PC側の設定

PCのソフトには Tera Term を使いますのでダウンロードしてインストールしておきます。

新しい接続で、シリアルを選択し、Virtual COM Port のあるポートを選択します。

次に、設定-シリアルポートから通信パラメーターを以下のとおりに設定します。

続いて、設定-端末でローカルエコーにチェックを入れます。
これで送信した文字が表示されます。

USBケーブルでPCとボートを接続しておきます。

動作させてみる

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

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

PCのTera Termで何かキーを押し、ボートから戻ってくれば成功です。
正常に戻ってくれば、2文字ずつTera Termに表示されます。

パリティエラーを起こしてみる

通信パラメータの設定で、パリティを ODD (奇数)に設定し、文字を送ってみます。
以下のようにエラーが検出されます。

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

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

お疲れさまでした。

UARTカテゴリの最新記事