A look into ways to implement and share data with interrupt handlers in Rust (Update 1)

In this blog entry I will explain a bit what interrupts are and they work in embedded systems and compare various interrupt implementation and sharing methods in Rust.

Update 2020-03-06: @jamesmunns has provided a new version of cmim which allows to use the SysTick exception and an adaption of the example for it so I updated this post accordingly.

Interrupt handling

Interrupt

An interrupt is a hardware method to react to special events recognized by the system to interrupt regular program flow and do some special handling of the event which caused the interrupt. This mechanism does not only exist for micro controllers but is also present on regular CPUs. On the latter it is used especially by external components, e.g. extension cards or connected external peripherals which can notify the system about events (e.g. data to be processed is available) by generating an interrupt signal. The other common case is that interrupts are generated internally in the processing core by configuration of certain trigger events, e.g. if some memory access violation happened, some timer expired or the transmit buffer is empty.

To enable the use of interrupts on a system there's a special hardware element called an interrupt controller which can be programmed to handle such special events happening in the system. Interrupts have a defined or configurable priority and whenever an interrupt is generated with a higher priority than what the current execution priority, then the ongoing execution flow will be suspended and execution continues at a special function called an interrupt handler...

Interrupt handler

An interrupt handler is a special function which is designed to handle one or multiple of the interrupt types an interrupt controller can support. During the execution of an interrupt handler the regular program execution is suspended and all processing resources are dedicated to the interrupt handler. Most interrupt controllers are so called nested interrupt controllers which means that the execution of an interrupt handler can also be suspended to work on an even higher priority interrupt, should one occur.

As you can imagine, in a system where multiple priority levels exist, this can be problematic. If a part of the program is working with a resource, gets suspended and then an interrupt handler takes over and works with the same resource, this can easily cause race conditions.

There're multiple ways around this and we'll talk about this later but for now I'll briefly mention the two simplest solution for single-core systems (multi-core systems can execute code in parallel which causes additional problems):

At the end of the interrupt handler, execution will continue at the last suspension point.

Resource sharing

There're essentially two pieces of resources you might you might want to "share" between interrupt handlers and regular code. It is essential to appropriately manage access from different parts of the program to them, especially for a language like Rust which takes safety very seriously.

Resource types

There're two important classes of data which we need to consider:

Data

Data can be any structure located at some address in random access memory. Those can be simply variables but also more complex structures like buffers where data piles up until it can be processed by a different piece of code.

Peripherals

In typical micro controllers all peripherals are accessible via memory mapped addresses which means that these magic addresses do interact directly with hardware when written to or read from.

Access management

There're 3 essential mechanisms to manage access to those resources:

Uniqueness

Every function, interrupt handler, memory address, etc. can only exist once. For the most part Rust already guarantees that this is the case, however it can not do this for raw pointers to memory addresses which is the reason why it is unsafe to dereference them into concrete types. However we do want to have access to those memory locations but still obtain unique handles to those resources which Rust will help us to deal with, so how do we do this?

The trick is that we're mapping those memory mapped addresses into concrete types which give us the required abstraction to safely use those and in addition we wrap them into a singleton so it's only possible to (safely) obtain the whole set of resources exactly once and let the Rust compiler handle most of that for us.

Sharing

Usually sharing resources in Rust is simple, you just pass borrows around and the Rust borrow checker will ensure that the lifetimes of the borrowed resources are not mixed up and that a mutable borrow and a regular borrow are mutually exclusive.

However things get more tricky if there're non-linear program flows involved as it is the case with threads and interrupt handlers. As soon as there's an external party involved which can interfere with program execution flow like a operating system scheduler or interrupt controller, all shared resources require special protection which know how to deal with a particular case. For threads we have the Sync trait to cover concurrent access however this assumes that there is an overseeing party which makes sure that some over-the-top guarantees are upheld, however there're cases in embedded where this cannot not work.

Moving

