1バイト送信する

最初のタスクは、シリアル通信でマイクロコントローラからコンピュータに1バイト送ることです。

そのために、以下のコードを使いましょう。(07-uart/src/main.rs中にあるものです。)

#![no_main]
#![no_std]

use cortex_m_rt::entry;
use rtt_target::rtt_init_print;
use panic_rtt_target as _;

#[cfg(feature = "v1")]
use microbit::{
    hal::prelude::*,
    hal::uart,
    hal::uart::{Baudrate, Parity},
};

#[cfg(feature = "v2")]
use microbit::{
    hal::prelude::*,
    hal::uarte,
    hal::uarte::{Baudrate, Parity},
};

#[cfg(feature = "v2")]
mod serial_setup;
#[cfg(feature = "v2")]
use serial_setup::UartePort;

#[entry]
fn main() -> ! {
    rtt_init_print!();
    let board = microbit::Board::take().unwrap();

    #[cfg(feature = "v1")]
    let mut serial = {
        uart::Uart::new(
            board.UART0,
            board.uart.into(),
            Parity::EXCLUDED,
            Baudrate::BAUD115200,
        )
    };

    #[cfg(feature = "v2")]
    let mut serial = {
        let serial = uarte::Uarte::new(
            board.UARTE0,
            board.uart.into(),
            Parity::EXCLUDED,
            Baudrate::BAUD115200,
        );
        UartePort::new(serial)
    };

    nb::block!(serial.write(b'X')).unwrap();
    nb::block!(serial.flush()).unwrap();

    loop {}
}

まず目新しいものといえば、cfgディレクティブでしょう。これは条件によって特定のコードセクションをソースに含めたり除外したりするためのもので、ここではmicro:bit v1用にUART、micro:bit v2用にはUARTEを使用するよう指定しています。

また、ここで初めてライブラリ外のコードを取り込んでいることにもお気づきでしょう。serial_setupモジュールのことです。UARTEのラッパであるこのモジュールを使うことで、UARTEもUARTとまったく同じようにembedded_hal::serialトレイト経由で扱うことができます。この章の理解には必要ありませんが、もし興味があればモジュールの設計をのぞいてみてください。

これらの違いを除けば、UARTとUARTEの初期化手続きはよく似ています。ですからここではUARTEの初期化についてだけ解説します。UARTEは以下のコードで初期化します。

uarte::Uarte::new(
    board.UARTE0,
    board.uart.into(),
    Parity::EXCLUDED,
    Baudrate::BAUD115200,
);

この関数はRustで表現されたUARTEペリフェラル(board.UARTE0)、ならびにTX/RXピン(board.uart.into())の所有権を取得します。こうすることで、私たちの使っているUARTEとピンを他で使えないようにできます。次に、ふたつの設定オプションをコンストラクタに渡します。ボーレート(Baudrate)とパリティ(Parity)です。パリティはシリアル通信ラインに受信したデータが破損していないか確認することを可能にするオプションです。ここでは使いませんので、除外(EXCLUDED)しておきましょう。最後にUartePort型で包んでやります。こうすることで、micro:bit v1の serialと同じように扱うことが可能になります。

初期化できたら、今作ったばかりのUARTインスタンスでXを送ります。ここに出てくるblock!マクロは、nb::block!マクロです。nbは、「最小限かつ再利用可能なノンブロッキングI/O層」(公式ドキュメントからの引用)です。これによって、バックグラウンドでハードウェア操作を行うあいだに別タスクを処理(ノンブロッキング)することができます。ですが、このケースでは平行して別の処理はしないので、ここではただblock! を呼び、I/O処理の成功・失敗を待ってからプログラムの実行を続けることにします。

最後に、シリアルポートをflush()します。なぜかというと、embedded-hal::serialトレイトの実装によっては、送信するデータが一定のバイト数になるまで、送信バッファに貯めておく実装になっていることがあるからです。(実際にUARTEはそのように実装されています。)flush()を呼ぶことで、送信データをそれ以上待たずに、送信バッファの内容を強制的に出力させることができるのです。

テストしましょう

プログラムを書き込む前に、忘れずにminicom/PuTTYをスタートしてください。シリアル通信で受信するデータは、どこかに保存されるわけではなく、リアルタイムに観察する必要があるからです。シリアルモニタが立ち上がったら、5章でしたようにプログラムを書き込みましょう。

# For micro:bit v2
$ cargo embed --features v2 --target thumbv7em-none-eabihf
  (...)

# For micro:bit v1
$ cargo embed --features v1 --target thumbv6m-none-eabi

書き込みが終わると、Xの文字がminicom/PuTTYのターミナルに出るはずです。おめでとうございます!