The app
attribute
This is the smallest possible RTFM application:
# #![allow(unused_variables)] #fn main() { //! examples/smallest.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] // panic-handler crate extern crate panic_semihosting; use rtfm::app; #[app(device = lm3s6965)] const APP: () = { #[init] fn init(_: init::Context) {} }; #}
All RTFM applications use the app
attribute (#[app(..)]
). This attribute
must be applied to a const
item that contains items. The app
attribute has
a mandatory device
argument that takes a path as a value. This path must
point to a peripheral access crate (PAC) generated using svd2rust
v0.14.x. The app
attribute will expand into a suitable entry point so it's
not required to use the cortex_m_rt::entry
attribute.
ASIDE: Some of you may be wondering why we are using a
const
item as a module and not a propermod
item. The reason is that using attributes on modules requires a feature gate, which requires a nightly toolchain. To make RTFM work on stable we use theconst
item instead. When more parts of macros 1.2 are stabilized we'll move from aconst
item to amod
item and eventually to a crate level attribute (#![app]
).
init
Within the pseudo-module the app
attribute expects to find an initialization
function marked with the init
attribute. This function must have signature
fn(init::Context) [-> init::LateResources]
.
This initialization function will be the first part of the application to run.
The init
function will run with interrupts disabled and has exclusive access
to Cortex-M and device specific peripherals through the core
and device
variables fields of init::Context
. Not all Cortex-M peripherals are available
in core
because the RTFM runtime takes ownership of some of them -- for more
details see the rtfm::Peripherals
struct.
static mut
variables declared at the beginning of init
will be transformed
into &'static mut
references that are safe to access.
The example below shows the types of the core
and device
variables and
showcases safe access to a static mut
variable.
# #![allow(unused_variables)] #fn main() { //! examples/init.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; #[rtfm::app(device = lm3s6965)] const APP: () = { #[init] fn init(c: init::Context) { static mut X: u32 = 0; // Cortex-M peripherals let _core: rtfm::Peripherals = c.core; // Device specific peripherals let _device: lm3s6965::Peripherals = c.device; // Safe access to local `static mut` variable let _x: &'static mut u32 = X; hprintln!("init").unwrap(); debug::exit(debug::EXIT_SUCCESS); } }; #}
Running the example will print init
to the console and then exit the QEMU
process.
$ cargo run --example init
init
idle
A function marked with the idle
attribute can optionally appear in the
pseudo-module. This function is used as 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 it's not allowed to return
so it runs forever.
When no idle
function is declared, the runtime sets the SLEEPONEXIT bit and
then sends the microcontroller to sleep after running init
.
Like in init
, static mut
variables will be transformed into &'static mut
references that are safe to access.
The example below shows that idle
runs after init
.
# #![allow(unused_variables)] #fn main() { //! examples/idle.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; #[rtfm::app(device = lm3s6965)] const APP: () = { #[init] fn init(_: init::Context) { hprintln!("init").unwrap(); } #[idle] fn idle(_: idle::Context) -> ! { static mut X: u32 = 0; // Safe access to local `static mut` variable let _x: &'static mut u32 = X; hprintln!("idle").unwrap(); debug::exit(debug::EXIT_SUCCESS); loop {} } }; #}
$ cargo run --example idle
init
idle
interrupt
/ exception
Just like you would do with the cortex-m-rt
crate you can use the interrupt
and exception
attributes within the app
pseudo-module to declare interrupt
and exception handlers. In RTFM, we refer to interrupt and exception handlers as
hardware tasks.
# #![allow(unused_variables)] #fn main() { //! examples/interrupt.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] extern crate panic_semihosting; use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; #[rtfm::app(device = lm3s6965)] const APP: () = { #[init] fn init(_: init::Context) { // Pends the UART0 interrupt but its handler won't run until *after* // `init` returns because interrupts are disabled rtfm::pend(Interrupt::UART0); hprintln!("init").unwrap(); } #[idle] fn idle(_: idle::Context) -> ! { // interrupts are enabled again; the `UART0` handler runs at this point hprintln!("idle").unwrap(); rtfm::pend(Interrupt::UART0); debug::exit(debug::EXIT_SUCCESS); loop {} } #[interrupt] fn UART0(_: UART0::Context) { static mut TIMES: u32 = 0; // Safe access to local `static mut` variable *TIMES += 1; hprintln!( "UART0 called {} time{}", *TIMES, if *TIMES > 1 { "s" } else { "" } ) .unwrap(); } }; #}
$ cargo run --example interrupt
init
UART0 called 1 time
idle
UART0 called 2 times
So far all the RTFM applications we have seen look no different that the
applications one can write using only the cortex-m-rt
crate. In the next
section we start introducing features unique to RTFM.