Hardware tasks

At its core RTIC is using the hardware interrupt controller (ARM NVIC on cortex-m) to perform scheduling and executing tasks, and all tasks except #[init] and #[idle] run as interrupt handlers. This also means that you can manually bind tasks to interrupt handlers.

To bind an interrupt use the #[task] attribute argument binds = InterruptName. This task becomes the interrupt handler for this hardware interrupt vector.

All tasks bound to an explicit interrupt are hardware tasks since they start execution in reaction to a hardware event.

Specifying a non-existing interrupt name will cause a compilation error. The interrupt names are commonly defined by PAC or HAL crates.

Any available interrupt vector should work, but different hardware might have added special properties to select interrupt priority levels, such as the nRF “softdevice”.

Beware of re-purposing interrupt vectors used internally by hardware features, RTIC is unaware of such hardware specific details.

The example below demonstrates the use of the #[task(binds = InterruptName)] attribute to declare a hardware task bound to an interrupt handler.


#![allow(unused)]
fn main() {
//! examples/hardware.rs

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

use panic_semihosting as _;

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

    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

    #[init]
    fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
        // Pends the UART0 interrupt but its handler won't run until *after*
        // `init` returns because interrupts are disabled
        rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend

        hprintln!("init").unwrap();

        (Shared {}, Local {}, init::Monotonics())
    }

    #[idle]
    fn idle(_: idle::Context) -> ! {
        // interrupts are enabled again; the `UART0` handler runs at this point

        hprintln!("idle").unwrap();

        rtic::pend(Interrupt::UART0);

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

        loop {
            cortex_m::asm::nop();
        }
    }

    #[task(binds = UART0, local = [times: u32 = 0])]
    fn uart0(cx: uart0::Context) {
        // Safe access to local `static mut` variable
        *cx.local.times += 1;

        hprintln!(
            "UART0 called {} time{}",
            *cx.local.times,
            if *cx.local.times > 1 { "s" } else { "" }
        )
        .unwrap();
    }
}
}
$ cargo run --target thumbv7m-none-eabi --example hardware
init
UART0 called 1 time
idle
UART0 called 2 times