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: Vanillamain
must be supported in binary crates. You can use thecortex-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 afterinit
.
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 Resource
s 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 |
Threshold |
Preemption threshold |
Traits
GreaterThanOrEqual |
Type-level |
LessThanOrEqual |
Type-level |
ResourceLike |
Maps a |
Functions
atomic |
Runs the closure |
bkpt |
Puts the processor in Debug state. Debuggers can pick this up as a "breakpoint". |
disable |
Disables a |
enable |
Enables a |
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 |
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 |