{"doc_urls":["preface.html#preface","preface.html#is-rtic-an-rtos","preface.html#features","preface.html#crate-cortex-m-06-vs-07-in-rtic-05x","preface.html#user-documentation","preface.html#api-reference","preface.html#community-provided-examples-repo","preface.html#chat","preface.html#contributing","preface.html#running-tests-locally","preface.html#acknowledgments","preface.html#references","preface.html#license","preface.html#contribution","by-example.html#rtic-by-example","by-example/app.html#the-app-attribute-and-an-rtic-application","by-example/app.html#requirements-on-the-app-attribute","by-example/app.html#an-rtic-application-example","by-example/resources.html#resource-usage","by-example/resources.html#local-resources","by-example/resources.html#task-local-initialized-resources","by-example/resources.html#shared-resources-and-lock","by-example/resources.html#multi-lock","by-example/resources.html#only-shared---access","by-example/resources.html#lock-free-resource-access-of-shared-resources","by-example/app_init.html#app-initialization-and-the-init-task","by-example/app_init.html#example","by-example/app_idle.html#the-background-task-idle","by-example/app_task.html#defining-tasks-with-task","by-example/hardware_tasks.html#hardware-tasks","by-example/software_tasks.html#software-tasks--spawn","by-example/message_passing.html#message-passing--capacity","by-example/app_priorities.html#task-priorities","by-example/app_priorities.html#priorities","by-example/monotonic.html#monotonic--spawn_","by-example/monotonic.html#canceling-or-rescheduling-a-scheduled-task","by-example/starting_a_project.html#starting-a-new-project","by-example/app_minimal.html#the-minimal-app","by-example/tips.html#tips--tricks","by-example/tips_monotonic_impl.html#implementing-a-monotonic-timer-for-scheduling","by-example/tips_destructureing.html#resource-de-structure-ing","by-example/tips_indirection.html#using-indirection-for-faster-message-passing","by-example/tips_static_lifetimes.html#static-super-powers","by-example/tips_view_code.html#inspecting-generated-code","by-example/tips_from_ram.html#running-tasks-from-ram","awesome_rtic.html#awesome-rtic-examples","migration.html#migration-guides","migration/migration_v5.html#migrating-from-v05x-to-v100","migration/migration_v5.html#cargotoml---version-bump","migration/migration_v5.html#mod-instead-of-const","migration/migration_v5.html#move-dispatchers-from-extern-c-to-app-arguments","migration/migration_v5.html#resources-structs---shared-local","migration/migration_v5.html#shared-and-local-arguments-in-tasks","migration/migration_v5.html#symmetric-locks","migration/migration_v5.html#lock-free-resource-access","migration/migration_v5.html#no-static-mut-transform","migration/migration_v5.html#init-always-returns-late-resources","migration/migration_v5.html#spawn-from-anywhere","migration/migration_v5.html#additions","migration/migration_v5.html#extern-tasks","migration/migration_v4.html#migrating-from-v04x-to-v050","migration/migration_v4.html#project-name-change-rtfm---rtic","migration/migration_v4.html#cargotoml","migration/migration_v4.html#context-argument","migration/migration_v4.html#resources","migration/migration_v4.html#device-peripherals","migration/migration_v4.html#interrupt-and-exception","migration/migration_v4.html#schedule","migration/migration_rtic.html#migrating-from-rtfm-to-rtic","migration/migration_rtic.html#cargotoml","migration/migration_rtic.html#code-changes","internals.html#under-the-hood","internals/targets.html#target-architecture","internals/targets.html#priority-ceiling","internals/targets.html#source-masking"],"index":{"documentStore":{"docInfo":{"0":{"body":44,"breadcrumbs":2,"title":1},"1":{"body":46,"breadcrumbs":3,"title":2},"10":{"body":17,"breadcrumbs":2,"title":1},"11":{"body":64,"breadcrumbs":2,"title":1},"12":{"body":40,"breadcrumbs":2,"title":1},"13":{"body":20,"breadcrumbs":2,"title":1},"14":{"body":128,"breadcrumbs":4,"title":2},"15":{"body":0,"breadcrumbs":7,"title":4},"16":{"body":44,"breadcrumbs":6,"title":3},"17":{"body":263,"breadcrumbs":6,"title":3},"18":{"body":117,"breadcrumbs":5,"title":2},"19":{"body":246,"breadcrumbs":5,"title":2},"2":{"body":116,"breadcrumbs":2,"title":1},"20":{"body":119,"breadcrumbs":7,"title":4},"21":{"body":253,"breadcrumbs":6,"title":3},"22":{"body":115,"breadcrumbs":5,"title":2},"23":{"body":191,"breadcrumbs":5,"title":2},"24":{"body":168,"breadcrumbs":9,"title":6},"25":{"body":63,"breadcrumbs":8,"title":4},"26":{"body":132,"breadcrumbs":5,"title":1},"27":{"body":268,"breadcrumbs":7,"title":3},"28":{"body":80,"breadcrumbs":7,"title":3},"29":{"body":240,"breadcrumbs":8,"title":2},"3":{"body":57,"breadcrumbs":9,"title":8},"30":{"body":222,"breadcrumbs":10,"title":3},"31":{"body":125,"breadcrumbs":10,"title":3},"32":{"body":0,"breadcrumbs":8,"title":2},"33":{"body":297,"breadcrumbs":7,"title":1},"34":{"body":290,"breadcrumbs":8,"title":2},"35":{"body":173,"breadcrumbs":10,"title":4},"36":{"body":52,"breadcrumbs":8,"title":3},"37":{"body":40,"breadcrumbs":6,"title":2},"38":{"body":8,"breadcrumbs":6,"title":2},"39":{"body":142,"breadcrumbs":10,"title":4},"4":{"body":3,"breadcrumbs":3,"title":2},"40":{"body":128,"breadcrumbs":12,"title":4},"41":{"body":226,"breadcrumbs":13,"title":5},"42":{"body":176,"breadcrumbs":10,"title":3},"43":{"body":120,"breadcrumbs":10,"title":3},"44":{"body":188,"breadcrumbs":10,"title":3},"45":{"body":13,"breadcrumbs":6,"title":3},"46":{"body":12,"breadcrumbs":4,"title":2},"47":{"body":7,"breadcrumbs":7,"title":3},"48":{"body":6,"breadcrumbs":7,"title":3},"49":{"body":70,"breadcrumbs":7,"title":3},"5":{"body":0,"breadcrumbs":3,"title":2},"50":{"body":41,"breadcrumbs":10,"title":6},"51":{"body":51,"breadcrumbs":8,"title":4},"52":{"body":60,"breadcrumbs":8,"title":4},"53":{"body":72,"breadcrumbs":6,"title":2},"54":{"body":64,"breadcrumbs":8,"title":4},"55":{"body":60,"breadcrumbs":7,"title":3},"56":{"body":45,"breadcrumbs":9,"title":5},"57":{"body":61,"breadcrumbs":6,"title":2},"58":{"body":0,"breadcrumbs":5,"title":1},"59":{"body":26,"breadcrumbs":6,"title":2},"6":{"body":0,"breadcrumbs":5,"title":4},"60":{"body":11,"breadcrumbs":7,"title":3},"61":{"body":19,"breadcrumbs":9,"title":5},"62":{"body":30,"breadcrumbs":5,"title":1},"63":{"body":73,"breadcrumbs":6,"title":2},"64":{"body":42,"breadcrumbs":5,"title":1},"65":{"body":42,"breadcrumbs":6,"title":2},"66":{"body":53,"breadcrumbs":6,"title":2},"67":{"body":109,"breadcrumbs":5,"title":1},"68":{"body":28,"breadcrumbs":7,"title":3},"69":{"body":20,"breadcrumbs":5,"title":1},"7":{"body":11,"breadcrumbs":2,"title":1},"70":{"body":23,"breadcrumbs":6,"title":2},"71":{"body":45,"breadcrumbs":4,"title":2},"72":{"body":126,"breadcrumbs":7,"title":2},"73":{"body":7,"breadcrumbs":7,"title":2},"74":{"body":171,"breadcrumbs":7,"title":2},"8":{"body":11,"breadcrumbs":2,"title":1},"9":{"body":22,"breadcrumbs":4,"title":3}},"docs":{"0":{"body":"RTIC Real-Time Interrupt-driven Concurrency A concurrency framework for building real-time systems This book contains user level documentation for the Real-Time Interrupt-driven Concurrency (RTIC) framework. The API reference is available here . Formerly known as Real-Time For the Masses. This is the documentation of v1.0.x of RTIC; for the documentation of version v0.5.x go here . v0.4.x go here .","breadcrumbs":"Preface » Preface","id":"0","title":"Preface"},"1":{"body":"A common question is whether RTIC is an RTOS or not, and depending on your background the answer may vary. From RTIC's developers point of view; RTIC is a hardware accelerated RTOS that utilizes the NVIC in Cortex-M MCUs to perform scheduling, rather than the more classical software kernel. Another common view from the community is that RTIC is a concurrency framework as there is no software kernel and that it relies on external HALs. crates.io docs.rs book matrix Meeting notes","breadcrumbs":"Preface » Is RTIC an RTOS?","id":"1","title":"Is RTIC an RTOS?"},"10":{"body":"This crate is based on the Real-Time For the Masses language created by the Embedded Systems group at Luleå University of Technology , led by Prof. Per Lindgren .","breadcrumbs":"Preface » Acknowledgments","id":"10","title":"Acknowledgments"},"11":{"body":"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. Lindgren, P., Fresk, E., Lindner, M., Lindner, A., Pereira, D., & Pinho, L. M. (2016). Abstract timers and their implementation onto the arm cortex-m family of mcus. ACM SIGBED Review, 13(1), 48-53.","breadcrumbs":"Preface » References","id":"11","title":"References"},"12":{"body":"All source code (including code snippets) is licensed under either of Apache License, Version 2.0 ( LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0 ) MIT license ( LICENSE-MIT or https://opensource.org/licenses/MIT ) at your option. The written prose contained within the book is licensed under the terms of the Creative Commons CC-BY-SA v4.0 license ( LICENSE-CC-BY-SA or https://creativecommons.org/licenses/by-sa/4.0/legalcode ).","breadcrumbs":"Preface » License","id":"12","title":"License"},"13":{"body":"Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.","breadcrumbs":"Preface » Contribution","id":"13","title":"Contribution"},"14":{"body":"This part of the book introduces the Real-Time Interrupt-driven Concurrency (RTIC) framework to new users by walking them through examples of increasing complexity. All examples in this part of the book are accessible at the GitHub repository . The examples are runnable on QEMU (emulating a Cortex M3 target), thus no special hardware required to follow along. To run the examples with QEMU you will need the qemu-system-arm program. Check the embedded Rust book for instructions on how to set up an embedded development environment that includes QEMU. To run the examples found in examples/ locally, cargo needs a supported target and either --examples (run all examples) or --example NAME to run a specific example. Assuming dependencies in place, running: $ cargo run --target thumbv7m-none-eabi --example locals Yields this output: foo: local_to_foo = 1\nbar: local_to_bar = 1\nidle: local_to_idle = 1 NOTE : You can choose target device by passing a target triple to cargo (e.g. cargo run --example init --target thumbv7m-none-eabi) or configure a default target in .cargo/config.toml. For running the examples, we use a Cortex M3 emulated in QEMU, so the target is thumbv7m-none-eabi.","breadcrumbs":"RTIC by example » RTIC by example","id":"14","title":"RTIC by example"},"15":{"body":"","breadcrumbs":"RTIC by example » The app » The #[app] attribute and an RTIC application","id":"15","title":"The #[app] attribute and an RTIC application"},"16":{"body":"All RTIC applications use the app attribute (#[app(..)]). This attribute only applies to a mod-item containing the RTIC application. The app attribute has a mandatory device argument that takes a path as a value. This must be a full path pointing to a peripheral access crate (PAC) generated using svd2rust v0.14.x or newer. The app attribute will expand into a suitable entry point and thus replaces the use of the cortex_m_rt::entry attribute.","breadcrumbs":"RTIC by example » The app » Requirements on the app attribute","id":"16","title":"Requirements on the app attribute"},"17":{"body":"To give a flavour of RTIC, the following example contains commonly used features. In the following sections we will go through each feature in detail. //! examples/common.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; use systick_monotonic::*; // Implements the `Monotonic` trait // A monotonic timer to enable scheduling in RTIC #[monotonic(binds = SysTick, default = true)] type MyMono = Systick<100>; // 100 Hz / 10 ms granularity // Resources shared between tasks #[shared] struct Shared { s1: u32, s2: i32, } // Local resources to specific tasks (cannot be shared) #[local] struct Local { l1: u8, l2: i8, } #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { let systick = cx.core.SYST; // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); // Spawn the task `foo` directly after `init` finishes foo::spawn().unwrap(); // Spawn the task `bar` 1 second after `init` finishes, this is enabled // by the `#[monotonic(..)]` above bar::spawn_after(1.secs()).unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator ( // Initialization of shared resources Shared { s1: 0, s2: 1 }, // Initialization of task local resources Local { l1: 2, l2: 3 }, // Move the monotonic timer to the RTIC run-time, this enables // scheduling init::Monotonics(mono), ) } // Background task, runs whenever no other tasks are running #[idle] fn idle(_: idle::Context) -> ! { loop { continue; } } // Software task, not bound to a hardware interrupt. // This task takes the task local resource `l1` // The resources `s1` and `s2` are shared between all other tasks. #[task(shared = [s1, s2], local = [l1])] fn foo(_: foo::Context) { // This task is only spawned once in `init`, hence this task will run // only once hprintln!(\"foo\"); } // Software task, also not bound to a hardware interrupt // This task takes the task local resource `l2` // The resources `s1` and `s2` are shared between all other tasks. #[task(shared = [s1, s2], local = [l2])] fn bar(_: bar::Context) { hprintln!(\"bar\"); // Run `bar` once per second bar::spawn_after(1.secs()).unwrap(); } // Hardware task, bound to a hardware interrupt // The resources `s1` and `s2` are shared between all other tasks. #[task(binds = UART0, priority = 3, shared = [s1, s2])] fn uart0_interrupt(_: uart0_interrupt::Context) { // This task is bound to the interrupt `UART0` and will run // whenever the interrupt fires // Note that RTIC does NOT clear the interrupt flag, this is up to the // user hprintln!(\"UART0 interrupt!\"); }\n}","breadcrumbs":"RTIC by example » The app » An RTIC application example","id":"17","title":"An RTIC application example"},"18":{"body":"The RTIC framework manages shared and task local resources allowing persistent data storage and safe accesses without the use of unsafe code. RTIC resources are visible only to functions declared within the #[app] module and the framework gives the user complete control (on a per-task basis) over resource accessibility. Declaration of system-wide resources is done by annotating two structs within the #[app] module with the attribute #[local] and #[shared]. Each field in these structures corresponds to a different resource (identified by field name). The difference between these two sets of resources will be covered below. Each task must declare the resources it intends to access in its corresponding metadata attribute using the local and shared arguments. Each argument takes a list of resource identifiers. The listed resources are made available to the context under the local and shared fields of the Context structure. The init task returns the initial values for the system-wide (#[shared] and #[local]) resources, and the set of initialized timers used by the application. The monotonic timers will be further discussed in Monotonic & spawn_{at/after} .","breadcrumbs":"RTIC by example » Resources » Resource usage","id":"18","title":"Resource usage"},"19":{"body":"#[local] resources are locally accessible to a specific task, meaning that only that task can access the resource and does so without locks or critical sections. This allows for the resources, commonly drivers or large objects, to be initialized in #[init] and then be passed to a specific task. Thus, a task #[local] resource can only be accessed by one singular task. Attempting to assign the same #[local] resource to more than one task is a compile-time error. Types of #[local] resources must implement a Send trait as they are being sent from init to a target task, crossing a thread boundary. The example application shown below contains two tasks where each task has access to its own #[local] resource; the idle task has its own #[local] as well. //! examples/locals.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared {} #[local] struct Local { /// Local foo local_to_foo: i64, /// Local bar local_to_bar: i64, /// Local idle local_to_idle: i64, } // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { foo::spawn().unwrap(); bar::spawn().unwrap(); ( Shared {}, // initial values for the `#[local]` resources Local { local_to_foo: 0, local_to_bar: 0, local_to_idle: 0, }, init::Monotonics(), ) } // `local_to_idle` can only be accessed from this context #[idle(local = [local_to_idle])] fn idle(cx: idle::Context) -> ! { let local_to_idle = cx.local.local_to_idle; *local_to_idle += 1; hprintln!(\"idle: local_to_idle = {}\", local_to_idle); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator // error: no `local_to_foo` field in `idle::LocalResources` // _cx.local.local_to_foo += 1; // error: no `local_to_bar` field in `idle::LocalResources` // _cx.local.local_to_bar += 1; loop { cortex_m::asm::nop(); } } // `local_to_foo` can only be accessed from this context #[task(local = [local_to_foo])] fn foo(cx: foo::Context) { let local_to_foo = cx.local.local_to_foo; *local_to_foo += 1; // error: no `local_to_bar` field in `foo::LocalResources` // cx.local.local_to_bar += 1; hprintln!(\"foo: local_to_foo = {}\", local_to_foo); } // `local_to_bar` can only be accessed from this context #[task(local = [local_to_bar])] fn bar(cx: bar::Context) { let local_to_bar = cx.local.local_to_bar; *local_to_bar += 1; // error: no `local_to_foo` field in `bar::LocalResources` // cx.local.local_to_foo += 1; hprintln!(\"bar: local_to_bar = {}\", local_to_bar); }\n} Running the example: $ cargo run --target thumbv7m-none-eabi --example locals\nfoo: local_to_foo = 1\nbar: local_to_bar = 1\nidle: local_to_idle = 1 Local resources in #[init] and #[idle] have 'static lifetimes. This is safe since both tasks are not re-entrant.","breadcrumbs":"RTIC by example » Resources » #[local] resources","id":"19","title":"#[local] resources"},"2":{"body":"Tasks as the unit of concurrency [1] . Tasks can be event triggered (fired in response to asynchronous stimuli) or spawned by the application on demand. Message passing between tasks. Specifically, messages can be passed to software tasks at spawn time. A timer queue [2] . Software tasks can be scheduled to run at some time in the future. This feature can be used to implement periodic tasks. Support for prioritization of tasks and, thus, preemptive multitasking . Efficient and data race free memory sharing through fine grained priority based critical sections [1] . Deadlock free execution guaranteed at compile time. This is a stronger guarantee than what's provided by the standard Mutex abstraction . Minimal scheduling overhead . The task scheduler has minimal software footprint; the hardware does the bulk of 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-M devices are fully supported . This task model is amenable to known WCET (Worst Case Execution Time) analysis and scheduling analysis techniques.","breadcrumbs":"Preface » Features","id":"2","title":"Features"},"20":{"body":"Local resources can also be specified directly in the resource claim like so: #[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]; this allows for creating locals which do no need to be initialized in #[init]. Types of #[task(local = [..])] resources have to be neither Send nor Sync as they are not crossing any thread boundary. In the example below the different uses and lifetimes are shown: //! examples/declared_locals.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [UART0])]\nmod app { use cortex_m_semihosting::debug; #[shared] struct Shared {} #[local] struct Local {} #[init(local = [a: u32 = 0])] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { // Locals in `#[init]` have 'static lifetime let _a: &'static mut u32 = cx.local.a; debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator (Shared {}, Local {}, init::Monotonics()) } #[idle(local = [a: u32 = 0])] fn idle(cx: idle::Context) -> ! { // Locals in `#[idle]` have 'static lifetime let _a: &'static mut u32 = cx.local.a; loop {} } #[task(local = [a: u32 = 0])] fn foo(cx: foo::Context) { // Locals in `#[task]`s have a local lifetime let _a: &mut u32 = cx.local.a; // error: explicit lifetime required in the type of `cx` // let _a: &'static mut u32 = cx.local.a; }\n}","breadcrumbs":"RTIC by example » Resources » Task local initialized resources","id":"20","title":"Task local initialized resources"},"21":{"body":"Critical sections are required to access #[shared] resources in a data race-free manner and to achieve this the shared field of the passed Context implements the Mutex trait for each shared resource accessible to the task. This trait has only one method, lock , which runs its closure argument in a critical section. The critical section created by the lock API is based on dynamic priorities: it temporarily raises the dynamic priority of the context to a ceiling priority that prevents other tasks from preempting the critical section. This synchronization protocol is known as the Immediate Ceiling Priority Protocol (ICPP) , and complies with Stack Resource Policy (SRP) based scheduling of RTIC. In the example below we have three interrupt handlers with priorities ranging from one to three. The two handlers with the lower priorities contend for a shared resource and need to succeed in locking the resource in order to access its data. The highest priority handler, which does not access the shared resource, is free to preempt a critical section created by the lowest priority handler. //! examples/lock.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [GPIOA, GPIOB, GPIOC])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared { shared: u32, } #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { foo::spawn().unwrap(); (Shared { shared: 0 }, Local {}, init::Monotonics()) } // when omitted priority is assumed to be `1` #[task(shared = [shared])] fn foo(mut c: foo::Context) { hprintln!(\"A\"); // the lower priority task requires a critical section to access the data c.shared.shared.lock(|shared| { // data can only be modified within this critical section (closure) *shared += 1; // bar will *not* run right now due to the critical section bar::spawn().unwrap(); hprintln!(\"B - shared = {}\", *shared); // baz does not contend for `shared` so it's allowed to run now baz::spawn().unwrap(); }); // critical section is over: bar can now start hprintln!(\"E\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2, shared = [shared])] fn bar(mut c: bar::Context) { // the higher priority task does still need a critical section let shared = c.shared.shared.lock(|shared| { *shared += 1; *shared }); hprintln!(\"D - shared = {}\", shared); } #[task(priority = 3)] fn baz(_: baz::Context) { hprintln!(\"C\"); }\n} $ cargo run --target thumbv7m-none-eabi --example lock\nA\nB - shared = 1\nC\nD - shared = 2\nE Types of #[shared] resources have to be Send .","breadcrumbs":"RTIC by example » Resources » #[shared] resources and lock","id":"21","title":"#[shared] resources and lock"},"22":{"body":"As an extension to lock, and to reduce rightward drift, locks can be taken as tuples. The following examples show this in use: //! examples/mutlilock.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [GPIOA])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared { shared1: u32, shared2: u32, shared3: u32, } #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { locks::spawn().unwrap(); ( Shared { shared1: 0, shared2: 0, shared3: 0, }, Local {}, init::Monotonics(), ) } // when omitted priority is assumed to be `1` #[task(shared = [shared1, shared2, shared3])] fn locks(c: locks::Context) { let s1 = c.shared.shared1; let s2 = c.shared.shared2; let s3 = c.shared.shared3; (s1, s2, s3).lock(|s1, s2, s3| { *s1 += 1; *s2 += 1; *s3 += 1; hprintln!(\"Multiple locks, s1: {}, s2: {}, s3: {}\", *s1, *s2, *s3); }); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator }\n} $ cargo run --target thumbv7m-none-eabi --example multilock\nMultiple locks, s1: 1, s2: 1, s3: 1","breadcrumbs":"RTIC by example » Resources » Multi-lock","id":"22","title":"Multi-lock"},"23":{"body":"By default, the framework assumes that all tasks require exclusive access (&mut-) to resources, but it is possible to specify that a task only requires shared access (&-) to a resource using the &resource_name syntax in the shared list. The advantage of specifying shared access (&-) to a resource is that no locks are required to access the resource even if the resource is contended by more than one task running at different priorities. The downside is that the task only gets a shared reference (&-) to the resource, limiting the operations it can perform on it, but where a shared reference is enough this approach reduces the number of required locks. In addition to simple immutable data, this shared access can be useful where the resource type safely implements interior mutability, with appropriate locking or atomic operations of its own. Note that in this release of RTIC it is not possible to request both exclusive access (&mut-) and shared access (&-) to the same resource from different tasks. Attempting to do so will result in a compile error. In the example below a key (e.g. a cryptographic key) is loaded (or created) at runtime and then used from two tasks that run at different priorities without any kind of lock. //! examples/only-shared-access.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared { key: u32, } #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { foo::spawn().unwrap(); bar::spawn().unwrap(); (Shared { key: 0xdeadbeef }, Local {}, init::Monotonics()) } #[task(shared = [&key])] fn foo(cx: foo::Context) { let key: &u32 = cx.shared.key; hprintln!(\"foo(key = {:#x})\", key); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2, shared = [&key])] fn bar(cx: bar::Context) { hprintln!(\"bar(key = {:#x})\", cx.shared.key); }\n} $ cargo run --target thumbv7m-none-eabi --example only-shared-access\nbar(key = 0xdeadbeef)\nfoo(key = 0xdeadbeef)","breadcrumbs":"RTIC by example » Resources » Only shared (&-) access","id":"23","title":"Only shared (&-) access"},"24":{"body":"A critical section is not required to access a #[shared] resource that's only accessed by tasks running at the same priority. In this case, you can opt out of the lock API by adding the #[lock_free] field-level attribute to the resource declaration (see example below). Note that this is merely a convenience to reduce needless resource locking code, because even if the lock API is used, at runtime the framework will not produce a critical section due to how the underlying resource-ceiling preemption works. Also worth noting: using #[lock_free] on resources shared by tasks running at different priorities will result in a compile-time error -- not using the lock API would be a data race in that case. //! examples/lock-free.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [GPIOA])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared { #[lock_free] // <- lock-free shared resource counter: u64, } #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { foo::spawn().unwrap(); (Shared { counter: 0 }, Local {}, init::Monotonics()) } #[task(shared = [counter])] // <- same priority fn foo(c: foo::Context) { bar::spawn().unwrap(); *c.shared.counter += 1; // <- no lock API required let counter = *c.shared.counter; hprintln!(\" foo = {}\", counter); } #[task(shared = [counter])] // <- same priority fn bar(c: bar::Context) { foo::spawn().unwrap(); *c.shared.counter += 1; // <- no lock API required let counter = *c.shared.counter; hprintln!(\" bar = {}\", counter); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator }\n} $ cargo run --target thumbv7m-none-eabi --example lock-free foo = 1 bar = 2","breadcrumbs":"RTIC by example » Resources » Lock-free resource access of shared resources","id":"24","title":"Lock-free resource access of shared resources"},"25":{"body":"An RTIC application requires an init task setting up the system. The corresponding init function must have the signature fn(init::Context) -> (Shared, Local, init::Monotonics), where Shared and Local are resource structures defined by the user. The init task executes after system reset, after an optionally defined pre-init code section and an always occurring internal RTIC initialization. The init and optional pre-init tasks runs with interrupts disabled and have exclusive access to Cortex-M (the bare_metal::CriticalSection token is available as cs). Device specific peripherals are available through the core and device fields of init::Context.","breadcrumbs":"RTIC by example » The init task » App initialization and the #[init] task","id":"25","title":"App initialization and the #[init] task"},"26":{"body":"The example below shows the types of the core, device and cs fields, and showcases the use of a local variable with 'static lifetime. Such variables can be delegated from the init task to other tasks of the RTIC application. The device field is only available when the peripherals argument is set to the default value true. In the rare case you want to implement an ultra-slim application you can explicitly set peripherals to false. //! examples/init.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, peripherals = true)]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared {} #[local] struct Local {} #[init(local = [x: u32 = 0])] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { // Cortex-M peripherals let _core: cortex_m::Peripherals = cx.core; // Device specific peripherals let _device: lm3s6965::Peripherals = cx.device; // Locals in `init` have 'static lifetime let _x: &'static mut u32 = cx.local.x; // Access to the critical section token, // to indicate that this is a critical seciton let _cs_token: bare_metal::CriticalSection = cx.cs; hprintln!(\"init\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator (Shared {}, Local {}, init::Monotonics()) }\n} Running the example will print init to the console and then exit the QEMU process. $ cargo run --target thumbv7m-none-eabi --example init\ninit","breadcrumbs":"RTIC by example » The init task » Example","id":"26","title":"Example"},"27":{"body":"A function marked with the idle attribute can optionally appear in the module. This becomes the special idle task and must have signature fn(idle::Context) -> !. When present, the runtime will execute the idle task after init. Unlike init, idle will run with interrupts enabled and must never return, as the -> ! function signature indicates. The Rust type ! means “never” . Like in init, locally declared resources will have 'static lifetimes that are safe to access. The example below shows that idle runs after init. //! examples/idle.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965)]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { hprintln!(\"init\"); (Shared {}, Local {}, init::Monotonics()) } #[idle(local = [x: u32 = 0])] fn idle(cx: idle::Context) -> ! { // Locals in idle have lifetime 'static let _x: &'static mut u32 = cx.local.x; hprintln!(\"idle\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator loop { cortex_m::asm::nop(); } }\n} $ cargo run --target thumbv7m-none-eabi --example idle\ninit\nidle By default, the RTIC idle task does not try to optimize for any specific targets. A common useful optimization is to enable the SLEEPONEXIT and allow the MCU to enter sleep when reaching idle. Caution some hardware unless configured disables the debug unit during sleep mode. Consult your hardware specific documentation as this is outside the scope of RTIC. The following example shows how to enable sleep by setting the SLEEPONEXIT and providing a custom idle task replacing the default nop() with wfi() . //! examples/idle-wfi.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965)]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(mut cx: init::Context) -> (Shared, Local, init::Monotonics) { hprintln!(\"init\"); // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit cx.core.SCB.set_sleepdeep(); (Shared {}, Local {}, init::Monotonics()) } #[idle(local = [x: u32 = 0])] fn idle(cx: idle::Context) -> ! { // Locals in idle have lifetime 'static let _x: &'static mut u32 = cx.local.x; hprintln!(\"idle\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator loop { // Now Wait For Interrupt is used instead of a busy-wait loop // to allow MCU to sleep between interrupts // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/WFI rtic::export::wfi() } }\n} $ cargo run --target thumbv7m-none-eabi --example idle-wfi\ninit\nidle","breadcrumbs":"RTIC by example » The idle task » The background task #[idle]","id":"27","title":"The background task #[idle]"},"28":{"body":"Tasks, defined with #[task], are the main mechanism of getting work done in RTIC. Tasks can Be spawned (now or in the future, also by themselves) Receive messages (passing messages between tasks) Be prioritized, allowing preemptive multitasking Optionally bind to a hardware interrupt RTIC makes a distinction between “software tasks” and “hardware tasks”. Hardware tasks are tasks that are bound to a specific interrupt vector in the MCU while software tasks are not. This means that if a hardware task is bound to, lets say, a UART RX interrupt, the task will be run every time that interrupt triggers, usually when a character is received. Software tasks are explicitly spawned in a task, either immediately or using the Monotonic timer mechanism. In the coming pages we will explore both tasks and the different options available.","breadcrumbs":"RTIC by example » Defining tasks » Defining tasks with #[task]","id":"28","title":"Defining tasks with #[task]"},"29":{"body":"At its core RTIC is using a hardware interrupt controller ( ARM NVIC on cortex-m ) to schedule and start execution of tasks. All tasks except pre-init, #[init] and #[idle] run as interrupt handlers. Hardware tasks are explicitly bound to interrupt handlers. To bind a task to an interrupt, use the #[task] attribute argument binds = InterruptName. This task then becomes the interrupt handler for this hardware interrupt vector. All tasks bound to an explicit interrupt are called hardware tasks since they start execution in reaction to a hardware event. Specifying a non-existing interrupt name will cause a compilation error. The interrupt names are commonly defined by PAC or HAL crates. Any available interrupt vector should work. Specific devices may bind specific interrupt priorities to specific interrupt vectors outside user code control. See for example the nRF “softdevice” . Beware of using interrupt vectors that are used internally by hardware features; RTIC is unaware of such hardware specific details. The example below demonstrates the use of the #[task(binds = InterruptName)] attribute to declare a hardware task bound to an interrupt handler. //! examples/hardware.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965)]\nmod app { use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { // Pends the UART0 interrupt but its handler won't run until *after* // `init` returns because interrupts are disabled rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend hprintln!(\"init\"); (Shared {}, Local {}, init::Monotonics()) } #[idle] fn idle(_: idle::Context) -> ! { // interrupts are enabled again; the `UART0` handler runs at this point hprintln!(\"idle\"); rtic::pend(Interrupt::UART0); loop { // Exit moved after nop to ensure that rtic::pend gets // to run before exiting cortex_m::asm::nop(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } #[task(binds = UART0, local = [times: u32 = 0])] fn uart0(cx: uart0::Context) { // Safe access to local `static mut` variable *cx.local.times += 1; hprintln!( \"UART0 called {} time{}\", *cx.local.times, if *cx.local.times > 1 { \"s\" } else { \"\" } ); }\n} $ cargo run --target thumbv7m-none-eabi --example hardware\ninit\nUART0 called 1 time\nidle\nUART0 called 2 times","breadcrumbs":"RTIC by example » Defining tasks » Hardware tasks » Hardware tasks","id":"29","title":"Hardware tasks"},"3":{"body":"The crate cortex-m 0.7 started using trait InterruptNumber for interrupts instead of Nr from bare-metal. In order to preserve backwards compatibility, RTIC 0.5.x will keep using cortex-m 0.6 by default. cortex-m 0.7 can be enabled using the feature cortex-m-7 and disabling default features: cortex-m-rtic = { version = \"0.5.8\", default-features = false, features = [\"cortex-m-7\"] } RTIC 1.0.0 already uses cortex-m 0.7 by default.","breadcrumbs":"Preface » Crate cortex-m 0.6 vs 0.7 in RTIC 0.5.x","id":"3","title":"Crate cortex-m 0.6 vs 0.7 in RTIC 0.5.x"},"30":{"body":"The RTIC concept of a software task shares a lot with that of hardware tasks with the core difference that a software task is not explicitly bound to a specific interrupt vector, but rather bound to a “dispatcher” interrupt vector running at the intended priority of the software task (see below). Thus, software tasks are tasks which are not directly bound to an interrupt vector. The #[task] attributes used on a function determine if it is software tasks, specifically the absence of a binds = InterruptName argument to the attribute definition. The static method task_name::spawn() spawns (schedules) a software task by registering it with a specific dispatcher. If there are no higher priority tasks available to the scheduler (which serves a set of dispatchers), the task will start executing directly. All software tasks at the same priority level share an interrupt handler bound to their dispatcher. What differentiates software and hardware tasks is the usage of either a dispatcher or a bound interrupt vector. The interrupt vectors used as dispatchers cannot be used by hardware tasks. Availability of a set of “free” (not in use by hardware tasks) and usable interrupt vectors allows the framework to dispatch software tasks via dedicated interrupt handlers. This set of dispatchers, dispatchers = [FreeInterrupt1, FreeInterrupt2, ...] is an argument to the #[app] attribute. Each interrupt vector acting as dispatcher gets assigned to a unique priority level meaning that the list of dispatchers needs to cover all priority levels used by software tasks. Example: The dispatchers = argument needs to have at least 3 entries for an application using three different priorities for software tasks. The framework will give a compilation error if there are not enough dispatchers provided. See the following example: //! examples/spawn.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { hprintln!(\"init\"); foo::spawn().unwrap(); (Shared {}, Local {}, init::Monotonics()) } #[task] fn foo(_: foo::Context) { hprintln!(\"foo\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator }\n} $ cargo run --target thumbv7m-none-eabi --example spawn\ninit\nfoo","breadcrumbs":"RTIC by example » Defining tasks » Software tasks & spawn » Software tasks & spawn","id":"30","title":"Software tasks & spawn"},"31":{"body":"Software tasks support message passing, this means that software tasks can be spawned with an argument: foo::spawn(1) which will run the task foo with the argument 1. Capacity sets the size of the spawn queue for the task, if not specified capacity defaults to 1. In the example below, the capacity of task foo is 3, allowing three simultaneous pending spawns of foo. Exceeding this capacity is an Error. The number of arguments to a task is not limited: //! examples/message_passing.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { foo::spawn(1, 1).unwrap(); foo::spawn(1, 2).unwrap(); foo::spawn(2, 3).unwrap(); assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached (Shared {}, Local {}, init::Monotonics()) } #[task(capacity = 3)] fn foo(_c: foo::Context, x: i32, y: u32) { hprintln!(\"foo {}, {}\", x, y); if x == 2 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } }\n} $ cargo run --target thumbv7m-none-eabi --example message_passing\nfoo 1, 1\nfoo 1, 2\nfoo 2, 3","breadcrumbs":"RTIC by example » Defining tasks » Message passing & capacity » Message passing & capacity","id":"31","title":"Message passing & capacity"},"32":{"body":"","breadcrumbs":"RTIC by example » Defining tasks » Task priorities » Task priorities","id":"32","title":"Task priorities"},"33":{"body":"The priority argument declares the static priority of each task. For Cortex-M, tasks can have priorities in the range 1..=(1 << NVIC_PRIO_BITS) where NVIC_PRIO_BITS is a constant defined in the device crate. Omitting the priority argument the task priority defaults to 1. The idle task has a non-configurable static priority of 0, the lowest priority. A higher number means a higher priority in RTIC, which is the opposite from what Cortex-M does in the NVIC peripheral. Explicitly, this means that number 10 has a higher priority than number 9. The highest static priority task takes precedence when more than one task are ready to execute. The following scenario demonstrates task prioritization: Spawning a higher priority task A during execution of a lower priority task B suspends task B. Task A has higher priority thus preempting task B which gets suspended until task A completes execution. Thus, when task A completes task B resumes execution. Task Priority ┌────────────────────────────────────────────────────────┐ │ │ │ │\n3 │ Preempts │\n2 │ A─────────► │\n1 │ B─────────► - - - - B────────► │\n0 │Idle┌─────► Resumes ┌──────────► │ ├────┴──────────────────────────────────┴────────────────┤ │ │ └────────────────────────────────────────────────────────┘Time The following example showcases the priority based scheduling of tasks: //! examples/preempt.rs #![no_main]\n#![no_std] use panic_semihosting as _;\nuse rtic::app; #[app(device = lm3s6965, dispatchers = [SSI0, QEI0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { foo::spawn().unwrap(); (Shared {}, Local {}, init::Monotonics()) } #[task(priority = 1)] fn foo(_: foo::Context) { hprintln!(\"foo - start\"); baz::spawn().unwrap(); hprintln!(\"foo - end\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] fn bar(_: bar::Context) { hprintln!(\" bar\"); } #[task(priority = 2)] fn baz(_: baz::Context) { hprintln!(\" baz - start\"); bar::spawn().unwrap(); hprintln!(\" baz - end\"); }\n} $ cargo run --target thumbv7m-none-eabi --example preempt\nfoo - start baz - start baz - end bar\nfoo - end Note that the task bar does not preempt task baz because its priority is the same as baz's. The higher priority task bar runs before foo when bazreturns. When bar returns foo can resume. One more note about priorities: choosing a priority higher than what the device supports will result in a compilation error. The error is cryptic due to limitations in the Rust language if priority = 9 for task uart0_interrupt in example/common.rs this looks like: error[E0080]: evaluation of constant value failed --> examples/common.rs:10:1 |\n10 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `8_usize - 9_usize`, which would overflow | = note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info) The error message incorrectly points to the starting point of the macro, but at least the value subtracted (in this case 9) will suggest which task causes the error.","breadcrumbs":"RTIC by example » Defining tasks » Task priorities » Priorities","id":"33","title":"Priorities"},"34":{"body":"The understanding of time is an important concept in embedded systems, and to be able to run tasks based on time is essential. The framework provides the static methods task::spawn_after(/* duration */) and task::spawn_at(/* specific time instant */). spawn_after is more commonly used, but in cases where it's needed to have spawns happen without drift or to a fixed baseline spawn_at is available. The #[monotonic] attribute, applied to a type alias definition, exists to support this. This type alias must point to a type which implements the rtic_monotonic::Monotonic trait. This is generally some timer which handles the timing of the system. One or more monotonics can coexist in the same system, for example a slow timer that wakes the system from sleep and another which purpose is for fine grained scheduling while the system is awake. The attribute has one required parameter and two optional parameters, binds, default and priority respectively. The required parameter, binds = InterruptName, associates an interrupt vector to the timer's interrupt, while default = true enables a shorthand API when spawning and accessing time (monotonics::now() vs monotonics::MyMono::now()), and priority sets the priority of the interrupt vector. The default priority is the maximum priority of the system. If your system has a high priority task with tight scheduling requirements, it might be desirable to demote the monotonic task to a lower priority to reduce scheduling jitter for the high priority task. This however might introduce jitter and delays into scheduling via the monotonic, making it a trade-off. The monotonics are initialized in #[init] and returned within the init::Monotonic( ... ) tuple. This activates the monotonics making it possible to use them. See the following example: //! examples/schedule.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; use systick_monotonic::*; #[monotonic(binds = SysTick, default = true)] type MyMono = Systick<100>; // 100 Hz / 10 ms granularity #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { let systick = cx.core.SYST; // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); hprintln!(\"init\"); // Schedule `foo` to run 1 second in the future foo::spawn_after(1.secs()).unwrap(); ( Shared {}, Local {}, init::Monotonics(mono), // Give the monotonic to RTIC ) } #[task] fn foo(_: foo::Context) { hprintln!(\"foo\"); // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) bar::spawn_after(1.secs()).unwrap(); } #[task] fn bar(_: bar::Context) { hprintln!(\"bar\"); // Schedule `baz` to run 1 seconds from now, but with a specific time instant. baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); } #[task] fn baz(_: baz::Context) { hprintln!(\"baz\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator }\n} $ cargo run --target thumbv7m-none-eabi --example schedule\ninit\nfoo\nbar\nbaz A key requirement of a Monotonic is that it must deal gracefully with hardware timer overruns.","breadcrumbs":"RTIC by example » Defining tasks » Monotonic & spawn_{at/after} » Monotonic & spawn_","id":"34","title":"Monotonic & spawn_"},"35":{"body":"Tasks spawned using task::spawn_after and task::spawn_at returns a SpawnHandle, which allows canceling or rescheduling of the task scheduled to run in the future. If cancel or reschedule_at/reschedule_after returns an Err it means that the operation was too late and that the task is already sent for execution. The following example shows this in action: //! examples/cancel-reschedule.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; use systick_monotonic::*; #[monotonic(binds = SysTick, default = true)] type MyMono = Systick<100>; // 100 Hz / 10 ms granularity #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { let systick = cx.core.SYST; // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); hprintln!(\"init\"); // Schedule `foo` to run 1 second in the future foo::spawn_after(1.secs()).unwrap(); ( Shared {}, Local {}, init::Monotonics(mono), // Give the monotonic to RTIC ) } #[task] fn foo(_: foo::Context) { hprintln!(\"foo\"); // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) let spawn_handle = baz::spawn_after(2.secs()).unwrap(); bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true } #[task] fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { hprintln!(\"bar\"); if do_reschedule { // Reschedule baz 2 seconds from now, instead of the original 1 second // from now. baz_handle.reschedule_after(2.secs()).unwrap(); // Or baz_handle.reschedule_at(/* time */) } else { // Or cancel it baz_handle.cancel().unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } #[task] fn baz(_: baz::Context) { hprintln!(\"baz\"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator }\n} $ cargo run --target thumbv7m-none-eabi --example cancel-reschedule\ninit\nfoo\nbar","breadcrumbs":"RTIC by example » Defining tasks » Monotonic & spawn_{at/after} » Canceling or rescheduling a scheduled task","id":"35","title":"Canceling or rescheduling a scheduled task"},"36":{"body":"A recommendation when starting a RTIC project from scratch is to follow RTIC's defmt-app-template . If you are targeting ARMv6-M or ARMv8-M-base architecture, check out the section Target Architecture for more information on hardware limitations to be aware of. This will give you an RTIC application with support for RTT logging with defmt and stack overflow protection using flip-link . There is also a multitude of examples provided by the community: rtic-examples - Multiple projects https://github.com/kalkyl/f411-rtic ... More to come","breadcrumbs":"RTIC by example » Starting a new project » Starting a new project","id":"36","title":"Starting a new project"},"37":{"body":"This is the smallest possible RTIC application: //! examples/smallest.rs #![no_main]\n#![no_std] use panic_semihosting as _; // panic handler\nuse rtic::app; #[app(device = lm3s6965)]\nmod app { use cortex_m_semihosting::debug; #[shared] struct Shared {} #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator (Shared {}, Local {}, init::Monotonics()) }\n}","breadcrumbs":"RTIC by example » The minimal app » The minimal app","id":"37","title":"The minimal app"},"38":{"body":"In this section we will explore common tips & tricks related to using RTIC.","breadcrumbs":"RTIC by example » Tips & Tricks » Tips & tricks","id":"38","title":"Tips & tricks"},"39":{"body":"The framework is flexible because it can use any timer which has compare-match and optionally supporting overflow interrupts for scheduling. The single requirement to make a timer usable with RTIC is implementing the rtic_monotonic::Monotonic trait. Implementing time counting that supports large time spans is generally difficult , in RTIC 0.5 implementing time handling was a common problem. Moreover, the relation between time and timers used for scheduling was difficult to understand. For RTIC 1.0 we instead assume the user has a time library, e.g. fugit or embedded_time , as the basis for all time-based operations when implementing Monotonic. These libraries make it much easier to correctly implement the Monotonic trait, allowing the use of almost any timer in the system for scheduling. The trait documents the requirements for each method, and for inspiration here is a list of Monotonic implementations: STM32F411 series , implemented for the 32-bit timers Nordic nRF52 series Timer , implemented for the 32-bit timers Nordic nRF52 series RTC , implemented for the RTCs Systick based , runs at a fixed interrupt (tick) rate - with some overhead but simple and with support for large time spans DWT and Systick based , a more efficient (tickless) implementation - requires both SysTick and DWT, supports both high resolution and large time spans If you know of more implementations feel free to add them to this list.","breadcrumbs":"RTIC by example » Tips & Tricks » Implementing Monotonic » Implementing a Monotonic timer for scheduling","id":"39","title":"Implementing a Monotonic timer for scheduling"},"4":{"body":"Documentation for the development version .","breadcrumbs":"Preface » User documentation","id":"4","title":"User documentation"},"40":{"body":"Destructuring task resources might help readability if a task takes multiple resources. Here are two examples on how to split up the resource struct: //! examples/destructure.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [UART0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared { a: u32, b: u32, c: u32, } #[local] struct Local {} #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { foo::spawn().unwrap(); bar::spawn().unwrap(); (Shared { a: 0, b: 0, c: 0 }, Local {}, init::Monotonics()) } #[idle] fn idle(_: idle::Context) -> ! { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator loop {} } // Direct destructure #[task(shared = [&a, &b, &c])] fn foo(cx: foo::Context) { let a = cx.shared.a; let b = cx.shared.b; let c = cx.shared.c; hprintln!(\"foo: a = {}, b = {}, c = {}\", a, b, c); } // De-structure-ing syntax #[task(shared = [&a, &b, &c])] fn bar(cx: bar::Context) { let bar::SharedResources { a, b, c } = cx.shared; hprintln!(\"bar: a = {}, b = {}, c = {}\", a, b, c); }\n} $ cargo run --target thumbv7m-none-eabi --example destructure\nfoo: a = 0, b = 0, c = 0\nbar: a = 0, b = 0, c = 0","breadcrumbs":"RTIC by example » Tips & Tricks » Resource de-structure-ing » Resource de-structure-ing","id":"40","title":"Resource de-structure-ing"},"41":{"body":"Message passing always involves copying the payload from the sender into a static variable and then from the static variable into the receiver. Thus sending a large buffer, like a [u8; 128], as a message involves two expensive memcpys. Indirection can minimize message passing overhead: instead of sending the buffer by value, one can send an owning pointer into the buffer. One can use a global memory allocator to achieve indirection (alloc::Box, alloc::Rc, etc.), which requires using the nightly channel as of Rust v1.37.0, or one can use a statically allocated memory pool like heapless::Pool . As this example of approach goes completely outside of RTIC resource model with shared and local the program would rely on the correctness of the memory allocator, in this case heapless::pool. Here's an example where heapless::Pool is used to \"box\" buffers of 128 bytes. //! examples/pool.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n// pool!() generates a struct without docs\n//#![deny(missing_docs)]\n#![no_main]\n#![no_std] use heapless::{ pool, pool::singleton::{Box, Pool},\n};\nuse panic_semihosting as _;\nuse rtic::app; // Declare a pool of 128-byte memory blocks\npool!(P: [u8; 128]); #[app(device = lm3s6965, dispatchers = [SSI0, QEI0])]\nmod app { use crate::{Box, Pool}; use cortex_m_semihosting::debug; use lm3s6965::Interrupt; // Import the memory pool into scope use super::P; #[shared] struct Shared {} #[local] struct Local {} #[init(local = [memory: [u8; 512] = [0; 512]])] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { // Increase the capacity of the memory pool by ~4 P::grow(cx.local.memory); rtic::pend(Interrupt::I2C0); (Shared {}, Local {}, init::Monotonics()) } #[task(binds = I2C0, priority = 2)] fn i2c0(_: i2c0::Context) { // claim a memory block, initialize it and .. let x = P::alloc().unwrap().init([0u8; 128]); // .. send it to the `foo` task foo::spawn(x).ok().unwrap(); // send another block to the task `bar` bar::spawn(P::alloc().unwrap().init([0u8; 128])) .ok() .unwrap(); } #[task] fn foo(_: foo::Context, _x: Box
) { // explicitly return the block to the pool drop(_x); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] fn bar(_: bar::Context, _x: Box
) { // this is done automatically so we can omit the call to `drop` // drop(x); }\n} $ cargo run --target thumbv7m-none-eabi --example pool","breadcrumbs":"RTIC by example » Tips & Tricks » Avoid copies when message passing » Using indirection for faster message passing","id":"41","title":"Using indirection for faster message passing"},"42":{"body":"In #[init] and #[idle] local resources have 'static lifetime. Useful when pre-allocating and/or splitting resources between tasks, drivers or some other object. This comes in handy when drivers, such as USB drivers, need to allocate memory and when using splittable data structures such as heapless::spsc::Queue . In the following example two different tasks share a heapless::spsc::Queue for lock-free access to the shared queue. //! examples/static.rs #![deny(unsafe_code)]\n#![deny(warnings)]\n#![deny(missing_docs)]\n#![no_main]\n#![no_std] use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [UART0])]\nmod app { use cortex_m_semihosting::{debug, hprintln}; use heapless::spsc::{Consumer, Producer, Queue}; #[shared] struct Shared {} #[local] struct Local { p: Producer<'static, u32, 5>, c: Consumer<'static, u32, 5>, } #[init(local = [q: Queue