RP2040(Raspberry Pi Pico) - RustでUART出力
Written on 2024-06-02
Raspberry Pi Pico(RasPico)上で、Rustを使ってUART出力をする方法。
bootloaderのサブ記事だが、独立させておく。
RasPico上でRustを使ってUART出力をする
RasPicoにはRTTという文字出力のほうもうもある。 しかし、UARTは汎用的でなにかと便利だ。使えたほうがありがたいことも多い。
- HALの
uart
をuse
する into_function()
を使ってGPIOピンをUART用に割り当てて、uart_pins
というタプルに束縛しておくUartPeripheral
を初期化する(new()
->enable()
->unwrap()
)uart.write_full_blocking()
で文字出力ができる。Rustの通常の文字列はUTF-8だが、この引数はバイト列なのでb"..."
を使う
/// uart のHALをuseする。fugitは周波数や時刻の演算用ライブラリ
use rp2040_hal::{
clocks::{init_clocks_and_plls, Clock},
fugit::RateExtU32, // time calculation library
gpio::Pins,
pac,
sio::Sio,
uart::{DataBits, StopBits, UartConfig, UartPeripheral}, /// HALからUARTをインポートする。この行を追加
watchdog::Watchdog,
};
/// UARTの初期化
let uart_pins = (pins.gpio0.into_function(), pins.gpio1.into_function()); // RP2040(BSPではなくHAL)のGP0とGP1をUARTに使う
let uart = UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) // クロックを初期化しつつUARTも初期化する
.enable(
UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One),
clocks.peripheral_clock.freq(),
)
.unwrap();
/// メッセージの出力は `write_full_blocking()`で。
/// Rustの通常の文字列はUTF-8だが、ここの引数はUTF-8ではなくバイト列で渡す。
uart.write_full_blocking(b"bootloader stated...\r\n");
/// ビルド設定を出力するようにしておくと便利 #[cfg(debug_assertions)]
uart.write_full_blocking(b"bootloader debug build\r\n");
#[cfg(not(debug_assertions))]
uart.write_full_blocking(b"bootloader release build\r\n");
UARTの出力は cu
など、好みのターミナルソフトで表示できる。cu
の場合、終了は~.
。
❯ sudo cu -l /dev/tty.usbmodem13202 -s 115200
Connected.
bootloader stated...
bootloader debug build
bootloader on!
bootloader off!
bootloader on!
bootloader off!
bootloader on!
~.
Disconnected.
uartを引数で渡す
関数を呼び出した場合、その呼んだ先でUART出力を行いたい。そのようなときは、上記のように作成したuart
オブジェクトも引数として関数に渡さなければならない。初期化時は型が自動で推定されるが、引数として渡すときは関数の型を、自力で、適切に定義する必要がある。
一瞬、グローバル変数にしてしまえ、と思うが、Rustの場合はミュータブルな静的変数はunsafeになってしまう。ここはおとなしく引数で渡すほうがいいだろう。
このような型付け、境界条件付けは初心者には難しい。コンパイラのエラーを丁寧に読み解いていく必要がある。rust-analyzerが自動でやってくれるとよいのだが。
呼ぶ側
let mut uart = UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS)
.enable(
UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One),
clocks.peripheral_clock.freq(),
)
.unwrap();
ih_print(&ih, &mut uart);
関数定義
- 引数の型は、
&mut UartPeripheral
。UartPeripheral
は<S, D, P>
の型パラメータを取る(generic)。 - 引数がジェネリックなので、関数もジェネリックになり、型パラメータと、実際の型を指定する。
writeln!()
を使うために、UartPeripheral
にWrite
トレイト境界を付ける。そうすると、Write
トレイトがついているオブジェクトを、writeln!()
の第一引数で渡すことで、fmt!()
で文字列をフォーマットして出力することができる。
fn ih_print<
S: rp2040_hal::uart::State,
D: rp2040_hal::uart::UartDevice,
P: rp2040_hal::uart::ValidUartPinout<D>,
>(
ih: &ImageHeader,
uart: &mut UartPeripheral<S, D, P>
)
where UartPeripheral<S, D, P>: Write{
writeln!(uart, "header_magic: {:04x}\r", ih.header_magic).unwrap();
}
ジェネリック引数の型付けやトレイト境界はヤヤコシイが、それさえ処理すれば、すぐにfmt!()
が使えるのは、とても便利。オブジェクトにDebug
トレイトがついていれば{:?}
などのデバッグ出力も使える。