Migrating from v0.5.x to v1.0.0

This section describes how to upgrade from v0.5.x to v1.0.0 of the RTIC framework.

Cargo.toml - version bump

Change the version of cortex-m-rtic to "1.0.0".

mod instead of const

With the support of attributes on modules the const APP workaround is not needed.

Change

#![allow(unused)]
fn main() {
#[rtic::app(/* .. */)]
const APP: () = {
  [code here]
};
}

into

#![allow(unused)]
fn main() {
#[rtic::app(/* .. */)]
mod app {
  [code here]
}
}

Now that a regular Rust module is used it means it is possible to have custom user code within that module. Additionally, it means that use-statements for resources used in user code must be moved inside mod app, or be referred to with super. For example, change:

#![allow(unused)]
fn main() {
use some_crate::some_func;

#[rtic::app(/* .. */)]
const APP: () = {
    fn func() {
        some_crate::some_func();
    }
};
}

into

#![allow(unused)]
fn main() {
#[rtic::app(/* .. */)]
mod app {
    use some_crate::some_func;

    fn func() {
        some_crate::some_func();
    }
}
}

or

#![allow(unused)]
fn main() {
use some_crate::some_func;

#[rtic::app(/* .. */)]
mod app {
    fn func() {
        super::some_crate::some_func();
    }
}
}

Move Dispatchers from extern "C" to app arguments

Change

#![allow(unused)]
fn main() {
#[rtic::app(/* .. */)]
const APP: () = {
    [code here]

    // RTIC requires that unused interrupts are declared in an extern block when
    // using software tasks; these free interrupts will be used to dispatch the
    // software tasks.
    extern "C" {
        fn SSI0();
        fn QEI0();
    }
};
}

into

#![allow(unused)]
fn main() {
#[rtic::app(/* .. */, dispatchers = [SSI0, QEI0])]
mod app {
  [code here]
}
}

This works also for ram functions, see examples/ramfunc.rs

Resources structs - #[shared], #[local]

Previously the RTIC resources had to be in in a struct named exactly "Resources":

#![allow(unused)]
fn main() {
struct Resources {
    // Resources defined in here
}
}

With RTIC v1.0.0 the resources structs are annotated similarly like #[task], #[init], #[idle]: with the attributes #[shared] and #[local]

#![allow(unused)]
fn main() {
#[shared]
struct MySharedResources {
    // Resources shared between tasks are defined here
}

#[local]
struct MyLocalResources {
    // Resources defined here cannot be shared between tasks; each one is local to a single task
}
}

These structs can be freely named by the developer.

shared and local arguments in #[task]s

In v1.0.0 resources are split between shared resources and local resources. #[task], #[init] and #[idle] no longer have a resources argument; they must now use the shared and local arguments.

In v0.5.x:

#![allow(unused)]
fn main() {
struct Resources {
    local_to_b: i64,
    shared_by_a_and_b: i64,
}

#[task(resources = [shared_by_a_and_b])]
fn a(_: a::Context) {}

#[task(resources = [shared_by_a_and_b, local_to_b])]
fn b(_: b::Context) {}
}

In v1.0.0:

#![allow(unused)]
fn main() {
#[shared]
struct Shared {
    shared_by_a_and_b: i64,
}

#[local]
struct Local {
    local_to_b: i64,
}

#[task(shared = [shared_by_a_and_b])]
fn a(_: a::Context) {}

#[task(shared = [shared_by_a_and_b], local = [local_to_b])]
fn b(_: b::Context) {}
}

Symmetric locks

Now RTIC utilizes symmetric locks, this means that the lock method need to be used for all shared resource access. In old code one could do the following as the high priority task has exclusive access to the resource:

#![allow(unused)]
fn main() {
#[task(priority = 2, resources = [r])]
fn foo(cx: foo::Context) {
    cx.resources.r = /* ... */;
}

#[task(resources = [r])]
fn bar(cx: bar::Context) {
    cx.resources.r.lock(|r| r = /* ... */);
}
}

And with symmetric locks one needs to use locks in both tasks:

#![allow(unused)]
fn main() {
#[task(priority = 2, shared = [r])]
fn foo(cx: foo::Context) {
    cx.shared.r.lock(|r| r = /* ... */);
}

#[task(shared = [r])]
fn bar(cx: bar::Context) {
    cx.shared.r.lock(|r| r = /* ... */);
}
}

Note that the performance does not change thanks to LLVM's optimizations which optimizes away unnecessary locks.

Lock-free resource access

In RTIC 0.5 resources shared by tasks running at the same priority could be accessed without the lock API. This is still possible in 1.0: the #[shared] resource must be annotated with the field-level #[lock_free] attribute.

v0.5 code:

#![allow(unused)]
fn main() {
struct Resources {
    counter: u64,
}

#[task(resources = [counter])]
fn a(cx: a::Context) {
    *cx.resources.counter += 1;
}

#[task(resources = [counter])]
fn b(cx: b::Context) {
    *cx.resources.counter += 1;
}
}

v1.0 code:

#![allow(unused)]
fn main() {
#[shared]
struct Shared {
    #[lock_free]
    counter: u64,
}

#[task(shared = [counter])]
fn a(cx: a::Context) {
    *cx.shared.counter += 1;
}

#[task(shared = [counter])]
fn b(cx: b::Context) {
    *cx.shared.counter += 1;
}
}

no static mut transform

static mut variables are no longer transformed to safe &'static mut references. Instead of that syntax, use the local argument in #[init].

v0.5.x code:

#![allow(unused)]
fn main() {
#[init]
fn init(_: init::Context) {
    static mut BUFFER: [u8; 1024] = [0; 1024];
    let buffer: &'static mut [u8; 1024] = BUFFER;
}
}

v1.0.0 code:

#![allow(unused)]
fn main() {
#[init(local = [
    buffer: [u8; 1024] = [0; 1024]
//   type ^^^^^^^^^^^^   ^^^^^^^^^ initial value
])]
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
    let buffer: &'static mut [u8; 1024] = cx.local.buffer;

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

Init always returns late resources

In order to make the API more symmetric the #[init]-task always returns a late resource.

From this:

#![allow(unused)]
fn main() {
#[rtic::app(device = lm3s6965)]
const APP: () = {
    #[init]
    fn init(_: init::Context) {
        rtic::pend(Interrupt::UART0);
    }

    // [more code]
};
}

to this:

#![allow(unused)]
fn main() {
#[rtic::app(device = lm3s6965)]
mod app {
    #[shared]
    struct MySharedResources {}

    #[local]
    struct MyLocalResources {}

    #[init]
    fn init(_: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
        rtic::pend(Interrupt::UART0);

        (MySharedResources, MyLocalResources, init::Monotonics())
    }

    // [more code]
}
}

Spawn from anywhere

With the new spawn/spawn_after/spawn_at interface, old code requiring the context cx for spawning such as:

#![allow(unused)]
fn main() {
#[task(spawn = [bar])]
fn foo(cx: foo::Context) {
    cx.spawn.bar().unwrap();
}

#[task(schedule = [bar])]
fn bar(cx: bar::Context) {
    cx.schedule.foo(/* ... */).unwrap();
}
}

Will now be written as:

#![allow(unused)]
fn main() {
#[task]
fn foo(_c: foo::Context) {
    bar::spawn().unwrap();
}

#[task]
fn bar(_c: bar::Context) {
    // Takes a Duration, relative to “now”
    let spawn_handle = foo::spawn_after(/* ... */);
}

#[task]
fn bar(_c: bar::Context) {
    // Takes an Instant
    let spawn_handle = foo::spawn_at(/* ... */);
}
}

Thus the requirement of having access to the context is dropped.

Note that the attributes spawn/schedule in the task definition are no longer needed.


Additions

Extern tasks

Both software and hardware tasks can now be defined external to the mod app. Previously this was possible only by implementing a trampoline calling out the task implementation.

See examples examples/extern_binds.rs and examples/extern_spawn.rs.

This enables breaking apps into multiple files.