Similarly to sharing, moving now becomes a problem because the Rust compiler cannot track resources which are supposed to move into e.g. an interrupt handler. To the Rust compilers' eye those are not even part of the program itself but more a kind of standalone functions which are never even called and for some weird reason just happen to be defined in the same code base are the "real" program. Silly developers!

So special precautions need to be applied to a resource which is initialized in once part of the "real" program but then supposed to move to a different location which is not seen as part of the "real" program but a weird function which seemingly unused.

Implementation approaches

Over the last couple of years a few mechanisms have been developed and evolved to deal with the implementation and handling of the before mentioned shenanigans in a sane way.

For the rest of the post I will look at different approaches to handle interrupts, share and move resources. For this I've developed a simple example application for a Cortex-M0 MCU and we will look at the different implementations with the different approaches, compare the generated code sizes and I will give you my opinion about the pros and cons of the various methods.

So this is what the end result will have to look like:

All of the code can be found in the interrupt-comparison repo under the flashing workspace.

So these are the contenders:

Cortex-m-rt with delay

This is almost the most basic approach one could take without interrupts or an kind of sharing and thus our primitive "baseline":

Code

#![no_main]
#![no_std]

use panic_halt as _;
use stm32f0xx_hal::{delay::Delay, prelude::*, stm32};
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    if let (Some(mut p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) {
        let mut state: u8 = 0;

        let (mut led, mut delay) = cortex_m::interrupt::free(|cs| {
            // Configure clock to 48 MHz (i.e. the maximum) and freeze it
            let mut rcc = p.RCC.configure().sysclk(48.mhz()).freeze(&mut p.FLASH);

            // Obtain resources from GPIO port A
            let gpioa = p.GPIOA.split(&mut rcc);

            // (Re-)configure PA5 as output
            let led = gpioa.pa5.into_push_pull_output(cs);

            // Get delay provider
            let delay = Delay::new(cp.SYST, &rcc);

            (led, delay)
        });

        loop {
            if state < 10 {
                // Turn off the LED
                led.set_low().ok();
                state += 1;
            } else {
                // Turn on the LED
                led.set_high().ok();
                state = 0;
            }
            delay.delay_ms(100_u16);
        }
    }

    loop {
        continue;
    }
}

Binary results

Optimized

Bloat for example flashing_delay
File  .text Size          Crate Name
0.2%  74.5% 328B flashing_delay flashing_delay::__cortex_m_rt_main
0.0%  14.5%  64B    cortex_m_rt Reset
0.0%   1.4%   6B      [Unknown] main
0.0%   0.5%   2B    cortex_m_rt HardFault_
0.0%   0.5%   2B    cortex_m_rt DefaultPreInit
0.0%   0.5%   2B    cortex_m_rt DefaultHandler_
0.0%   0.0%   0B                And 0 smaller methods. Use -n N to show more.
0.3% 100.0% 440B                .text section size, the file size is 154.7KiB

Section sizes:
   text    data     bss     dec     hex filename
    632       0       4     636     27c flashing_delay

Unoptimized

Bloat for example flashing_delay
File  .text   Size         Crate Name
0.5%  15.2% 1.4KiB stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze
0.2%   6.1%   592B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock
0.2%   5.0%   482B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_pll
0.1%   2.9%   280B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
0.1%   2.8%   276B stm32f0xx_hal stm32f0xx_hal::gpio::gpioa::PA5<MODE>::into_push_pull_output
0.1%   2.1%   206B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.1%   2.1%   204B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.1%   1.9%   180B stm32f0xx_hal <stm32f0xx_hal::delay::Delay as embedded_hal::blocking::delay::DelayUs<u32>>::delay_us
0.1%   1.9%   180B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
0.1%   1.7%   162B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_pll::{{closure}}
1.7%  57.8% 5.5KiB               And 109 smaller methods. Use -n N to show more.
3.0% 100.0% 9.5KiB               .text section size, the file size is 312.7KiB

Section size:
   text    data     bss     dec     hex filename
  11628       0       4   11632    2d70 flashing_delay

Pros

Cons

Cortex-m-rt with interrupts and moved resources

So compared to our baseline, here we'll also:

