As my daily routine was interrupted by COVID-19, I stayed home and played around with embedded Rust’s interrupt this week.

Interrupt can be dangerous if you don’t do it right. I read the concurrency section of the book and learned Rust’s safe way to share resources between the main and ISR contexts.

How to use timer interrupt

In this post, I will show how I did a blinking LED application using a timer interrupt.

“It’s amazing in the blink of an eye, you finally see the light.” - Steven Tyler

1. Wrap shared resources

The goal is to flip the desired state of a LED in an interrupt handler and turn on/off the LED accordingly in the main. So, it is necessary to share the state of LED and a timer peripheral between ISR and the main code.

In embedded Rust, we use statically allocated Mutex to share peripherals and variables between different contexts.

Here is how I wrap bool that represents the state of my LED.

static LED_STATE: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));

And a line below shows how I wrap my TIM2, timer 2 module. Note that TIMER_TIM2’s RefCell has an initial value of None. It will be replaced with a timer peripheral later.

static TIMER_TIM2: Mutex<RefCell<Option<Timer<stm32::TIM2>>>> = Mutex::new(RefCell::new(None));

Next, I set up an interrupt timer. I am setting it up as a 5Hz timer and wrapping it with Mutex.

let mut timer = Timer::tim2(dp.TIM2, 5.hz(), clocks, &mut rcc.apb1);
timer.listen(Event::Update);

I then use critical section with cortex_m::interrupt::free and replace TIMER_TIM2’s content (None) with my instantiated timer.

free(|cs| {
    TIMER_TIM2.borrow(cs).replace(Some(timer));
});

2. Handle interrupts

Enabling the TIM2 interrupt is very easy with NVIC (Nested vector interrupt control).

stm32::NVIC::unpend(Interrupt::TIM2);
unsafe {
    stm32::NVIC::unmask(Interrupt::TIM2);
}

That’s it. The timer is now running. When the interrupt is triggered, it goes to the interrupt handler defined like this:

#[interrupt]
fn TIM2() {
    free(|cs| {
        if let Some(ref mut tim2) = TIMER_TIM2.borrow(cs).borrow_mut().deref_mut() {
            tim2.clear_update_interrupt_flag();
        }
        let led_state = LED_STATE.borrow(cs);
        led_state.replace(!led_state.get());
    });
}

The first thing to do in the ISR is to clear the interrupt flag. stm32f3xx_hal has clear_update_interrupt_flag(). This method clears the status register’s UIF bit. This bit is set by the hardware and must be cleared by the software. If this bit is not cleared, the program repeats the ISR forever. After clearing the flag, I flip the state of LED_STATE and replace the old value with a new one.

3. Reflect the change in the main

In the main, I evaluate LED_STATE like this:

loop {
    if free(|cs| LED_STATE.borrow(cs).get()) {
        led.set_high().unwrap();
    } else {
        led.set_low().unwrap();
    }
}

See how I use critical section by calling cortex_m::interrupt::free to access LED_STATE. This and Mutex ensure exclusive access to the shared resource.

Here are two codes for the blinking LED. One for STM32F3DISCOVERY board and the other for Nucleo-F429ZI. I used stm32f3xx_hal for DISCOVERY and stm32f4xx_hal for Nucleo. The basic concept is the same between the two implementations. Only differences are the way to instantiate the timer peripheral and the call to clear the interrupt flag by using HAL specific methods.

STM32F3DISCOVERY

Here is the code for STM32F3DISCOVERY board board. This is built with stm32f3xx_hal.

#![no_main]
#![no_std]

extern crate panic_halt;

use core::cell::{Cell, RefCell};
use core::ops::DerefMut;
use cortex_m;
use cortex_m::interrupt::{free, Mutex};
use cortex_m_rt::entry;
use stm32f3xx_hal::{
    prelude::*,
    stm32,
    stm32::{interrupt, Interrupt},
    timer::{Event, Timer},
};

static LED_STATE: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static TIMER_TIM2: Mutex<RefCell<Option<Timer<stm32::TIM2>>>> = Mutex::new(RefCell::new(None));

#[interrupt]
fn TIM2() {
    free(|cs| {
        if let Some(ref mut tim2) = TIMER_TIM2.borrow(cs).borrow_mut().deref_mut() {
            tim2.clear_update_interrupt_flag();
        }
        let led_state = LED_STATE.borrow(cs);
        led_state.replace(!led_state.get());
    });
}

#[entry]
fn main() -> ! {
    let dp = stm32::Peripherals::take().unwrap();
    // Set up the system clock
    let mut flash = dp.FLASH.constrain();
    let mut rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.freeze(&mut flash.acr);

    // Set up the LED
    let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);
    let mut led = gpioe
        .pe9
        .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);

    // Set up the interrupt timer
    let mut timer = Timer::tim2(dp.TIM2, 5.hz(), clocks, &mut rcc.apb1);
    timer.listen(Event::Update);

    // Move shared resources to Mutex
    free(|cs| {
        TIMER_TIM2.borrow(cs).replace(Some(timer));
    });

    // Enable interrupt
    stm32::NVIC::unpend(Interrupt::TIM2);
    unsafe {
        stm32::NVIC::unmask(Interrupt::TIM2);
    }

    loop {
        if free(|cs| LED_STATE.borrow(cs).get()) {
            led.set_high().unwrap();
        } else {
            led.set_low().unwrap();
        }
    }
}

Nucleo-F429ZI

Nucleo-F429ZI board has STM32F429 microcontroller. I use stm32f4xx_hal for this.

#![no_main]
#![no_std]

extern crate panic_halt;

use core::cell::{Cell, RefCell};
use core::ops::DerefMut;
use cortex_m;
use cortex_m::interrupt::{free, Mutex};
use cortex_m_rt::entry;
use stm32f4xx_hal::{
    prelude::*,
    stm32,
    stm32::interrupt,
    timer::{Event, Timer},
};

static LED_STATE: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static TIMER_TIM2: Mutex<RefCell<Option<Timer<stm32::TIM2>>>> = Mutex::new(RefCell::new(None));

#[interrupt]
fn TIM2() {
    free(|cs| {
        if let Some(ref mut tim2) = TIMER_TIM2.borrow(cs).borrow_mut().deref_mut() {
            tim2.clear_interrupt(Event::TimeOut);
        }
        let led_state = LED_STATE.borrow(cs);
        led_state.replace(!led_state.get());
    });
}

#[entry]
fn main() -> ! {
    let dp = stm32::Peripherals::take().unwrap();
    // Set up the LED
    let gpiob = dp.GPIOB.split();
    let mut led = gpiob.pb7.into_push_pull_output();

    // Set up the system clock
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(48.mhz()).freeze();

    // Set up the interrupt timer
    let mut timer = Timer::tim2(dp.TIM2, 5.hz(), clocks);
    timer.listen(Event::TimeOut);

    free(|cs| {
        TIMER_TIM2.borrow(cs).replace(Some(timer));
    });

    // Enable interrupt
    stm32::NVIC::unpend(stm32::Interrupt::TIM2);
    unsafe {
        stm32::NVIC::unmask(stm32::Interrupt::TIM2);
    }

    loop {
        if free(|cs| LED_STATE.borrow(cs).get()) {
            led.set_high().unwrap();
        } else {
            led.set_low().unwrap();
        }
    }
}