The background task #[idle]

A function marked with the idle attribute can optionally appear in the module. This becomes the special idle task and must have signature fn(idle::Context) -> !.

When present, the runtime will execute the idle task after init. Unlike init, idle will run with interrupts enabled and must never return, as the -> ! function signature indicates. The Rust type ! means “never”.

Like in init, locally declared resources will have 'static lifetimes that are safe to access.

The example below shows that idle runs after init.

//! examples/idle.rs

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

use panic_semihosting as _;

#[rtic::app(device = lm3s6965)]
mod app {
    use cortex_m_semihosting::{debug, hprintln};

    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

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

        (Shared {}, Local {})
    }

    #[idle(local = [x: u32 = 0])]
    fn idle(cx: idle::Context) -> ! {
        // Locals in idle have lifetime 'static
        let _x: &'static mut u32 = cx.local.x;

        hprintln!("idle");

        debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator

        loop {
            cortex_m::asm::nop();
        }
    }
}
$ cargo xtask qemu --verbose --example idle
init
idle

By default, the RTIC idle task does not try to optimize for any specific targets.

A common useful optimization is to enable the SLEEPONEXIT and allow the MCU to enter sleep when reaching idle.

Caution: some hardware unless configured disables the debug unit during sleep mode.

Consult your hardware specific documentation as this is outside the scope of RTIC.

The following example shows how to enable sleep by setting the SLEEPONEXIT and providing a custom idle task replacing the default nop() with wfi().

//! examples/idle-wfi.rs

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

use panic_semihosting as _;

#[rtic::app(device = lm3s6965)]
mod app {
    use cortex_m_semihosting::{debug, hprintln};

    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

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

        // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts
        // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
        cx.core.SCB.set_sleepdeep();

        (Shared {}, Local {})
    }

    #[idle(local = [x: u32 = 0])]
    fn idle(cx: idle::Context) -> ! {
        // Locals in idle have lifetime 'static
        let _x: &'static mut u32 = cx.local.x;

        hprintln!("idle");

        debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator

        loop {
            // Now Wait For Interrupt is used instead of a busy-wait loop
            // to allow MCU to sleep between interrupts
            // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/WFI
            rtic::export::wfi()
        }
    }
}
$ cargo xtask qemu --verbose --example idle-wfi
init
idle

Notice: The idle task cannot be used together with software tasks running at priority zero. The reason is that idle is running as a non-returning Rust function at priority zero. Thus there would be no way for an executor at priority zero to give control to software tasks at the same priority.