Tasks with delay

A convenient way to express miniminal timing requirements is by delaying progression.

This can be achieved by instantiating a monotonic timer (for implementations, see rtic-monotonics):

...
    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        hprintln!("init");

        let systick_token = rtic_monotonics::create_systick_token!();
        Systick::start(cx.core.SYST, 12_000_000, systick_token);
        ...

A software task can await the delay to expire:

#[task]
async fn foo(_cx: foo::Context) {
    ...
    Systick::delay(100.millis()).await;
    ...
}
A complete example
//! examples/async-delay.rs

#![no_main]
#![no_std]
#![deny(warnings)]
#![deny(unsafe_code)]
#![deny(missing_docs)]
#![feature(type_alias_impl_trait)]

use panic_semihosting as _;

#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)]
mod app {
    use cortex_m_semihosting::{debug, hprintln};
    use rtic_monotonics::systick::*;

    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        hprintln!("init");

        let systick_token = rtic_monotonics::create_systick_token!();
        Systick::start(cx.core.SYST, 12_000_000, systick_token);

        foo::spawn().ok();
        bar::spawn().ok();
        baz::spawn().ok();

        (Shared {}, Local {})
    }

    #[task]
    async fn foo(_cx: foo::Context) {
        hprintln!("hello from foo");
        Systick::delay(100.millis()).await;
        hprintln!("bye from foo");
    }

    #[task]
    async fn bar(_cx: bar::Context) {
        hprintln!("hello from bar");
        Systick::delay(200.millis()).await;
        hprintln!("bye from bar");
    }

    #[task]
    async fn baz(_cx: baz::Context) {
        hprintln!("hello from baz");
        Systick::delay(300.millis()).await;
        hprintln!("bye from baz");

        debug::exit(debug::EXIT_SUCCESS);
    }
}
$ cargo run --target thumbv7m-none-eabi --example async-delay --features test-critical-section 
init
hello from bar
hello from baz
hello from foo
bye from foo
bye from bar
bye from baz

Interested in contributing new implementations of Monotonic, or more information about the inner workings of monotonics? Check out the Implementing a Monotonic chapter!

Timeout

Rust Futures (underlying Rust async/await) are composable. This makes it possible to select in between Futures that have completed.

A common use case is transactions with an associated timeout. In the examples shown below, we introduce a fake HAL device that performs some transaction. We have modelled the time it takes based on the input parameter (n) as 350ms + n * 100ms.

Using the select_biased macro from the futures crate it may look like this:

        // Call hal with short relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v),
            _ = Systick::delay(200.millis()).fuse() =>  hprintln!("timeout", ), // this will finish first
        }

        // Call hal with long relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v), // hal finish first
            _ = Systick::delay(1000.millis()).fuse() =>  hprintln!("timeout", ),
        }

Assuming the hal_get will take 450ms to finish, a short timeout of 200ms will expire before hal_get can complete.

Extending the timeout to 1000ms would cause hal_get will to complete first.

Using select_biased any number of futures can be combined, so its very powerful. However, as the timeout pattern is frequently used, more ergonomic support is baked into RTIC, provided by the rtic-monotonics and rtic-time crates.

Rewriting the second example from above using timeout_after gives:

        // get the current time instance
        let mut instant = Systick::now();

        // do this 3 times
        for n in 0..3 {
            // absolute point in time without drift
            instant += 1000.millis();
            Systick::delay_until(instant).await;

            // absolute point in time for timeout
            let timeout = instant + 500.millis();
            hprintln!("now is {:?}, timeout at {:?}", Systick::now(), timeout);

            match Systick::timeout_at(timeout, hal_get(n)).await {
                Ok(v) => hprintln!("hal returned {} at time {:?}", v, Systick::now()),
                _ => hprintln!("timeout"),
            }
        }

In cases where you want exact control over time without drift we can use exact points in time using Instant, and spans of time using Duration. Operations on the Instant and Duration types come from the [fugit] crate.