Code

#![no_main]
#![no_std]

use panic_halt as _;
use stm32f0xx_hal::{gpio::*, prelude::*, stm32};
use cortex_m::{interrupt::Mutex, peripheral::syst::SystClkSource::Core, Peripherals};
use cortex_m_rt::{entry, exception};
use core::cell::RefCell;

// Mutex protected structure for our shared GPIO pin
static GPIO: Mutex<RefCell<Option<flashing::LEDPIN>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    if let (Some(mut p), Some(mut cp)) = (stm32::Peripherals::take(), Peripherals::take()) {
        cortex_m::interrupt::free(move |cs| {
            // Configure clock to 48 MHz (i.e. the maximum) and freeze it
            let mut rcc = p.RCC.configure().sysclk(48.mhz()).freeze(&mut p.FLASH);

            // Get access to individual pins in the GPIO port
            let gpioa = p.GPIOA.split(&mut rcc);

            // (Re-)configure the pin connected to our LED as output
            let led = gpioa.pa5.into_push_pull_output(cs);

            // Transfer GPIO into a shared structure
            *GPIO.borrow(cs).borrow_mut() = Some(led);

            // Set source for SysTick counter, here full operating frequency (== 48MHz)
            cp.SYST.set_clock_source(Core);

            // Set reload value, i.e. timer delay 48 MHz/4 Mcounts == 12Hz or 83ms
            cp.SYST.set_reload(4_000_000 - 1);

            // Start counting
            cp.SYST.enable_counter();

            // Enable interrupt generation
            cp.SYST.enable_interrupt();
        });
    }

    loop {
        continue;
    }
}

// Define an exception handler, i.e. function to call when exception occurs. Here, if our SysTick
// timer generates an exception the following handler will be called
#[exception]
fn SysTick() -> () {
    // Our moved LED pin
    static mut LED: Option<flashing::LEDPIN> = None;

    // Exception handler state variable
    static mut STATE: u8 = 0;

    // If LED pin was moved into the exception handler, just use it
    if let Some(led) = LED {
        // Check state variable, keep LED off most of the time and turn it on every 10th tick
        if *STATE < 10 {
            // Turn off the LED
            led.set_low().ok();

            // And now increment state variable
            *STATE += 1;
        } else {
            // Turn on the LED
            led.set_high().ok();

            // And set new state variable back to 0
            *STATE = 0;
        }
    }
    // Otherwise move it out of the Mutex protected shared region into our exception handler
    else {
        // Enter critical section
        cortex_m::interrupt::free(|cs| {
            // Move LED pin here, leaving a None in its place
            LED.replace(GPIO.borrow(cs).replace(None).unwrap());
        });
    }
}

Binary results

Optimized

Bloat for example flashing_rt_move
File  .text Size            Crate Name
0.2%  52.9% 296B flashing_rt_move flashing_rt_move::__cortex_m_rt_main
0.1%  22.9% 128B        [Unknown] SysTick
0.0%  11.4%  64B      cortex_m_rt Reset
0.0%   1.1%   6B              std core::result::unwrap_failed
0.0%   1.1%   6B              std core::panicking::panic_fmt
0.0%   1.1%   6B              std core::panicking::panic
0.0%   1.1%   6B        [Unknown] main
0.0%   0.4%   2B      cortex_m_rt HardFault_
0.0%   0.4%   2B       panic_halt rust_begin_unwind
0.0%   0.4%   2B      cortex_m_rt DefaultPreInit
0.0%   0.4%   2B      cortex_m_rt DefaultHandler_
0.0%   0.0%   0B                  And 0 smaller methods. Use -n N to show more.
0.3% 100.0% 560B                  .text section size, the file size is 169.3KiB

Section sizes:
   text    data     bss     dec     hex filename
    796       0      12     808     328 flashing_rt_move

Unoptimized

