Tonight I worked on getting an external interrupt (EXTI) on my blue pill working with the press of a button. I achieved this by looking at the source of many repos, Googling for examples without much luck, watching a YouTube video, and reading the 1100+ page datasheet on the STM32F103 microcontroller. I hope this blog post will help another dev skip some of these steps.

My goal was to have a method (button_pressed()) get called when pin PB11 detects a falling edge. Let's get right to it. I'll start with RTFM's app! macro:

app! {
    resources: {
        static EXTI: hal::stm32f103xx::EXTI;
    tasks: {
        EXTI15_10: {
            path: button_pressed,
            resources: [EXTI]

Adding EXTI as a resource allows you to read & write from it in interrupt handlers. In order to do this, make sure it is in the resources array for your EXTI ISR task. The EXTI15_10 task tells RFTM to listen to that ISR and call the function button_pressed(). By the way, EXTI15_10 means external interrupts 10-15; they're all grouped together. Some lower interrupts are not grouped like this. Let's now go to the RTFM init() function:

// Enable the alternate function I/O clock (for external interrupts)
p.device.RCC.apb2enr.write(|w| w.afioen().enabled());

// Set PB11 to input with pull up resistor
let mut rcc = p.device.RCC.constrain();
let mut gpiob = p.device.GPIOB.split(&mut rcc.apb2);
gpiob.pb11.into_pull_up_input(&mut gpiob.crh);
// Set EXTI11 multiplexers to use port B
p.device.AFIO.exticr3.write(|w| unsafe { w.exti11().bits(0x01) });
// Enable interrupt on EXTI11
p.device.EXTI.imr.write(|w| w.mr11().set_bit());
// Set falling trigger selection for EXTI11
p.device.EXTI.ftsr.write(|w| w.tr11().set_bit());

// Declare EXTI as a late resource to be used in interrupt handlers
init::LateResources {
    EXTI: p.device.EXTI

Last but not least, my button_pressed() function:

fn button_pressed(_t: &mut Threshold, mut r: EXTI15_10::Resources) {
    // Set the pending register for EXTI11|_, w| w.pr11().set_bit());

    // Do additional logic or whatever you want upon button press

A very important (and easy to miss) step in this process is to clear the pending register in your interrupt. If you don't, the interrupt will simply keep getting called over and over.

Here is some source code that uses this technique. I cut a lot of it out intending on only pasting the important bits in the blog post, but sometimes context helps as much as the important parts. That's it for this post. I hope this helps someone out, and if it works out I'll do a few more blog posts like this one.