let mut instant = Systick::now() sets the starting time of execution.

We want to call hal_get after 1000ms relative to this starting time. This can be accomplished by using Systick::delay_until(instant).await.

Then, we define a point in time called timeout, and call Systick::timeout_at(timeout, hal_get(n)).await.

For the first iteration of the loop, with n == 0, the hal_get will take 350ms (and finishes before the timeout).

For the second iteration, with n == 1, the hal_get will take 450ms (and again succeeds to finish before the timeout).

For the third iteration, with n == 2, hal_get will take 550ms to finish, in which case we will run into a timeout.

A complete example
//! examples/async-timeout.rs

#![no_main]
#![no_std]
#![deny(warnings)]
#![deny(unsafe_code)]
#![deny(missing_docs)]
#![feature(type_alias_impl_trait)]

use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;
use rtic_monotonics::systick::*;

#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)]
mod app {
    use super::*;
    use futures::{future::FutureExt, select_biased};
    use rtic_monotonics::Monotonic;

    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

    // ANCHOR: init
    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        hprintln!("init");

        let systick_token = rtic_monotonics::create_systick_token!();
        Systick::start(cx.core.SYST, 12_000_000, systick_token);
        // ANCHOR_END: init

        foo::spawn().ok();

        (Shared {}, Local {})
    }

    #[task]
    async fn foo(_cx: foo::Context) {
        // ANCHOR: select_biased
        // Call hal with short relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v),
            _ = Systick::delay(200.millis()).fuse() =>  hprintln!("timeout", ), // this will finish first
        }

        // Call hal with long relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v), // hal finish first
            _ = Systick::delay(1000.millis()).fuse() =>  hprintln!("timeout", ),
        }
        // ANCHOR_END: select_biased

        // ANCHOR: timeout_after_basic
        // Call hal with long relative timeout using monotonic `timeout_after`
        match Systick::timeout_after(1000.millis(), hal_get(1)).await {
            Ok(v) => hprintln!("hal returned {}", v),
            _ => hprintln!("timeout"),
        }
        // ANCHOR_END: timeout_after_basic

        // ANCHOR: timeout_at_basic
        // get the current time instance
        let mut instant = Systick::now();

        // do this 3 times
        for n in 0..3 {
            // absolute point in time without drift
            instant += 1000.millis();
            Systick::delay_until(instant).await;

            // absolute point in time for timeout
            let timeout = instant + 500.millis();
            hprintln!("now is {:?}, timeout at {:?}", Systick::now(), timeout);

            match Systick::timeout_at(timeout, hal_get(n)).await {
                Ok(v) => hprintln!("hal returned {} at time {:?}", v, Systick::now()),
                _ => hprintln!("timeout"),
            }
        }
        // ANCHOR_END: timeout_at_basic

        debug::exit(debug::EXIT_SUCCESS);
    }
}

// Emulate some hal
async fn hal_get(n: u32) -> u32 {
    // emulate some delay time dependent on n
    let d = 350.millis() + n * 100.millis();
    hprintln!("the hal takes a duration of {:?}", d);
    Systick::delay(d).await;
    // emulate some return value
    5
}
$ cargo run --target thumbv7m-none-eabi --example async-timeout --features test-critical-section 
init
the hal takes a duration of Duration { ticks: 45 }
timeout
the hal takes a duration of Duration { ticks: 45 }
hal returned 5
the hal takes a duration of Duration { ticks: 45 }
hal returned 5
now is Instant { ticks: 213 }, timeout at Instant { ticks: 263 }
the hal takes a duration of Duration { ticks: 35 }
hal returned 5 at time Instant { ticks: 249 }
now is Instant { ticks: 313 }, timeout at Instant { ticks: 363 }
the hal takes a duration of Duration { ticks: 45 }
hal returned 5 at time Instant { ticks: 359 }
now is Instant { ticks: 413 }, timeout at Instant { ticks: 463 }
the hal takes a duration of Duration { ticks: 55 }
timeout