2015-10-05

Adding a Safe API for the Timer

In the last article, we added a safe API for the parallel I/O code. This time, we're going to do the same for the real-time timer.

You might remember that the timer code has already been cleaned up a while ago, by adding a low-level module that describes the hardware interface. We are going to build on that with a safe timer API.

Let's start with initialization:

pub struct Timer;

impl Timer {
	pub fn new() -> Timer {
		// Set the timer to a resolution of a millisecond
		unsafe { (*RTT).mode = 0x00000020; }
		Timer
	}

	// The rest of the API belongs here. See below.
}
			

Pretty simple, and not different from what we did before[1]. Only here, the unsafe block is stored away within a safe API.

Let's continue with the code that reads the timer value:

impl Timer {
	// Initialization code belongs here. See above.

	pub fn value(&self) -> u32 {
		unsafe { (*RTT).value }
	}

	// The rest of the API belongs here. See below.
}
			

This is a pretty simple method that just returns the current value of the timer.

And the last missing piece, our sleep function:

impl Timer {
	// The code we already saw belongs here. See above.

	pub fn sleep_ms(&self, milliseconds: u32) {
		let sleep_until = Wrapping(self.value()) + Wrapping(milliseconds);
		while self.value() < sleep_until.0 {}
	}
}
			

It looks similar to what we had before, with one big difference: Instead of regular addition, we're using wrapping addition.

Why is this important? Well, Rust as a language is very concerned with preventing accidental mistakes. This includes integer overflow. Unfortunately, checking each integer addition for overflow would be far too expensive in terms of performance, which is not acceptable for a systems programming language like Rust. These constraints have lead to the following compromise: Integer addition is checked in debug mode, and overflow will cause a panic. In release mode, those checks are optimized out[2].

So, how does that information help us? Well, it turns out it doesn't. The sleep method contains a rather glaringly obvious bug (which wasn't so glaringly obvious to me a few months ago, when I wrote it): When the addition overflows, self.value() will always be larger than sleep_until, meaning the method would return immediately instead of sleeping.

At this point, this is not that critical, however. The timer value won't wrap that often[3] and the method has other large problems[4]. We will have to replace it with something more advanced anyway, and in the meantime it's good enough to keep our LED blinking.

Before we wrap up, let's take a look at our current program:

use hardware::safe::pio;
use hardware::safe::rtt::Timer;


pub fn start() {
	// Pin 27 of the PIOB parallel I/O controller corresponds to pin 13 on the
	// Arduino Due, which is the built-in LED (labelled "L").
	let led = unsafe { pio::b().pin_27() };
	let led = led
		.enable()
		.enable_output();

	let timer = Timer::new();

	loop {
		led.set_output();
		timer.sleep_ms(200);
		led.clear_output();
		timer.sleep_ms(800);
	}
}
			

I think it has cleaned up nicely! Except for a single unsafe block which should be very easy to keep straight, it's pretty much idiot-proof. Quite the important attribute, considering that I'm the one who's working on it.

That's it for today. As always, the full code is available on GitHub. See you next time!