今回はhalを使ってUARTを抽象化してみます。
前回ベアメタル系の記事を書いたので、その halバージョンです。
概要については 前回の記事 をご覧になってください。
halにSerial構造体があります。
前回パリティエラーなどのエラー検出処理を書きましたが、2022年3月時点でSerial構造体には基本的なメソッドしかありませんでした。
こちらでつくるプロジェクトから見ると Serial構造体は外部クレートの一部です。
そのクレートに対してトレイトを使ってエラー処理を追加してみます。
またエラー処理のコード量はわずかですが、その部分を別ファイルに置いて部品化してみます。
開発環境は以下の通りです。
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-uart2 と入力します。
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-uart2
C:\Users\xxxxx\stm32f401-uart2 というフォルダができていることを確認します。
xxxxx は皆さまのユーザー名です。
VSCodeを起動する
以下のようにコマンドを入力することで、stm32f401-uart2をカレントディレクトリとしてエディタ(VSCode)が起動します。
cd stm32f401-uart2
code .
C:\Users\xxxxx>cd stm32f401-uart2
C:\Users\xxxxx\stm32f401-uart2>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-uart2\.vscode に保存します。
ライブラリクレートを追加する
ファイルを分割するのに必要な作業になります。
ファイル分割の方法は、いろいろあると思うのですが今回はライブラリクレートを使ってみます。
まず、カレントディレクトリを stm32f401-uart2 にします。
例.
C:\Users\xxxxx>cd stm32f401-uart2
C:\Users\xxxxx\stm32f401-uart2>
cargo new –lib stm32lib と入力してライブラリクレートをつくります。
C:\Users\xxxxx\stm32f401-uart2>cargo new --lib stm32lib
Created libral `stm32lib` package
stm32libディレクトリがつくられ、その下に src/lib.rs というファイルがつくられます。
lib.rs を以下のように編集して保存します。
#![no_std]
pub mod uart;
このファイルは、単に橋渡しをするためだけのものです。
Cargo.toml
ライブラリクレートをつくったので、Cargo.toml は2つ存在します。
まず上の階層の Cargo.toml にstm32libのパスを指定し、その存在を教えます。
[dependencies]
stm32lib = { path = "stm32lib" }
次に stm32libにある Cargo.toml には以下のように hal を追加します。
uartを使うために必要です。
[dependencies.stm32f4xx-hal]
features = ["stm32f401", "rt"]
version = "0.9"
モジュール 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() }
}
}
ソースコード main.rs
#![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::{Serial, config};
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 pa2 = gpioa.pa2.into_alternate_af7(); // afrl, modeレジスタを設定してくれる
let pa3 = gpioa.pa3.into_alternate_af7(); // afrl, modeレジスタを設定してくれる
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(); // (5)RCCの取得
let clks = rcc.cfgr.freeze(); // (6)各clockの設定
let mut serial = Serial::usart2(
dp.USART2,
(pa2, pa3),
seri_config,
clks,
).unwrap(); // (5)Serial構造体の生成
loop {
while !serial.is_rxne() {}
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_txe() {}
serial.write(c).unwrap();
}
}
}
コード概要
各所にコメントを入れたので参考にしてください。
(1)の宣言で write!マクロが使えるようになります。
引数内に”{}”のプレースホルダを使うことができます。
(5),(6)でclock系の初期化が行われています。
ステップ実行でメソッドの中に入っていくことができますので、お勉強できそうですね。
clockは HSI , 16MHz に設定されているようです。
ベアメタルに書いた場合に比べると、見さすさでは hal に軍配が上がるのではないでしょうか?
通信速度の設定など、見通しが良いですね。
PC側の設定
PCのソフトには Tera Term を使いますのでダウンロードしてインストールしておきます。
新しい接続で、シリアルを選択し、Virtual COM Port のあるポートを選択します。
次に、設定-シリアルポートから通信パラメーターを以下のとおりに設定します。
続いて、設定-端末でローカルエコーにチェックを入れます。
これで送信した文字が表示されます。
USBケーブルでPCとボートを接続しておきます。
動作させてみる
それでは F5キーでプログラムを起動した後、entry で停止したらもう一度F5キーを押してプログラムを動作させてみます。
デバッグモードは OpenOCD に設定する必要があるので、エラーが出る場合には確認してみてください。
PCのTera Termで何かキーを押し、ボートから戻ってくれば成功です。
正常に戻ってくれば、2文字ずつTera Termに表示されます。
パリティエラーを起こしてみる
通信パラメータの設定で、パリティを ODD (奇数)に設定し、文字を送ってみます。
以下のようにエラーが検出されます。
いかがでしたか?
皆さんの環境では、うまく動作しましたか?
今回のプロジェクトを stm32f401-uart2 におきましたので、よろしければ参考になさってください。
お疲れさまでした。