Bloat for example flashing_rt_move
File  .text    Size         Crate Name
0.4%  12.0%  1.4KiB stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze
0.3%   9.6%  1.1KiB           std core::fmt::Formatter::pad
0.2%   4.8%    592B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock
0.1%   3.9%    482B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_pll
0.1%   2.3%    280B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
0.1%   2.3%    276B stm32f0xx_hal stm32f0xx_hal::gpio::gpioa::PA5<MODE>::into_push_pull_output
0.1%   1.7%    210B           std core::ptr::swap_nonoverlapping_bytes
0.1%   1.7%    206B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.1%   1.7%    204B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.0%   1.5%    180B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
1.9%  58.0%  6.9KiB               And 150 smaller methods. Use -n N to show more.
3.3% 100.0% 12.0KiB               .text section size, the file size is 357.7KiB

Section size:
   text    data     bss     dec     hex filename
  14180       0      12   14192    3770 flashing_rt_move

Pros

Cons

IRQ

This uses the irq crate from Jonas Schievink which takes a slightly different approach than cortex-m-rt but still uses it under the hood to do the interrupt handling. Instead of defining the interrupt handlers manually the irq crate provides a scoped_interrupts macro to define the interrupts which are turned into regular cortex-m-rt interrupt handler functions. The real handlers are defined as closures within the main program flow and parsed to a special scope function while will register the real handler closure. As long as the scope is active any triggered interrupt handler internally created by scoped_interrupts will call the registered closure to do the real processing.

Code

#![no_main]
#![no_std]

use panic_halt as _;

use stm32f0xx_hal::{gpio::*, prelude::*, stm32};

use cortex_m::{peripheral::syst::SystClkSource::Core, Peripherals};
use cortex_m_rt::{entry, exception};
use irq::{handler, scope, scoped_interrupts};

// Hook `SysTick` using the `#[exception]` attribute
scoped_interrupts! {
    enum Exception {
        SysTick,
    }

    use #[exception];
}

#[entry]
fn main() -> ! {
    if let (Some(mut p), Some(mut cp)) = (stm32::Peripherals::take(), Peripherals::take()) {
        let mut led = cortex_m::interrupt::free(move |cs| {
            // Configure clock to 48 MHz (i.e. the maximum) and freeze it
            let mut rcc = p.RCC.configure().sysclk(48.mhz()).freeze(&mut p.FLASH);

            // Get access to individual pins in the GPIO port
            let gpioa = p.GPIOA.split(&mut rcc);

            // (Re-)configure the pin connected to our LED as output
            let led = gpioa.pa5.into_push_pull_output(cs);

            // Set source for SysTick counter, here full operating frequency (== 48MHz)
            cp.SYST.set_clock_source(Core);

            // Set reload value, i.e. timer delay 48 MHz/4 Mcounts == 12Hz or 83ms
            cp.SYST.set_reload(4_000_000 - 1);

            // Start counting
            cp.SYST.enable_counter();

            // Enable interrupt generation
            cp.SYST.enable_interrupt();

            led
        });

        // State variable
        let mut state: u8 = 0;

        handler!(
            systick = || {
                // Check state variable, keep LED off most of the time and turn it on every 10th tick
                if state < 10 {
                    // Turn off the LED
                    led.set_low().ok();

                    // And now increment state variable
                    state += 1;
                } else {
                    // Turn on the LED
                    led.set_high().ok();

                    // And set new state variable back to 0
                    state = 0;
                }
            }
        );

        // Create a scope and register the handlers
        scope(|scope| {
            scope.register(Exception::SysTick, systick);

            loop {
                continue;
            }
        });
    }

    loop {
        continue;
    }
}

Binary results

Optimized

