RTIC is a framework that transforms Rust into a real-time operating system by using the compiler to enforce safe, priority-based scheduling of tasks across interrupts and a main loop. You define tasks as functions annotated with specific attributes, and RTIC generates the necessary interrupt handlers and scheduling logic to ensure deterministic execution without manual context switching.
To get started, you need a supported target (like a Cortex-M microcontroller) and the rtic crate. First, define your resources (shared data) and tasks in a dedicated module. The #[rtic::app(device = ...)] macro wraps your application, where you declare resources in the resources block and tasks in the shared or local blocks. Tasks are scheduled based on priority, and the compiler prevents data races by enforcing that only one task of a specific priority can access a resource at a time.
Here is a minimal example for a Cortex-M4 device using the cortex-m-rtic ecosystem:
#![no_std]
#![no_main]
use panic_halt as _;
use rtic::app;
// Define the device and its peripherals
#[app(device = pac, dispatchers = [TIM1])]
mod app {
use crate::pac;
use rtic::monotonics::Monotonic;
// Shared resources accessible by tasks
#[shared]
struct Shared {
counter: u32,
}
// Local resources (task-specific)
#[local]
struct Local {
led: bool,
}
// A periodic task triggered by an interrupt (e.g., Timer)
#[task(binds = TIM1, shared = [counter])]
fn timer_tick(cx: timer_tick::Context) {
let mut counter = cx.shared.counter;
// Critical section ensures atomic access
let mut c = counter.lock(|c| *c);
*c += 1;
// Toggle LED logic would go here
}
// The main loop runs at the lowest priority
#[task(local = [led], shared = [counter])]
fn idle(cx: idle::Context) {
let mut led = cx.local.led;
let mut counter = cx.shared.counter;
// Access shared data safely
let count = counter.lock(|c| *c);
if count % 100 == 0 {
*led = !*led;
// Toggle actual GPIO here
}
}
}
In this setup, timer_tick runs at a higher priority than idle. When the TIM1 interrupt fires, the scheduler preempts idle, executes timer_tick, and resumes idle automatically. The .lock() method on shared resources creates a critical section, disabling interrupts to prevent race conditions while the data is being modified.
You must configure your Cargo.toml to include the rtic crate and your specific device PAC (Peripheral Access Crate). For example:
[dependencies]
rtic = { version = "2.0", features = ["cortex-m"] }
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
# Replace 'stm32f4' with your specific device PAC
stm32f4 = { version = "0.20", features = ["stm32f407"] }
Finally, ensure your linker script and interrupt vector table are correctly configured for your hardware, as RTIC relies on the underlying cortex-m-rt crate to map interrupt vectors to your generated handlers.