Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

オーバーラン

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

#![no_main]
#![no_std]

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

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

    // Send a string
    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 {}
}

おそらく、デバッグモードでコンパイルしたプログラムを実行すると、コンピューター上では次のようなものが表示されたはずです。

$ # minicom の端末
(..)
The uic brwn oxjums oer helaz do.

そして、リリースモードでコンパイルした場合は、おそらく次のようなものしか得られなかったはずです:

$ # minicom の端末
(..)
T

何が問題だったのでしょうか?

ご存じのとおり、バイトを配線越しに送信するには比較的長い時間がかかります。計算はすでに済ませてあるので、 ここでは以前の説明を引用します:

1 スタートビット、8 データビット、1 ストップビット、ボーレート 115200 bps という一般的な構成では、理論上は 1 秒あたり 11,520 フレームを送信できます。各フレームは 1 バイトのデータを運ぶので、 その結果、データレートは 11.52 KB/s になります

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

実際に、for ループの実行にどれくらい時間がかかるかを計測できます。aux11::init()MonoTimer(単調タイマー)値を返し、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();
    // Send a string
    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(); // in ticks

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

    loop {}
}

デバッグモードでは、私の環境では次のようになります:

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

これは 3,900 マイクロ秒未満ですが、それほど大きく離れているわけではありません。そのため、失われる情報は 数バイトだけで済みます。

要するに、プロセッサはハードウェアが実際に処理できる速度よりも速いレートでバイトを送信しようとしており、 その結果データ損失が発生しています。この状態はバッファ オーバーラン と呼ばれます。

これをどう回避すればよいでしょうか? ステータスレジスタ(ISR)には TXE というフラグがあり、データ損失を起こさずに TDR レジスタへ書き込んでも「安全」かどうかを示します。

これを使ってプロセッサを減速させましょう。

#![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();
    // Send a string
    for byte in b"The quick brown fox jumps over the lazy dog.".iter() {
        // wait until it's safe to write to TDR
        while usart1.isr.read().txe().bit_is_clear() {} // <- NEW!

        usart1
            .tdr
            .write(|w| w.tdr().bits(u16::from(*byte)));
    }
    let elapsed = instant.elapsed(); // in ticks

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

    loop {}
}

今回は、デバッグモードでもリリースモードでも、プログラムを実行すると受信側では完全な文字列が 得られるはずです。

$ # minicom/PuTTY のコンソール
(..)
The quick brown fox jumps over the lazy dog.

また、for ループのタイミングも理論値である 3,900 マイクロ秒に近くなるはずです。以下の タイミングはデバッグ版のものです。

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