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