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

Requirements

Dependencies

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:

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

The original Stack Resource Policy paper. PDF.

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