STM32F4 Embedded Rust at the PAC: GPIO Interrupts
Tags: rust, embedded, tutorial
Published: 2023-03-14 17:53:02

## Introduction

Interrupts allow for efficient handling of events in embedded real-time applications. Interrupts are signals generated by hardware devices that require immediate attention from the microcontroller. Dealing with interrupts from an embedded microcontroller perspective is more complex than polled code. There is typically additional configuration and maintenance code that needs to be included. Additionally, Rust provides a safe and reliable way to handle interrupts by leveraging its ownership and borrowing system. This makes dealing with interrupts using Rust a bit more involved than usual.

In this post, I will create a GPIO application that is based on interrupts. A button press event will trigger an interrupt service routine (ISR) that will toggle an output LED. In the code, I will also be sticking strictly to the PAC level. Along the way, I will try my best to explain the elements introduced by Rust.

#### If you find this post useful, and if Embedded Rust interests you, stay in the know and skyrocket your learning curve by subscribing to The Embedded Rustacean newsletter:

{% cta  http://www.theembeddedrustacean.com %} Subscribe Now to The Embedded Rustacean{% endcta%} 

### 📚 Knowledge Pre-requisites

To understand the content of this post, you need the following:

* Basic knowledge of coding in Rust.
    
* Familiarity with the basic template for creating embedded applications in Rust.
    
* Familiarity with interrupts in Cortex-M processors.
    

### 💾 Software Setup

All the code presented in this post in addition to instructions for the environment and toolchain setup is available on the [apollolabsdev Nucleo-F401RE](https://github.com/apollolabsdev/stm32-nucleo-f401re) git repo. Note that if the code on the git repo is slightly different then it means that it was modified to enhance the code quality or accommodate any HAL/Rust updates.

### 🛠 Hardware Setup

#### Materials

* [Nucleo-F401RE board](https://amzn.to/3yn6AIb)
    
![Nucleo Board](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tbn7stgyuy79xi94g8hu.jpg)

#### 🔌 Connections

There will be no need for external connections. On-board connections will be utilized and include the following:

* LED is connected to pin PA5 on the microcontroller. The pin will be used as an output.
    
* A user button connected to pin PC13 on the microcontroller. The pin will be used as input.
    

## 👨‍🎨 Software Design

In the application developed in this post, I want to toggle an LED with every button press. In essence, interrupts are software routines that are triggered by hardware events. As such, this application will be programmed to run entirely on interrupts, and the application `loop` will be empty.

To provide some context for configuring interrupts, the hardware part is more or less the same across controllers that use a certain architecture (ex. ARM). For ARM-based controllers (the STM32F401RE being one) typically the following steps need to be done:

* Configure and enable interrupts at the peripheral level (Ex. GPIO or ADC).
    
* Enable global interrupts at the Cortex-M processor level (enabled by default).
    
* Enable interrupts at the nested vectored interrupt controller (NVIC) level.
    

After that, one would need to define an Interrupt Service Routine (ISR) in the application code. As one would expect, the ISR contains the code executed in response to a specific interrupt event. Additionally, inside the ISR, it is typical that one would use values that are shared with the `main` routine. Also in the ISR, one would have to clear the hardware pending interrupt flag to allow consecutive interrupts to happen. This is a bit of a challenge in Rust for two reasons; First, to clear the pending flag one would need to access the peripheral registers. This is an issue because if you recall, Rust follows a singleton pattern and we cannot have more than one reference to a peripheral. Second, in Rust, global mutable variables, rightly so, are considered unsafe to read or write. This is because without taking special care, a race condition might be triggered. To solve both challenges, in Rust, global mutable data and peripherals need to be wrapped in safe abstractions that allow them to be shared between the ISR and the main thread.

Let's move on to the code.

## 👨‍💻 Code Implementation

### 📥 Crate Imports

In this implementation the crates required are as follows:

* The `core` crate to import the `Cell` and `RefCell` pointer constructs.
    
* The `cortex_m` crate to import the `Mutex` construct.
    
* The `cortex_m_rt` crate for startup code and minimal runtime for Cortex-M microcontrollers.
    
* The `panic_halt` crate to define the panicking behavior to halt on panic.
    
* The `stm32f4xx_pac` crate to import the STM32F401 microcontroller device PAC API that was created in the [**first post**](https://apollolabsblog.hashnode.dev/stm32f4-embedded-rust-at-the-pac-svd2rust) in this series.
    

```rust
use core::cell::RefCell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use pac::{interrupt, Peripherals};
use panic_halt as _;
use stm32f401_pac as pac;
```

#### 🌍 Global Variables

In the application at hand, I'm choosing to enable interrupts for the GPIO peripheral to detect a button press. As such, I would need to create a global shared variable to access the GPIO peripheral (remember the singleton pattern). This is because I would need to subsequently disable the interrupt pending flag in the ISR. In particular, I will be using `PC13` as the GPIO input pin that I want to enable interrupts for. For convenience, I create a `static` global variable called `G_PER` wrapping the complete `Peripherals` in a safe abstraction as follows:

```rust
// Create a Global Variable for the Peripherals
static G_PER: Mutex<RefCell<Option<Peripherals>>> = Mutex::new(RefCell::new(None));
```

So here the `Peripherals` struct is wrapped in an `Option` that is wrapped in a `RefCell`, which is wrapped in a `Mutex`. The `Mutex` makes sure that the peripheral can be safely shared among threads. Consequently, this means that it would require that we use a critical section to be able to access the peripheral. The `RefCell` is used to be able to obtain a mutable reference to the peripheral. Finally, the `Option` is used to allow for lazy initialization as one would not be able to initialize the variable until later (after I configure all peripherals).

**📝 Notes:**

> 1️⃣ These global variables can be viewed as entities that exist in a global context where access is obtained at runtime by the thread that needs them. This is why `RefCell` is needed. Compared to a `Box`, `RefCell` allows for checking during runtime that only one mutable reference exists to a variable. A `Box` makes sure statically at compile time that only one mutable reference exists. Obviously `Box` cannot be used with interrupts as multiple threads would require mutable access during runtime.

> 2️⃣ I would strongly recommend referring to [Chapter 6 of the Embedded Rust Book](https://docs.rust-embedded.org/book/concurrency/index.html) for more detail on this topic.

### 🎛 Peripheral Configuration Code

**1- Obtain a handle for the device peripherals**: In embedded Rust, as part of the singleton design pattern, we first have to take the PAC-level device peripherals. This is done using the `take()` method. Here I create a device peripheral handler named `dp` as follows:

```rust
let mut dp = pac::Peripherals::take().unwrap();
```

**2- Enable Clock & Configure GPIO**: This is similar to what was done in the [GPIO post](https://apollolabsblog.hashnode.dev/stm32f4-embedded-rust-at-the-pac-gpio-control). First, the clocks to `GPIOA` and `GPIOC` need to be enabled through the RCC registers. Second, the pins that need to be output (PA2 in our case) need to be configured through the `ODR` register.

```rust
// Enable Clock to GPIOA & GPIOC
dp.RCC
    .ahb1enr
    .write(|w| w.gpioaen().set_bit().gpiocen().set_bit());

// Configure PA5 as Output
dp.GPIOA.moder.write(|w| unsafe { w.moder5().bits(0b01) });
```

#### ⏸ Interrupt Configuration Code

The last thing that remains in the configuration is to configure and enable interrupt operation for the GPIO button peripheral. This is so that when a button is pressed, execution switches over to the interrupt service routine. First I will configure the GPIO peripheral. For that, some system-level configurations are required first.

**1- Assert the SYSCFGEN bit in the RCC\_APB2ENR register:** This is a similar idea to the configuration of GPIOs where clocks needed to be enabled to the peripherals first. Here we are enabling the clock to the system configuration controller that controls external interrupts.


![RCC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fkdcj924n3b626tsx9q.png)

```rust
// Assert the SYSCFG EN bit in the RCC register
dp.RCC.apb2enr.write(|w| w.syscfgen().set_bit());
```

**2- Connect External Interrupt Line to PC13:** Here the external interrupt line needs to be connected to the pin we want to configure for interrupts (PC13). This is done by configuring the `EXTI13` field in the `SYSCFG_EXTICR4` register.

![SYSCFG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8zuan6z22hvgdsj3vkzt.png)

```rust
// Configure SYSCFG EXTICR4 Register
dp.SYSCFG.exticr4.write(|w| unsafe { w.exti13().bits(2) });
```

**3- Unmask the external interrupt for PC13:** All interrupts in the STM32F4 are masked by default. To let them through, they need to be individually unmasked. This is done by asserting the field `mr13` in the interrupt mask register `EXTI_IMR` .


![EXTI IMR](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot5c9ottdgz59rnhqkte.png)
```rust
// Disable the EXTI Mask using Interrupt Mask Register (IMR)
dp.EXTI.imr.write(|w| w.mr13().set_bit());
```

**4- Configure the interrupt trigger:** This is the last step in peripheral-level configuration. Here we have to decide what would trigger an interrupt. A rising edge, falling edge, or both. I've elected to go for triggering on a rising edge. This is configured by asserting the `tr13` field in the `EXTI_RTSR` register.

![RTSR](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szai60gllkt0m70vlvcs.png)

```rust
// Configure the Rising Edge Trigger in the EXTI RTSR Register
dp.EXTI.rtsr.write(|w| w.tr13().set_bit());
```

**5- Enable global interrupts at the Cortex-M processor level**: Cortex-M processors have an architectural register named PRIMASK that contains a bit to enable/disable interrupts globally. Note that interrupts are globally enabled by default in the Cortex-M PRIMASK register. Technically nothing needs to be done here from a code perspective, however I wanted to mention this step for awareness.

**6- Enable interrupt source in the NVIC**: Now that the button interrupt is configured, the corresponding interrupt number in the NVIC needs to be unmasked. This is done using the NVIC `unmask` method in the `cotrex_m::peripheral` crate. Also it must be noted that unmasking interrupts in the NVIC is considered `unsafe` in Rust. However, in this case it's fine since we know that we aren't doing any `unsafe` behavior. The `unmask` method expects that we pass it the number for the interrupt that we want to unmask. This could be done by leveraging the `interrupt` enum in the PAC crate that enumerates all the device interrupts. Our interrupt source name is `EXTI15_10` which covers all interrupts for lines 10-15 (ours is 13).

```rust
// Enable EXT13 at NVIC Level
unsafe { cortex_m::peripheral::NVIC::unmask(interrupt::EXTI15_10) }
```

**7- Move Peripherals to Global Context**: Recall how earlier a global variable `G_PER` was introduced to move around the GPIO peripheral between contexts. However, `G_PER` was initialized with `None` pending the configuration of the GPIO button that is now available. This means that we can now move `dp` to the global context in which it can be shared by threads. This is done as follows:

```rust
cortex_m::interrupt::free(|cs| {
    G_PER.borrow(cs).replace(Some(dp));
});
```

Here we are introducing a critical section of code enclosed in the closure `cortex_m::interrupt::free`. In this critical section of code, preemption from interrupts is disabled to ensure that accessing the global variable does not introduce any race conditions. This is required because `G_PER` is wrapped in a `Mutex`. The closure passes a token `cs` that allows us to `borrow` a mutable reference to the global variable and replace the `Option` inside of with `Some(button)`.

Note that from this point on in code, every time we want to access `G_PER` (or any other `Mutex` global variable) we would need to introduce a critical section using `cortex_m::interrupt::free`.

### 📱 Application Code

#### 🔁 Application Loop

Following the design described earlier there is no application loop. All the code for this example will be managed through the ISR. As such our application loop will remain empty.

```rust
loop {}
```

#### ⏸ Interrupt Service Routine(s)

Here I need to setup the ISR that would include the code that executes once the interrupt is detected. To define the interrupt in Rust, first one would need to use the `#[interrupt]` attribute, followed by a function definition that has the interrupt name as an identifier. The interrupt name is obtained from the hal [documentation](https://docs.rs/stm32f4xx-hal/latest/stm32f4xx_hal/enum.interrupt.html) and in our case for the pin PC13 its `EXTI15_10`. This looks as follows:

```rust
#[interrupt]
fn EXTI15_10() {
  // Interrupt Service Routine Code
}
```

Inside the ISR, the first thing that needs to be done is to check that `PA13` was the cause of the interrupt. This is done by checking if the `pr13` field in the pending register `EXTI_PR` is asserted. If `pr13` is asserted then it first needs to be cleared by asserting it again. After that the LED output is toggled via the `GPIOA_ODR` register. Though note that as before, to access `G_PER`, a critical section is needed. The first line in the critical section binds a handle `dp` to a mutable reference of the `Option` in `G_PER` using the `borrow_mut` method. In the following lines, `dp` is unwrapped, providing access to the `button` handle.

```rust
// Start a Critical Section
cortex_m::interrupt::free(|cs| {
    // Obtain Access to Peripherals Global Data
    let mut dp = G_PER.borrow(cs).borrow_mut();
    // Check if PA13 caused the interrupt
    if dp.as_mut().unwrap().EXTI.pr.read().pr13().bit() {
        // Clear Interrupt Flag for Button
        dp.as_mut().unwrap().EXTI.pr.write(|w| w.pr13().set_bit());
        // Toggle Output LED
        dp.as_mut()
            .unwrap()
            .GPIOA
            .odr
            .modify(|r, w| w.odr5().bit(!r.odr5().bit()));
     }
});
```

## 📱 Full Application Code

Here is the full code for the implementation described in this post. You can additionally find the full project and others available on the [apollolabsdev Nucleo-F401RE](https://github.com/apollolabsdev/stm32-nucleo-f401re) git repo.

```rust
#![no_std]
#![no_main]

// Imports
use core::cell::RefCell;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use pac::{interrupt, Peripherals};
use panic_halt as _;
use stm32f401_pac as pac;

// Create a Global Variable for the Peripherals
static G_PER: Mutex<RefCell<Option<Peripherals>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    // Setup handler for device peripherals
    let dp = pac::Peripherals::take().unwrap();

    // GPIO Configuration

    // 1. Enable Clock to GPIOA & GPIOC
    dp.RCC
        .ahb1enr
        .write(|w| w.gpioaen().set_bit().gpiocen().set_bit());

    // 2. Configure PA5 as Output
    dp.GPIOA.moder.write(|w| unsafe { w.moder5().bits(0b01) });

    // Interrupt Configuration

    // 1. Enable the SYSCFG bit in the RCC register
    dp.RCC.apb2enr.write(|w| w.syscfgen().set_bit());

    // 2. Configure SYSCFG EXTICR4 Register
    dp.SYSCFG.exticr4.write(|w| unsafe { w.exti13().bits(2) });

    // 3. Disable the EXTI Mask using Interrupt Mask Register (IMR)
    dp.EXTI.imr.write(|w| w.mr13().set_bit());

    // 4. Configure the Rising Edge Trigger in the EXTI RTSR Register
    dp.EXTI.rtsr.write(|w| w.tr13().set_bit());

    // 5. Enable EXT13 at NVIC Level
    unsafe { cortex_m::peripheral::NVIC::unmask(interrupt::EXTI15_10) }

    // Since Initialization is complete, move Peripherals struct to Global Context
    cortex_m::interrupt::free(|cs| {
        G_PER.borrow(cs).replace(Some(dp));
    });

    // Application Loop
    loop {}
}

// Handler for pins connected to line 10 to 15
#[interrupt]
fn EXTI15_10() {
    // Start a Critical Section
    cortex_m::interrupt::free(|cs| {
        // Obtain Access to Peripherals Global Data
        let mut dp = G_PER.borrow(cs).borrow_mut();
        // Check if PA13 caused the interrupt
        if dp.as_mut().unwrap().EXTI.pr.read().pr13().bit() {
            // Clear Interrupt Flag for Button
            dp.as_mut().unwrap().EXTI.pr.write(|w| w.pr13().set_bit());
            // Toggle Output LED
            dp.as_mut()
                .unwrap()
                .GPIOA
                .odr
                .modify(|r, w| w.odr5().bit(!r.odr5().bit()));
        }
    });
}
```

## 🔬 Further Experimentation/Ideas

* If you have extra buttons, try implementing additional interrupts from other input pins where each button toggles the same LED.
    
* A cool mini project is capturing a human response time. Using the LED and a press button, see how long it takes you to press the button after the LED turns on. You can use a counter/timer peripheral to capture duration and UART to propagate the result. Refer to past posts for dealing with the timer and UART.
    

## Conclusion

In this post, an interrupt-based LED control application was created leveraging the GPIO peripheral for the STM32F401RE microcontroller on the Nucleo-F401RE development board. All code was created at the PAC level. Have any questions/comments? Share your thoughts in the comments below 👇.

#### If you found this post useful, and if Embedded Rust interests you, stay in the know and skyrocket your learning curve by subscribing to The Embedded Rustacean newsletter:

{% cta  http://www.theembeddedrustacean.com %} Subscribe Now to The Embedded Rustacean{% endcta%}