オーバーラン

プログラムを次のように書いた場合、

#![deny(unsafe_code)]
#![no_main]
#![no_std]

#[allow(unused_imports)]
use aux11::{entry, iprint, iprintln};

#[entry]
fn main() -> ! {
    let (usart1, mono_timer, itm) = aux11::init();

    // 文字列を送信します
    for byte in b"The quick brown fox jumps over the lazy dog.".iter() {
        usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte)));
    }

    loop {}
}

デバッグモードでコンパイルしたプログラムを実行した場合、ノートPCでは、おそらく次のようなものを受信したと思います。

$ # minicom's terminal
(..)
The uic brwn oxjums oer helaz do.

そして、リリースモードでコンパイルした場合、次のような表示になったと思います。

$ # minicom's terminal
(..)
T

何がいけなかったのでしょう?

おわかりのように、ワイヤを介してバイトを送信するには、比較的長い時間がかかります。計算しておいた値を引用します。

一般的な、スタートビット1ビット、データ8ビット、ストップビット1ビットでボーレートが115200 bpsの設定では、 理論上は、毎秒11,520フレームを送信できます。1フレームにつき1バイトのデータを転送するので、データレートは、11.52 KB/秒になります。

送信した文字列は、45バイトの長さです。これは、この文字列を送るために、 少なくとも3,900マイクロ秒(45 bytes / (11,520 bytes/s) = 3,906 us)かかることを意味しています。 プロセッサは、8MHzで動作しており、1命令125ナノ秒で実行します。このforループは、3,900マイクロ秒より短い時間で完了するようです。

このforループの実行に、どのくらい時間がかかっているのか、を実際に計測します。 aux11::init()は、MonoTimer (monotonic timer) の値を返します。これは、std::timeに似たInstant APIを提供します。

#![deny(unsafe_code)]
#![no_main]
#![no_std]

#[allow(unused_imports)]
use aux11::{entry, iprint, iprintln};

#[entry]
fn main() -> ! {
    let (usart1, mono_timer, mut itm) = aux11::init();

    let instant = mono_timer.now();
    // 文字列を送信します
    for byte in b"The quick brown fox jumps over the lazy dog.".iter() {
        usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte)));
    }
    let elapsed = instant.elapsed(); // ティック単位で

    iprintln!(
        &mut itm.stim[0],
        "`for` loop took {} ticks ({} us)",
        elapsed,
        elapsed as f32 / mono_timer.frequency().0 as f32 * 1e6
    );

    loop {}
}

デバッグモードでは、下記の結果が得られました。

$ # itmdump terminal
(..)
`for` loop took 22415 ticks (2801.875 us)

これは、3,900マイクロ秒より短いですが、それほどかけ離れているわけではありません。そのため、情報の数バイトだけが失われました。

結論として、プロセッサは、ハードウェアの実際の処理能力より速いレートでバイトを送信し、その結果、データが失われました。 この状態は、バッファオーバーランと呼ばれています。

どうすれば、これを回避できるでようか?ステータスレジスタ(ISR)は、TXEというフラグを持っています。 このフラグは、TDRレジスタにデータ損失なしで「安全」に書き込むことができるかどうかを、示しています。

プロセッサを減速させるために、これを使いましょう。

#![deny(unsafe_code)]
#![no_main]
#![no_std]

#[allow(unused_imports)]
use aux11::{entry, iprint, iprintln};

#[entry]
fn main() -> ! {
    let (usart1, mono_timer, mut itm) = aux11::init();

    let instant = mono_timer.now();
    // 文字列を送信します
    for byte in b"The quick brown fox jumps over the lazy dog.".iter() {
        // TDRへの書き込みが安全になるまで待ちます
        while usart1.isr.read().txe().bit_is_clear() {} // <- NEW!

        usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte)));
    }
    let elapsed = instant.elapsed(); // ティック単位で

    iprintln!(
        &mut itm.stim[0],
        "`for` loop took {} ticks ({} us)",
        elapsed,
        elapsed as f32 / mono_timer.frequency().0 as f32 * 1e6
    );

    loop {}
}

今回は、デバッグモードかリリースモードかに関わらず、プログラムを実行すると、受信側で完全に文字列を受信できるはずです。

$ # minicom/PuTTY's console
(..)
The quick brown fox jumps over the lazy dog.

forループの実行時間は、理論上の3,900マイクロ秒に近くなるはずです。 デバッグモードのバージョンを下記に示します。

$ # itmdump terminal
(..)
`for` loop took 30499 ticks (3812.375 us)