Bloat for example flashing_irq
File  .text Size        Crate Name
0.2%  55.7% 312B flashing_irq flashing_irq::__cortex_m_rt_main
0.0%  11.4%  64B  cortex_m_rt Reset
0.0%   7.1%  40B flashing_irq flashing_irq::__cortex_m_rt_main::{{closure}}
0.0%   7.1%  40B    [Unknown] SysTick
0.0%   6.4%  36B          std core::ops::function::FnOnce::call_once{{vtable.shim}}
0.0%   1.1%   6B          std core::panicking::panic_fmt
0.0%   1.1%   6B          std core::panicking::panic
0.0%   1.1%   6B    [Unknown] main
0.0%   0.4%   2B  cortex_m_rt HardFault_
0.0%   0.4%   2B   panic_halt rust_begin_unwind
0.0%   0.4%   2B  cortex_m_rt DefaultPreInit
0.0%   0.4%   2B  cortex_m_rt DefaultHandler_
0.0%   0.4%   2B          std core::ptr::real_drop_in_place
0.0%   0.0%   0B              And 0 smaller methods. Use -n N to show more.
0.3% 100.0% 560B              .text section size, the file size is 162.4KiB

Section sizes:
   text    data     bss     dec     hex filename
    808       0      12     820     334 flashing_irq

Unoptimized

Bloat for example flashing_irq
File  .text   Size         Crate Name
0.4%  14.7% 1.4KiB stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze
0.2%   5.9%   592B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock
0.1%   4.8%   482B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_pll
0.1%   2.8%   280B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
0.1%   2.8%   276B stm32f0xx_hal stm32f0xx_hal::gpio::gpioa::PA5<MODE>::into_push_pull_output
0.1%   2.1%   206B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.1%   2.0%   204B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.1%   1.8%   180B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
0.0%   1.6%   162B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_pll::{{closure}}
0.0%   1.5%   148B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
1.8%  59.3% 5.8KiB               And 121 smaller methods. Use -n N to show more.
3.0% 100.0% 9.8KiB               .text section size, the file size is 321.0KiB

Section size:
   text    data     bss     dec     hex filename
  12148       0       8   12156    2f7c flashing_irq

Pros

Cons

cortex-m-rtfm

cortex-m-rtfm is a very clever toolbox combining a macro based domain specific language annotation with static analysis to provide ideal results with calculable worst time interrupt execution times and lock-free operation.

Code

#![no_main]
#![no_std]

use panic_halt as _;
use stm32f0xx_hal::{gpio::*, prelude::*, stm32};
use cortex_m::peripheral::syst::SystClkSource::Core;
use rtfm::app;

#[app(device = crate::stm32, peripherals = true)]
const APP: () = {
    // Late resources
    struct Resources {
        led: flashing::LEDPIN,
    }

    #[init]
    fn init(cx: init::Context) -> init::LateResources {
        // Cortex-M peripherals
        let mut core = cx.core;

        // Device specific peripherals
        let mut device = cx.device;

        // Configure clock to 48 MHz (i.e. the maximum) and freeze it
        let mut rcc = device
            .RCC
            .configure()
            .sysclk(48.mhz())
            .freeze(&mut device.FLASH);

        // Get access to individual pins in the GPIO port
        let gpioa = device.GPIOA.split(&mut rcc);

        let led = cortex_m::interrupt::free(move |cs| {
            // (Re-)configure the pin connected to our LED as output
            gpioa.pa5.into_push_pull_output(cs)
        });

        // Set source for SysTick counter, here full operating frequency (== 48MHz)
        core.SYST.set_clock_source(Core);

        // Set reload value, i.e. timer delay 48 MHz/4 Mcounts == 12Hz or 83ms
        core.SYST.set_reload(4_000_000 - 1);

        // Start counting
        core.SYST.enable_counter();

        // Enable interrupt generation
        core.SYST.enable_interrupt();

        init::LateResources { led }
    }

    // Define an exception handler, i.e. function to call when exception occurs. Here, if our SysTick
    // timer generates an exception the following handler will be called
    #[task(binds = SysTick, priority = 1, resources = [led])]
    fn systick(c: systick::Context) {
        // Exception handler state variable
        static mut STATE: u8 = 0;

        // If LED pin was moved into the exception handler, just use it
        // Check state variable, keep LED off most of the time and turn it on every 10th tick
        if *STATE < 10 {
            // Turn off the LED
            c.resources.led.set_low().ok();

            // And now increment state variable
            *STATE += 1;
        } else {
            // Turn on the LED
            c.resources.led.set_high().ok();

            // And set new state variable back to 0
            *STATE = 0;
        }
    }
};

