オーバーラン
プログラムを次のように書いた場合:
#![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)