Skip to content

User Guide Syntax

Zakaria Madaoui edited this page Aug 9, 2026 · 19 revisions

Syntax Reference

This page describes the RTICX attributes currently supported by the core pass (rticx-core) and the software-tasks pass (rticx-sw-pass). Other officially supported passes may add their own attributes; those will be documented as they are added.

Application-level attributes: #[app(...)]

The #[<distro>::app(...)] attribute is placed on the module that contains your application.

Common arguments

#[<distro>::app(device = rp2040_hal::pac, dispatchers = [DMA_IRQ_0])]
mod my_app { ... }
  • device = path — path to the PAC (Peripheral Access Crate) for the target.
  • cores = N — number of cores (default is 1).
  • dispatchers = [...] — single-core list of interrupt dispatchers for software tasks.
  • dispatchers = [[...], [...]] — per-core dispatcher lists for software tasks on multicore targets.

Shared resources: #[shared]

#[shared]
struct SharedResources {
    alarm: Alarm0,
    led: LedOutPin,
}

Fields of SharedResources are accessed inside tasks via self.shared().name and locked with .lock(|resource| { ... }).

Init: #[init]

#[init]
fn system_init() -> SharedResources {
    // setup code
    SharedResources { alarm, led }
}

The init function runs once at startup and returns the initial shared resources. On multicore targets, use #[init(core = N)].

Idle: #[idle]

#[idle]
struct MyIdleTask;
impl RticIdleTask for MyIdleTask {
    fn init() -> Self { Self }
    fn exec(&mut self) -> ! {
        loop { /* low-power or background work */ }
    }
}

On multicore targets, use #[idle(core = N)].

Hardware tasks: #[task(...)]

#[task(binds = TIMER_IRQ_0, priority = 3, shared = [alarm, led])]
struct MyTask;
impl RticTask for MyTask {
    fn init() -> Self { Self }
    fn exec(&mut self) { /* handler code */ }
}
  • binds = IRQ — interrupt line that triggers this task.
  • priority = N — task priority. Higher values preempt lower values.
  • shared = [...] — list of shared resource fields this task accesses.
  • core = N — on multicore targets, the core on which the interrupt lives.

Software tasks: #[sw_task(...)]

Software tasks are provided by the rticx-sw-pass compilation pass. They are triggered by other tasks via message queues instead of direct interrupts.

#[sw_task(priority = 2, shared = [led], spawn_by = 0, core = 0)]
struct MySwTask;
impl RticSwTask for MySwTask {
    type SpawnInput = u16;
    fn init() -> Self { Self }
    fn exec(&mut self, input: u16) { /* handler code */ }
}
  • priority = N — dispatcher hardware task priority.
  • shared = [...] — shared resources this task accesses.
  • core = N — the core on which this task executes.
  • spawn_by = N — the only core allowed to spawn this task. If omitted, any core may spawn it.

Spawning software tasks

  • MySwTask::spawn(value) — spawn from the same core.
  • MySwTask::spawn_from(core, value) — spawn from another core, when allowed.

Spawning from #[init] always returns Err(input). Use #[post_init] to spawn initial tasks.

Resource locking

Inside a task, shared resources are accessed through the shared proxy and locked to obtain a mutable reference:

self.shared().led.lock(|led| {
    let _ = led.set_high();
});

The lock implementation is target-specific and generated by the distribution backend.

Async software tasks: #[async_task(...)]

Async tasks provide async fn / .await support in RTICX. Each async task has a future polled by an executor loop — which is a hardware-task dispatcher shared by all tasks at the same priority or (core, priority) pair in multicore case.

Async tasks require the async distribution feature and the rticx-async crate in your dependencies.

Syntax

#[async_task(priority = 2, shared = [counter])]     // core and spawn_by optional
struct Worker {
    rx: Receiver<'static, u32, 4>,
}

impl RticAsyncTask for Worker {
    type InitArgs = Self;           // late-init via TaskInits
    type SpawnInput = ();           // input type for exec
    fn init(s: Self::InitArgs) -> Self { s }
    async fn exec(&mut self, input: Self::SpawnInput) { ... }
}

Channels

Task-to-task communication uses channels from rticx-async:

use rticx_async::{channel::{Receiver, Sender}, make_channel};

let (tx, rx) = make_channel!(u32, 4); 

The make_channel! macro creates a channel backed by static memory, returning (Sender<'static, T, N>, Receiver<'static, T, N>). Each call site can only execute once (a run-time guard panics on repeated calls). Channels must be created and split in #[init] and passed into tasks via TaskInits.

Spawning

let _ = Worker::spawn(input);                       // same core → Result<(), Input>
let _ = Worker::spawn_from(core_token, input);      // cross-core (spawn_by)
// Ok(()) on success, Err(input) if the task is already running
// Err(input) if called from #[init] (tasks not yet initialized)

Spawning from #[init] always returns Err(input). Use #[post_init] to spawn initial tasks.

Priority 0 async tasks

Setting priority = 0 assigns the task to the idle executor — a framework-generated #[idle] task that busy-polls all priority-0 futures. Priority-0 tasks do not require a dispatcher IRQ.

#[async_task(priority = 0)]
struct Background;
impl RticAsyncTask for Background {
    type InitArgs = ();
    type SpawnInput = ();
    fn init(_: Self::InitArgs) -> Self { Self }
    async fn exec(&mut self, _input: ()) {
        loop {
            do_background_work().await;
        }
    }
}
  • No dispatcher needed: priority-0 tasks are polled by the idle loop, not by an interrupt-driven dispatcher.
  • No custom #[idle]: defining priority-0 tasks and a custom #[idle] simultaneously is a compile error. Use #[post_init] to spawn initial tasks instead.
  • No cross-core priority 0: spawn_by != core is disallowed for priority-0 tasks.

Dispatcher requirements

Every async priority group needs a dispatcher IRQ listed in dispatchers = [...] (one per priority level). Dispatchers are hardware tasks bound to the listed IRQs; they poll all tasks in their priority group and self-pend until all futures complete.

Other officially supported passes

The following passes are available but may add their own attributes. Detailed syntax for each will be added as the documentation expands:

  • rticx-deadline-pass — converts deadline = D attributes into RTICX priorities.
  • rticx-auto-assign — automatically assigns core = N based on shared resource usage.
  • rticx-async-pass — adds #[async_task] with async/await, channels, and executor dispatching (see Async software tasks above).

See the Supported Distributions page to know which passes are enabled by each distribution.

Next steps

Clone this wiki locally