Crate cortex_m_rtfm [] [src]

Real Time For the Masses (RTFM), a framework for building concurrent applications, for ARM Cortex-M microcontrollers

This crate is based on the RTFM framework created by the Embedded Systems group at Luleå University of Technology, led by Prof. Per Lindgren, and uses a simplified version of the Stack Resource Policy as scheduling policy (check the references for details).

Features

  • Event triggered tasks as the unit of concurrency.
  • Support for prioritization of tasks and, thus, preemptive multitasking.
  • Efficient and data race free memory sharing through fine grained non global critical sections.
  • Deadlock free execution guaranteed at compile time.
  • Minimal scheduling overhead as the scheduler has no "software component": the hardware does all the scheduling.
  • Highly efficient memory usage: All the tasks share a single call stack and there's no hard dependency on a dynamic memory allocator.
  • All Cortex M3, M4 and M7 devices are fully supported. M0(+) is partially supported as the whole API is not available due to missing hardware features.
  • The number of task priority levels is configurable at compile time through the P2 (4 levels), P3 (8 levels), etc. Cargo features. The number of priority levels supported by the hardware is device specific but this crate defaults to 16 as that's the most common scenario.
  • This task model is amenable to known WCET (Worst Case Execution Time) analysis and scheduling analysis techniques. (Though we haven't yet developed Rust friendly tooling for that.)

Requirements

  • Tasks must run to completion. That's it, tasks can't contain endless loops.
  • Task priorities must remain constant at runtime.

Dependencies

  • A device crate generated using svd2rust v0.7.x
  • A start lang time: Vanilla main must be supported in binary crates. You can use the cortex-m-rt crate to fulfill the requirement

Examples

Ordered in increasing level of complexity:

Zero tasks

#![feature(used)]
#![no_std]

#[macro_use] // for the `hprintln!` macro
extern crate cortex_m;

// before main initialization + `start` lang item
extern crate cortex_m_rt;

#[macro_use] // for the `tasks!` macro
extern crate cortex_m_rtfm as rtfm;

// device crate generated using svd2rust
extern crate stm32f30x;

use rtfm::{P0, T0, TMax};

// TASKS (None in this example)
tasks!(stm32f30x, {});

// INITIALIZATION PHASE
fn init(_priority: P0, _threshold: &TMax) {
    hprintln!("INIT");
}

// IDLE LOOP
fn idle(_priority: P0, _threshold: T0) -> ! {
    hprintln!("IDLE");

    // Sleep
    loop {
        rtfm::wfi();
    }
}

Expected output:

INIT
IDLE

The tasks! macro overrides the main function and imposes the following structure into your program:

  • init, the initialization phase, runs first. This function is executed "atomically", in the sense that no task / interrupt can preempt it.

  • idle, a never ending function that runs after init.

Both init and idle have a priority of 0, the lowest priority. In RTFM, a higher priority value means more urgent.

One task

#![feature(const_fn)]
#![feature(used)]
#![no_std]

extern crate cortex_m_rt;
#[macro_use]
extern crate cortex_m_rtfm as rtfm;
extern crate stm32f30x;

use stm32f30x::interrupt::Tim7;
use rtfm::{Local, P0, P1, T0, T1, TMax};

// INITIALIZATION PHASE
fn init(_priority: P0, _threshold: &TMax) {
    // Configure TIM7 for periodic interrupts
    // Configure GPIO for LED driving
}

// IDLE LOOP
fn idle(_priority: P0, _threshold: T0) -> ! {
    // Sleep
    loop {
        rtfm::wfi();
    }
}

// TASKS
tasks!(stm32f30x, {
    periodic: Task {
        interrupt: Tim7,
        priority: P1,
        enabled: true,
    },
});

fn periodic(mut task: Tim7, _priority: P1, _threshold: T1) {
    // Task local data
    static STATE: Local<bool, Tim7> = Local::new(false);

    let state = STATE.borrow_mut(&mut task);

    // Toggle state
    *state = !*state;

    // Blink an LED
    if *state {
        LED.on();
    } else {
        LED.off();
    }
}

Here we define a task named periodic and bind it to the Tim7 interrupt. The periodic task will run every time the Tim7 interrupt is triggered. We assign to this task a priority of 1 (P1); this is the lowest priority that a task can have.

We use the Local abstraction to add state to the task; this task local data will be preserved across runs of the periodic task. Note that STATE is owned by the periodic task, in the sense that no other task can access it; this is reflected in its type signature (the Tim7 type parameter).

Two "serial" tasks

#![feature(const_fn)]
#![feature(used)]
#![no_std]

extern crate cortex_m_rt;
#[macro_use]
extern crate cortex_m_rtfm as rtfm;
extern crate stm32f30x;

use core::cell::Cell;

use stm32f30x::interrupt::{Tim6Dacunder, Tim7};
use rtfm::{C1, P0, P1, Resource, T0, T1, TMax};

tasks!(stm32f30x, {
    t1: Task {
        interrupt: Tim6Dacunder,
        priority: P1,
        enabled: true,
    },
    t2: Task {
        interrupt: Tim7,
        priority: P1,
        enabled: true,
    },
});

// Data shared between tasks `t1` and `t2`
static COUNTER: Resource<Cell<u32>, C1> = Resource::new(Cell::new(0));

fn init(priority: P0, threshold: &TMax) {
    // ..
}

fn idle(priority: P0, threshold: T0) -> ! {
    // Sleep
    loop {
        rtfm::wfi();
    }
}

fn t1(_task: Tim6Dacunder, priority: P1, threshold: T1) {
    let counter = COUNTER.access(&priority, &threshold);

    counter.set(counter.get() + 1);
}

fn t2(_task: Tim7, priority: P1, threshold: T1) {
    let counter = COUNTER.access(&priority, &threshold);

    counter.set(counter.get() + 2);
}

Here we declare two tasks, t1 and t2; both with a priority of 1 (P1). As both tasks have the same priority, we say that they are serial tasks in the sense that t1 can only run after t2 is done and vice versa; i.e. no preemption between them is possible.

To share data between these two tasks, we use the Resource abstraction. As the tasks can't preempt each other, they can access the COUNTER resource using the zero cost access method -- no synchronization is required.

COUNTER has an extra type parameter: C1. This is the ceiling of the resource. For now suffices to say that the ceiling must be the maximum of the priorities of all the tasks that access the resource -- in this case, C1 == max(P1, P1). If you try a smaller value like C0, you'll find out that your program doesn't compile.

Preemptive multitasking

#![feature(const_fn)]
#![feature(used)]
#![no_std]

extern crate cortex_m_rt;
#[macro_use]
extern crate cortex_m_rtfm as rtfm;
extern crate stm32f30x;

use core::cell::Cell;

use stm32f30x::interrupt::{Tim6Dacunder, Tim7};
use rtfm::{C2, P0, P1, P2, Resource, T0, T1, T2, TMax};

tasks!(stm32f30x, {
    t1: Task {
        interrupt: Tim6Dacunder,
        priority: P1,
        enabled: true,
    },
    t2: Task {
        interrupt: Tim7,
        priority: P2,
        enabled: true,
    },
});

static COUNTER: Resource<Cell<u32>, C2> = Resource::new(Cell::new(0));

fn init(priority: P0, threshold: &TMax) {
    // ..
}

fn idle(priority: P0, threshold: T0) -> ! {
    // Sleep
    loop {
        rtfm::wfi();
    }
}

fn t1(_task: Tim6Dacunder, priority: P1, threshold: T1) {
    // ..

    threshold.raise(
        &COUNTER, |threshold: &T2| {
            let counter = COUNTER.access(&priority, threshold);

            counter.set(counter.get() + 1);
        }
    );

    // ..
}

fn t2(_task: Tim7, priority: P2, threshold: T2) {
    let counter = COUNTER.access(&priority, &threshold);

    counter.set(counter.get() + 2);
}

Now we have a variation of the previous example. Like before, t1 has a priority of 1 (P1) but t2 now has a priority of 2 (P2). This means that t2 can preempt t1 if a Tim7 interrupt occurs while t1 is being executed.

To avoid data races, t1 must modify COUNTER in an atomic way; i.e. t2 most not preempt t1 while COUNTER is being modified. This is accomplished by raise-ing the preemption threshold. This creates a critical section, denoted by a closure; for whose execution, COUNTER is accessible while t2 is prevented from preempting t1.

How t2 accesses COUNTER remains unchanged. Since t1 can't preempt t2 due to the differences in priority; no critical section is needed in t2.

Note that the ceiling of COUNTER had to be changed to C2. This is required because the ceiling must be the maximum between P1 and P2.

Finally, it should be noted that the critical section in t1 will only block tasks with a priority of 2 or lower. This is exactly what the preemption threshold represents: it's the "bar" that a task priority must pass in order to be able to preempt the current task / critical section. Note that a task with a priority of e.g. 3 (P3) effectively imposes a threshold of 3 (C3) because only a task with a priority of 4 or greater can preempt it.

Peripherals as resources

#![feature(const_fn)]
#![feature(used)]
#![no_std]

extern crate cortex_m_rt;
#[macro_use]
extern crate cortex_m_rtfm as rtfm;
extern crate stm32f30x;

use rtfm::{P0, Peripheral, T0, TMax};

peripherals!(stm32f30x, {
    GPIOA: Peripheral {
        register_block: Gpioa,
        ceiling: C0,
    },
    RCC: Peripheral {
        register_block: Rcc,
        ceiling: C0,
    },
});

tasks!(stm32f30x, {});

fn init(priority: P0, threshold: &TMax) {
    let gpioa = GPIOA.access(&priority, threshold);
    let rcc = RCC.access(&priority, threshold);

    // ..
}

fn idle(_priority: P0, _threshold: T0) -> ! {
    // Sleep
    loop {
        rtfm::wfi();
    }
}

Peripherals are global resources too and as such they can be protected in the same way as Resources using the Peripheral abstraction.

Peripheral and Resource has pretty much the same API except that Peripheral instances must be declared using the peripherals! macro.

References

  • Baker, T. P. (1991). Stack-based scheduling of realtime processes. Real-Time Systems, 3(1), 67-99.

The original Stack Resource Policy paper. PDF.

  • Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P. (2013, June). Real-time for the masses, step 1: Programming API and static priority SRP kernel primitives. In Industrial Embedded Systems (SIES), 2013 8th IEEE International Symposium on (pp. 110-113). IEEE.

A description of the RTFM task and resource model. PDF

Macros

peripherals

A macro to assign ceilings to peripherals

tasks

A macro to declare tasks

Structs

Local

Task local data

Peripheral

A hardware peripheral as a resource

Priority

Priority

Resource

A resource with ceiling C

Threshold

Preemption threshold

Traits

GreaterThanOrEqual

Type-level >= operator

LessThanOrEqual

Type-level <= operator

ResourceLike

Maps a Resource / Peripheral to its ceiling

Functions

atomic

Runs the closure f "atomically"

bkpt

Puts the processor in Debug state. Debuggers can pick this up as a "breakpoint".

disable

Disables a task

enable

Enables a task

hw2logical

Converts a shifted hardware priority into a logical priority

logical2hw

Converts a logical priority into a shifted hardware priority, as used by the NVIC and the BASEPRI register

request

Requests the execution of a task

wfi

Wait For Interrupt

Type Definitions

C0

A ceiling of 0

C1

A ceiling of 1

C2

A ceiling of 2

C3

A ceiling of 3

C4

A ceiling of 4

C5

A ceiling of 5

C6

A ceiling of 6

C7

A ceiling of 7

C8

A ceiling of 8

C9

A ceiling of 9

C10

A ceiling of 10

C11

A ceiling of 11

C12

A ceiling of 12

C13

A ceiling of 13

C14

A ceiling of 14

C15

A ceiling of 15

C16

A ceiling of 16

CMax

Maximum ceiling

P0

A priority of 0, the lowest priority

P1

A priority of 1

P2

A priority of 2

P3

A priority of 3

P4

A priority of 4

P5

A priority of 5

P6

A priority of 6

P7

A priority of 7

P8

A priority of 8

P9

A priority of 9

P10

A priority of 10

P11

A priority of 11

P12

A priority of 12

P13

A priority of 13

P14

A priority of 14

P15

A priority of 15

P16

A priority of 16, the highest priority

PMax

Maximum priority

T0

A preemption threshold of 0

T1

A preemption threshold of 1

T2

A preemption threshold of 2

T3

A preemption threshold of 3

T4

A preemption threshold of 4

T5

A preemption threshold of 5

T6

A preemption threshold of 6

T7

A preemption threshold of 7

T8

A preemption threshold of 8

T9

A preemption threshold of 9

T10

A preemption threshold of 10

T11

A preemption threshold of 11

T12

A preemption threshold of 12

T13

A preemption threshold of 13

T14

A preemption threshold of 14

T15

A preemption threshold of 15

T16

A preemption threshold of 16

TMax

Maximum preemption threshold

UMax

Maximum priority level