Overruns

If you wrote your program like this:

  1. #![deny(unsafe_code)]
  2. #![no_main]
  3. #![no_std]
  4. #[allow(unused_imports)]
  5. use aux11::{entry, iprint, iprintln};
  6. #[entry]
  7. fn main() -> ! {
  8. let (usart1, mono_timer, itm) = aux11::init();
  9. // Send a string
  10. for byte in b"The quick brown fox jumps over the lazy dog.".iter() {
  11. usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte)));
  12. }
  13. loop {}
  14. }

You probably received something like this on your laptop when you executed the program compiled indebug mode.

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

And if you compiled in release mode, you probably only got something like this:

  1. $ # minicom's terminal
  2. (..)
  3. T

What went wrong?

You see, sending bytes over the wire takes a relatively large amount of time. I already did the mathso let me quote myself:

With a common configuration of 1 start bit, 8 bits of data, 1 stop bit and a baud rate of 115200bps one can, in theory, send 11,520 frames per second. Since each one frame carries a byte of datathat results in a data rate of 11.52 KB/s

Our pangram has a length of 45 bytes. That means it’s going to take, at least, 3,900 microseconds(45 bytes / (11,520 bytes/s) = 3,906 us) to send the string. The processor is working at 8 MHz,where executing an instruction takes 125 nanoseconds, so it’s likely going to be done with the forloop is less than 3,900 microseconds.

We can actually time how long it takes to execute the for loop. aux11::init() returns aMonoTimer (monotonic timer) value that exposes an Instant API that’s similar to the one instd::time.

  1. #![deny(unsafe_code)]
  2. #![no_main]
  3. #![no_std]
  4. #[allow(unused_imports)]
  5. use aux11::{entry, iprint, iprintln};
  6. #[entry]
  7. fn main() -> ! {
  8. let (usart1, mono_timer, mut itm) = aux11::init();
  9. let instant = mono_timer.now();
  10. // Send a string
  11. for byte in b"The quick brown fox jumps over the lazy dog.".iter() {
  12. usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte)));
  13. }
  14. let elapsed = instant.elapsed(); // in ticks
  15. iprintln!(
  16. &mut itm.stim[0],
  17. "`for` loop took {} ticks ({} us)",
  18. elapsed,
  19. elapsed as f32 / mono_timer.frequency().0 as f32 * 1e6
  20. );
  21. loop {}
  22. }

In debug mode, I get:

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

This is less than 3,900 microseconds but it’s not that far off and that’s why only a few bytes ofinformation are lost.

In conclusion, the processor is trying to send bytes at a faster rate than what the hardware canactually handle and this results in data loss. This condition is known as buffer overrun.

How do we avoid this? The status register (ISR) has a flag, TXE, that indicates if it’s “safe”to write to the TDR register without incurring in data loss.

Let’s use that to slowdown the processor.

  1. #![deny(unsafe_code)]
  2. #![no_main]
  3. #![no_std]
  4. #[allow(unused_imports)]
  5. use aux11::{entry, iprint, iprintln};
  6. #[entry]
  7. fn main() -> ! {
  8. let (usart1, mono_timer, mut itm) = aux11::init();
  9. let instant = mono_timer.now();
  10. // Send a string
  11. for byte in b"The quick brown fox jumps over the lazy dog.".iter() {
  12. // wait until it's safe to write to TDR
  13. while usart1.isr.read().txe().bit_is_clear() {} // <- NEW!
  14. usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte)));
  15. }
  16. let elapsed = instant.elapsed(); // in ticks
  17. iprintln!(
  18. &mut itm.stim[0],
  19. "`for` loop took {} ticks ({} us)",
  20. elapsed,
  21. elapsed as f32 / mono_timer.frequency().0 as f32 * 1e6
  22. );
  23. loop {}
  24. }

This time, running the program in debug or release mode should result in a complete string on thereceiving side.

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

The timing of the for loop should be closer to the theoretical 3,900 microseconds as well. Thetiming below is for the debug version.

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