「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 におきましたので、よろしければ参考になさってください。
お疲れさまでした。