Timers
In this section, we will walk through the button_abort
example in Rust. It uses the first button
and the first LED of the board. On a short press, the LED will start blinking. On a long press, the
LED will stop blinking. While the button is pressed, the LED indicates whether the press is short or
long:
- The LED is on while the press is short.
- The LED turns off once the press is long.
This applet will need a shared state to know whether the LED must be blinking or not. We cannot
simply use a boolean because the state will be shared. We cannot use Cell<bool>
neither because
the state must be in the heap1. So we use Rc<Cell<bool>>
which is a common pattern when
using callbacks:
// We define a shared state to decide whether we must blink.
let blinking = Rc::new(Cell::new(false));
We can now allocate a timer for the blinking behavior using timer::Timer::new
. This function takes
the handler that will be called each time the timer fires. The handler simply toggles the LED if we
must be blinking. Note how we must move a clone of the state to the callback. This is also a common
pattern when using callbacks:
// Allocate a timer for blinking the LED.
let blink = timer::Timer::new({
// Move a clone of the state to the callback.
let blinking = blinking.clone();
move || {
// Toggle the LED if blinking.
if blinking.get() {
led::set(0, !led::get(0));
}
}
});
The rest of the code is done in an infinite loop:
loop {
At each iteration, we start by setting up a button::Listener
to record whether the button was
pressed and then released. The logic is similar to the callback setup for the timer. The small
difference is that we won't need to call any function on the listener so we prefix its variable name
_button
with an underscore. We cannot simply omit the variable name because we don't want to drop
it until the end of the loop iteration, otherwise we would stop listening to button events.
// Setup button listeners.
let pressed = Rc::new(Cell::new(false));
let released = Rc::new(Cell::new(false));
let _button = button::Listener::new(0, {
let pressed = pressed.clone();
let released = released.clone();
move |state| match state {
button::Pressed => pressed.set(true),
button::Released if pressed.get() => released.set(true),
button::Released => (),
}
})?;
We then wait until the button is pressed using scheduling::wait_until()
. This function takes a
condition as argument and only executes callbacks until the condition is satisfied.
// Wait for the button to be pressed.
scheduling::wait_until(|| pressed.get());
According to the specification of this example applet, when the button is pressed we must turn on
the LED (and stop blinking if we were blinking) to signal a possible short press. Note that
callbacks can only executed when a scheduling function is called, so we are essentially in a
critical section. As such, the order in which we do those 3 lines doesn't matter. However, a
callback might be scheduled before we stop the blink
timer. It will execute next time we call a
scheduling function. This is ok because when that will happen, the blinking
state will be false
and the blink
handler will do nothing.
// Turn the LED on.
blink.stop();
blinking.set(false);
led::set(0, led::On);
To detect a long press, we need to start a timer. There is nothing new here except the
Timer::start()
function which takes the timer mode (one-shot or periodic) and its duration.
// Start the timeout to decide between short and long press.
let timed_out = Rc::new(Cell::new(false));
let timer = timer::Timer::new({
let timed_out = timed_out.clone();
move || timed_out.set(true)
});
timer.start(timer::Oneshot, Duration::from_secs(1));
We now wait for the first event between the button being released and the timeout firing. This is simply done by using a condition which is a disjunction of the events of interest. This is a common pattern when implementing behavior with a timeout.
// Wait for the button to be released or the timeout to fire.
scheduling::wait_until(|| released.get() || timed_out.get());
To signal that the timeout was reached or the button was released, we turn off the LED.
// Turn the LED off.
led::set(0, led::Off);
Finally, if the press was short (i.e. the button was released before the timeout), we start blinking. This demonstrates the use of periodic timers.
// Start blinking if short press.
if !timed_out.get() {
blinking.set(true);
blink.start(timer::Periodic, Duration::from_millis(200));
}
There are a few things to note:
- The code is implicit in Rust, but the button handler and the timer handler within the loop
iteration are dropped before the next iteration. This means that their callbacks are unregistered.
This could be done explicitly by calling
core::mem::drop()
on their variable if needed. - It is not needed to start and stop the
blink
timer within the loop as long as it is started before entering the loop. This is just an optimization to avoid calling the handler when we know that theblinking
shared state isfalse
, because the handler would do nothing in that case.
The final code looks like this:
#![no_std] wasefire::applet!(); use alloc::rc::Rc; use core::cell::Cell; use core::time::Duration; fn main() -> Result<(), Error> { assert!(button::count() > 0, "Board has no buttons."); assert!(led::count() > 0, "Board has no LEDs."); // We define a shared state to decide whether we must blink. let blinking = Rc::new(Cell::new(false)); // Allocate a timer for blinking the LED. let blink = timer::Timer::new({ // Move a clone of the state to the callback. let blinking = blinking.clone(); move || { // Toggle the LED if blinking. if blinking.get() { led::set(0, !led::get(0)); } } }); loop { // Setup button listeners. let pressed = Rc::new(Cell::new(false)); let released = Rc::new(Cell::new(false)); let _button = button::Listener::new(0, { let pressed = pressed.clone(); let released = released.clone(); move |state| match state { button::Pressed => pressed.set(true), button::Released if pressed.get() => released.set(true), button::Released => (), } })?; // Wait for the button to be pressed. scheduling::wait_until(|| pressed.get()); // Turn the LED on. blink.stop(); blinking.set(false); led::set(0, led::On); // Start the timeout to decide between short and long press. let timed_out = Rc::new(Cell::new(false)); let timer = timer::Timer::new({ let timed_out = timed_out.clone(); move || timed_out.set(true) }); timer.start(timer::Oneshot, Duration::from_secs(1)); // Wait for the button to be released or the timeout to fire. scheduling::wait_until(|| released.get() || timed_out.get()); // Turn the LED off. led::set(0, led::Off); // Start blinking if short press. if !timed_out.get() { blinking.set(true); blink.start(timer::Periodic, Duration::from_millis(200)); } } }
Testing
This is best tested with the Web UI.
If the state were on the stack and a callback were pointing to that state, it would become
a safety requirement to unregister the callback before popping the state from the stack.
However, it is safe to leak a callback with core::mem::forget()
and thus not drop it. So we
enforce callbacks to be 'static
and thus not depend on references to the stack.