Binary results

Optimized

Bloat for example flashing_rtfm
File  .text Size       Crate Name
0.1%  61.5% 236B   [Unknown] main
0.0%  16.7%  64B cortex_m_rt Reset
0.0%  10.4%  40B   [Unknown] SysTick
0.0%   0.5%   2B cortex_m_rt HardFault_
0.0%   0.5%   2B cortex_m_rt DefaultPreInit
0.0%   0.5%   2B cortex_m_rt DefaultHandler_
0.0%   0.0%   0B             And 0 smaller methods. Use -n N to show more.
0.2% 100.0% 384B             .text section size, the file size is 154.8KiB

Section sizes:
   text    data     bss     dec     hex filename
    576       0       4     580     244 flashing_rtfm

Unoptimized

Bloat for example flashing_rtfm
File  .text    Size         Crate Name
0.4%  12.8%  1.4KiB stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze
0.3%   8.9%   1018B           std core::fmt::Formatter::pad_integral
0.2%   5.2%    592B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock
0.1%   4.2%    482B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_pll
0.1%   2.8%    320B           std core::fmt::num::imp::fmt_u32
0.1%   2.4%    280B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
0.1%   2.4%    276B stm32f0xx_hal stm32f0xx_hal::gpio::gpioa::PA5<MODE>::into_push_pull_output
0.1%   1.8%    206B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.1%   1.8%    204B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
0.1%   1.8%    204B      cortex_m cortex_m::peripheral::scb::<impl cortex_m::peripheral::SCB>::set_priority::{{closure}}
1.9%  55.5%  6.2KiB               And 117 smaller methods. Use -n N to show more.
3.4% 100.0% 11.2KiB               .text section size, the file size is 331.1KiB

Section size:
   text    data     bss     dec     hex filename
  14100       0       4   14104    3718 flashing_rtfm

Pros

Cons

cmim (Added 2020-03-06)

cmim is crate which aims to simplify the move of resources into an interrupt handler in order to avoid having to use a critical section to gain access to a resource protected by a Mutex. Unlike a Mutex it keeps internal track of the state of protected resource so it can handle accesses to it accordingly.

Code

#![no_main]
#![no_std]

use panic_halt as _;

use stm32f0xx_hal::{gpio::*, prelude::*, stm32};

use cortex_m::{peripheral::syst::SystClkSource::Core, Peripherals};
use cortex_m_rt::{entry, exception};

use cmim::{
    Move,
    Context,
    Exception,
};

// Mutex protected structure for our shared GPIO pin
static GPIO: Move<flashing::LEDPIN, stm32::Interrupt> = Move::new_uninitialized(Context::Exception(Exception::SysTick));

#[entry]
fn main() -> ! {
    if let (Some(mut p), Some(mut cp)) = (stm32::Peripherals::take(), Peripherals::take()) {
        cortex_m::interrupt::free(move |cs| {
            // Configure clock to 48 MHz (i.e. the maximum) and freeze it
            let mut rcc = p.RCC.configure().sysclk(48.mhz()).freeze(&mut p.FLASH);

            // Get access to individual pins in the GPIO port
            let gpioa = p.GPIOA.split(&mut rcc);

            // (Re-)configure the pin connected to our LED as output
            let led = gpioa.pa5.into_push_pull_output(cs);

            // Transfer GPIO into a shared structure
            GPIO.try_move(led).ok();

            // Set source for SysTick counter, here full operating frequency (== 48MHz)
            cp.SYST.set_clock_source(Core);

            // Set reload value, i.e. timer delay 48 MHz/4 Mcounts == 12Hz or 83ms
            cp.SYST.set_reload(4_000_000 - 1);

            // Start counting
            cp.SYST.enable_counter();

            // Enable interrupt generation
            cp.SYST.enable_interrupt();
        });
    }

    loop {
        continue;
    }
}

// Define an exception handler, i.e. function to call when exception occurs. Here, if our SysTick
// timer generates an exception the following handler will be called
#[exception]
fn SysTick() -> () {
    // Exception handler state variable
    static mut STATE: u8 = 0;

    GPIO.try_lock(|led| {
        // Check state variable, keep LED off most of the time and turn it on every 10th tick
        if *STATE < 10 {
            // Turn off the LED
            led.set_low().ok();

            // And now increment state variable
            *STATE += 1;
        } else {
            // Turn on the LED
            led.set_high().ok();

            // And set new state variable back to 0
            *STATE = 0;
        }
    }).ok();
}

Binary results

Optimized

 Bloat for example flashing_cmim
  File  .text Size         Crate Name
  0.2%  61.2% 328B flashing_cmim flashing_cmim::__cortex_m_rt_main
  0.1%  17.9%  96B     [Unknown] SysTick
  0.0%  11.9%  64B   cortex_m_rt Reset
  0.0%   1.1%   6B     [Unknown] main
  0.0%   0.4%   2B   cortex_m_rt HardFault_
  0.0%   0.4%   2B   cortex_m_rt DefaultPreInit
  0.0%   0.4%   2B   cortex_m_rt DefaultHandler_
  0.0%   0.0%   0B               And 0 smaller methods. Use -n N to show more.
  0.3% 100.0% 536B               .text section size, the file size is 163.0KiB

  Section sizes:
     text    data     bss     dec     hex filename
      728       0       4     732     2dc flashing_cmim

Unoptimized

  Bloat for example flashing_cmim
  File  .text    Size         Crate Name
  0.4%  12.6%  1.4KiB stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze
  0.2%   5.1%    592B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock
  0.1%   4.1%    482B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_pll
  0.1%   2.7%    316B      stm32f0? <stm32f0::stm32f0x2::Interrupt as bare_metal::Nr>::nr
  0.1%   2.4%    280B stm32f0xx_hal stm32f0xx_hal::rcc::CFGR::freeze::{{closure}}
  0.1%   2.4%    276B stm32f0xx_hal stm32f0xx_hal::gpio::gpioa::PA5<MODE>::into_push_pull_output
  0.1%   2.0%    236B          cmim cmim::Move<T,I>::try_lock
  0.1%   1.8%    216B      cortex_m cortex_m::peripheral::scb::<impl cortex_m::peripheral::SCB>::vect_active
  0.1%   1.8%    210B           std core::ptr::swap_nonoverlapping_bytes
  0.1%   1.8%    206B stm32f0xx_hal stm32f0xx_hal::rcc::inner::enable_clock::{{closure}}
  2.1%  63.0%  7.2KiB               And 136 smaller methods. Use -n N to show more.
  3.3% 100.0% 11.4KiB               .text section size, the file size is 345.6KiB

  Section size:
     text    data     bss     dec     hex filename
    13816       4       4   13824    3600 flashing_cmim

Pros

Cons

Epilog

Exploring the various options to use interrupts was quite a fun ride and also quite enlightening. I've spent a bit of time to look under the hood of the various approaches in order to understand what they're doing and whether there might be potential to combine approaches or improve on them for future work.

As can be seen each of the approaches comes with a their own set of drawbacks and it would be great if there was a way to combine them in a way that combines the best of all worlds: The straight-forwardness of cortex-m with the simplicity of irq but the power, efficiency and compile time guarantees of cortex-m-rtfm.

Also it would be great to have an approach which covers more architectures than ARM Cortex-M. However to cover all of Cortex-M I specifically opted to use a Cortex-M0 which lacks e.g. compare-and-swap options and thus resembles the lowest possible denominator for this particular vendor.

You can find all the code and tools used to conduct the tests in my repository at interrupt-comparison. Feel free to experiment and let me know your experiences. Also let me know if you have another approach you feel should be covered here.

I was trying to include cmim in this comparison but I failed to get the SysTick exception to work and failed. If this gets addressed I might update this blog post with another implementation.

Update 2020-03-06: cmim is now included

Thanks for sticking with me for this long and I hope to see you another time around.