Embedded Rust: How to configure interrupt on an STM32F103 MCU

Hello guys,

I am learning how to write embedded rust code and I choose to learn writing code for the stm32f103 MCU as my entry point. I have the following code:

// std and main are not available for bare-metal software
#![no_std]
#![no_main]

extern crate panic_halt;

use cortex_m_rt;
use stm32f1;

use cortex_m_semihosting::hprintln;

use cortex_m_rt::entry;
use stm32f1::stm32f103;

/* Code Description
- Power on LED on PC13 on PB12 rising edge interrupt
- Power off LED on PC13 on PB12 falling edge interrupt
*/

#[interrupt]
fn EXTI1() {
    let _res = hprintln!("Hello, world!");
    let peripherals = stm32f103::Peripherals::take().unwrap();
    let gpioc = &peripherals.GPIOC;
    match gpioc.odr.read().odr13() {
        stm32f103::gpioa::odr::ODR0R::HIGH => {
            gpioc.brr.write(|w| w.br13().set_bit());
        }
        stm32f103::gpioa::odr::ODR0R::LOW => {
            gpioc.bsrr.write(|w| w.bs13().set_bit());
        }
    };
}

// use `main` as the entry point of this application
#[entry]
fn main() -> ! {
    // get handles to the hardware
    let peripherals = stm32f103::Peripherals::take().unwrap();
    let gpioc = &peripherals.GPIOC;

    // set the handler for interrupts on PB12
    let mut core_peripherals = stm32f103::CorePeripherals::take().unwrap();
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI0);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI1);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI2);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI3);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI4);

    let rcc = &peripherals.RCC;

    // enable the GPIO clock for IO port B
    rcc.apb2enr.write(|w| w.iopben().set_bit());
    // input
    peripherals.GPIOB.crh.write(|w| unsafe {
        w.mode12().bits(0b00);
        w.cnf12().bits(0b10)
    });
    // pull-up
    peripherals.GPIOB.odr.write(|w| w.odr12().set_bit());

    // set MR12 in IMR
    peripherals.EXTI.imr.write(|w| w.mr12().set_bit());
    // set MR12 in RTMR
    peripherals.EXTI.rtsr.write(|w| w.tr12().set_bit());
    //set MR12 in FTMR
    peripherals.EXTI.ftsr.write(|w| w.tr12().set_bit());
    // configure pinb12 as source input for external interrupt
    peripherals
        .AFIO
        .exticr4
        .write(|w| unsafe { w.exti12().bits(0b0001) });

    // enable the GPIO clock for IO port C
    rcc.apb2enr.write(|w| w.iopcen().set_bit());
    gpioc.crh.write(|w| unsafe {
        w.mode13().bits(0b11);
        w.cnf13().bits(0b00)
    });

    gpioc.bsrr.write(|w| w.bs13().set_bit());

    loop {
        cortex_m::asm::delay(2000000);
    }
}

On compiling, I get the error:

error: cannot find attribute macro `interrupt` in this scope
  --> src/main.rs:20:3
   |
20 | #[interrupt]
   |   ^^^^^^^^^

error: aborting due to previous error

I have tried multiple solutions including using the exception! macro provided by the cortex-m-rt. Below is my dependencies list:

[dependencies]
stm32f1 = {version = "0.6.0", features = ["stm32f103", "rt"]}
cortex-m-rt = "0.6.7"
cortex-m = "0.5.8"
panic-halt = "0.2.0"
cortex-m-semihosting = "0.3.5"

I have also checked out this thread but I still was not able to resolve this issue. What am I missing?

#[interrupt], like #[entry], comes from the cortex_m_rt crate, though in practice you'll want the one exported from your device crate. So, like #[entry], you must use it:

use stm32f1::stm32f103::interrupt;

You may also be able to use the attribute fully-qualified, but for me this doesn't work reliably, and I'm not sure why: #[stm32f1::stm32f103::interrupt].

1 Like

I have added the use stm32f1::stm32f103::interrupt; and managed to successfully compile the code. Unfortunately, no interrupts seem to be fired when I toggle the voltage level on PB12.

As per the stm32f1xx reference manual, to configure the 20 lines as interrupt sources, use the following procedure:

  • Configure the mask bits of the 20 Interrupt lines (EXTI_IMR)
    peripherals.EXTI.imr.write(|w| w.mr12().set_bit());
  • Configure the Trigger Selection bits of the Interrupt lines (EXTI_RTSR and
    EXTI_FTSR)
    peripherals.EXTI.rtsr.write(|w| w.tr12().set_bit());
    peripherals.EXTI.ftsr.write(|w| w.tr12().set_bit());
  • Configure the enable and mask bits that control the NVIC IRQ channel mapped to the
    External Interrupt Controller (EXTI) so that an interrupt coming from one of the 20 lines
    can be correctly acknowledged.
    peripherals
        .AFIO
        .exticr4
        .write(|w| unsafe { w.exti12().bits(0b0001) });

At this point, I expect the interrupt is fully configured yet none is getting fired. Did I miss something else?

Soooo you mention configuring the NVIC, but the code you pasted right after that doesn't configure the NVIC. You need to ensure that you've got a line somewhere reading

NVIC::unmask(Interrupt::EXTI);

...where NVIC is from cortex_m::peripheral and Interrupt is from stm32f1::stm32f103. Without this, the peripheral can pend the interrupt all it wants, but your ISR will never run.

Edited to add: Also, if the F103 is anything like the larger F-series parts I've used, there are a handful of GPIO lines that are not inputs by default. Usually ~2 lines in GPIOA and GPIOB related to JTAG. If you're using one of those pins, you also need to ensure that it's set to input mode for EXTI to work (at least on the F407). If you're using any other pin, it's an input by default, so you should be safe.

And edited again: Okay, from your original code snippet it looks like you're enabling the interrupts in the NVIC correctly, so my guess here is probably wrong.

Aaaand I'm back: I notice you have not set the AFIO_EXTICRx register corresponding to the EXTI source you're trying to use, but the comments suggest that you're using a pin on GPIOB. It looks like the EXTI ports default to GPIOA and must be overridden. I'm on page 191 in the reference manual, fwiw. I've never actually used an F103, though, so I could be reading this wrong.

I used this link by mjepronk and I trimmed down my code to the following which works:

// std and main are not available for bare metal software
#![no_std]
#![no_main]

extern crate panic_halt;

use cortex_m_rt;
use stm32f1;

use cortex_m_semihosting::hprintln;

use cortex_m_rt::entry;
use stm32f1::stm32f103;
use stm32f1::stm32f103::interrupt;

/* Code Description
- Power on LED on PC13 on PB12 rising edge interrupt
- Power off LED on PC13 on PB12 falling edge interrupt
*/

#[interrupt]
fn EXTI1() -> ! {
    hprintln!("Interrupt!!!").unwrap();
    // let peripherals = stm32f103::Peripherals::take().unwrap();
    // peripherals.EXTI.pr.write(|w| w.pr1().set_bit());
}

// use `main` as the entry point of this application
#[entry]
fn main() -> ! {
    // enable the GPIO clock for IO port C
    let mut core_peripherals = stm32f103::CorePeripherals::take().unwrap();
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI1);
    // get handles to the hardware
    let peripherals = stm32f103::Peripherals::take().unwrap();
    // PC13 output 50 MHz
    peripherals.RCC.apb2enr.write(|w| w.iopcen().set_bit());
    peripherals.GPIOC.crh.write(|w| unsafe {
        w.mode13().bits(0b11);
        w.cnf13().bits(0b00)
    });
    // PA1 input
    peripherals.RCC.apb2enr.write(|w| w.iopaen().set_bit());
    peripherals.GPIOA.crl.write(|w| unsafe {
        w.mode1().bits(0b00);
        w.cnf1().bits(0b10)
    });
    // pull-up
    peripherals.GPIOA.bsrr.write(|w| w.br1().set_bit());
    // set MR12 in IMR
    peripherals.EXTI.imr.write(|w| w.mr1().set_bit());
    // set MR12 in RTMR
    peripherals.EXTI.rtsr.write(|w| w.tr1().set_bit());

    loop {
        match peripherals.GPIOA.idr.read().idr1() {
            stm32f103::gpioa::idr::IDR0R::HIGH => {
                let _res = hprintln!("A1 = HIGH");
            }
            stm32f103::gpioa::idr::IDR0R::LOW => {
                let _res = hprintln!("A1 = LOW");
            }
        };
        cortex_m::asm::delay(2000000);
    }
}

However, if I change the above to PB12, as in

// std and main are not available for bare metal software
#![no_std]
#![no_main]

extern crate panic_halt;

use cortex_m_rt;
use stm32f1;

use cortex_m_semihosting::hprintln;

use cortex_m_rt::entry;
use stm32f1::stm32f103;
use stm32f1::stm32f103::interrupt;

/* Code Description
- Power on LED on PC13 on PB12 rising edge interrupt
- Power off LED on PC13 on PB12 falling edge interrupt
*/

#[interrupt]
fn EXTI0() -> ! {
    hprintln!("Interrupt!!!").unwrap();
    // let peripherals = stm32f103::Peripherals::take().unwrap();
    // peripherals.EXTI.pr.write(|w| w.pr1().set_bit());
}

#[interrupt]
fn EXTI1() -> ! {
    hprintln!("Interrupt!!!").unwrap();
    // let peripherals = stm32f103::Peripherals::take().unwrap();
    // peripherals.EXTI.pr.write(|w| w.pr1().set_bit());
}

#[interrupt]
fn EXTI2() -> ! {
    hprintln!("Interrupt!!!").unwrap();
    // let peripherals = stm32f103::Peripherals::take().unwrap();
    // peripherals.EXTI.pr.write(|w| w.pr1().set_bit());
}

#[interrupt]
fn EXTI3() -> ! {
    hprintln!("Interrupt!!!").unwrap();
    // let peripherals = stm32f103::Peripherals::take().unwrap();
    // peripherals.EXTI.pr.write(|w| w.pr1().set_bit());
}

#[interrupt]
fn EXTI4() -> ! {
    hprintln!("Interrupt!!!").unwrap();
    // let peripherals = stm32f103::Peripherals::take().unwrap();
    // peripherals.EXTI.pr.write(|w| w.pr1().set_bit());
}

// use `main` as the entry point of this application
#[entry]
fn main() -> ! {
    // enable the GPIO clock for IO port C
    let mut core_peripherals = stm32f103::CorePeripherals::take().unwrap();
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI0);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI1);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI2);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI3);
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI4);
    // get handles to the hardware
    let peripherals = stm32f103::Peripherals::take().unwrap();
    // PC13 output 50 MHz
    peripherals.RCC.apb2enr.write(|w| w.iopcen().set_bit());
    peripherals.GPIOC.crh.write(|w| unsafe {
        w.mode13().bits(0b00);
        w.cnf13().bits(0b10)
    });
    // PB12 input
    peripherals.RCC.apb2enr.write(|w| w.iopben().set_bit());
    peripherals.GPIOB.crh.write(|w| unsafe {
        w.mode12().bits(0b11);
        w.cnf12().bits(0b00)
    });
    // pull-up
    peripherals.GPIOB.bsrr.write(|w| w.br12().set_bit());
    // set MR12 in IMR
    peripherals.EXTI.imr.write(|w| w.mr12().set_bit());
    // set MR12 in RTMR
    peripherals.EXTI.rtsr.write(|w| w.tr12().set_bit());

    loop {
        match peripherals.GPIOB.idr.read().idr12() {
            stm32f103::gpioa::idr::IDR0R::HIGH => {
                let _res = hprintln!("PB12 = HIGH");
            }
            stm32f103::gpioa::idr::IDR0R::LOW => {
                let _res = hprintln!("PB12 = LOW");
            }
        };
        cortex_m::asm::delay(2000000);
    }
}

it does not work. This is weird because I expect the configuration process for both pins to be the same . :confused:

I actually did, at this point:

// configure pinb12 as source input for external interrupt
    peripherals
        .AFIO
        .exticr4
        .write(|w| unsafe { w.exti12().bits(0b0001) });

Setting exti12 to:

  • 0b0000 => PINA12
  • 0b0001 => PINB12
  • 0b0010 => PINC12
    ...

In this case, I'm pretty sure you have omitted the AFIO config, so it will only work for GPIOA. (I agree that you had it before.)

1 Like

Here is the complete code:

// std and main are not available for bare metal software
#![no_std]
#![no_main]

extern crate panic_halt;

use core::cell::RefCell;
// use core::sync::atomic::{AtomicUsize, Ordering};
use cortex_m::interrupt as cortex_m_interrupt;

use cortex_m_rt;
use stm32f1;

use cortex_m_semihosting::hprintln;

use cortex_m_rt::entry;
use stm32f1::stm32f103;
use stm32f1::stm32f103::interrupt;

// static Y: AtomicUsize = AtomicUsize::new(0);
// static X: AtomicUsize = AtomicUsize::new(0);

static EXTI: cortex_m_interrupt::Mutex<RefCell<Option<stm32f103::EXTI>>> =
    cortex_m_interrupt::Mutex::new(RefCell::new(None));

#[interrupt]
fn ADC1_2() -> ! {
    hprintln!("ADC interrupt!").unwrap();
}

#[interrupt]
fn EXTI3() -> ! {
    hprintln!("Click nterrupt!").unwrap();
    cortex_m_interrupt::free(|cs| {
        EXTI.borrow(cs)
            .borrow()
            .as_ref()
            .unwrap()
            .pr
            .write(|w| w.pr8().set_bit());
    });
}

fn configure_joystick(peripherals: &stm32f103::Peripherals) {
    // configure  PB8 and an input
    peripherals.RCC.apb2enr.write(|w| w.iopben().set_bit());
    peripherals.GPIOB.crh.write(|w| unsafe {
        w.mode8().bits(0b00);
        w.cnf8().bits(0b10)
    });
    // enable pull-up resistors on PB8
    peripherals.GPIOB.bsrr.write(|w| w.br8().set_bit());
    // set MR12 in IMR
    peripherals.EXTI.imr.write(|w| w.mr8().set_bit());
    // set MR12 in RTMR
    peripherals.EXTI.rtsr.write(|w| w.tr8().set_bit());
    // set the interrupt source as PB8 pin
    peripherals
        .AFIO
        .exticr3
        .write(|w| unsafe { w.exti8().bits(0b0001) });
}

// use `main` as the entry point of this application
#[entry]
fn main() -> ! {
    // get handles to the hardware
    let peripherals = stm32f103::Peripherals::take().unwrap();
    configure_joystick(&peripherals);
    let exti = peripherals.EXTI;
    // initialize PR reference before enabling interrupts
    cortex_m_interrupt::free(|cs| EXTI.borrow(cs).replace(Some(exti)));
    // enable EXTI3 interrupts
    let mut core_peripherals = stm32f103::CorePeripherals::take().unwrap();
    core_peripherals.NVIC.enable(stm32f103::Interrupt::EXTI3);
    loop {
        match peripherals.GPIOB.idr.read().idr8() {
            stm32f103::gpioa::idr::IDR0R::HIGH => {
                let _res = hprintln!("A1 = HIGH");
            }
            stm32f103::gpioa::idr::IDR0R::LOW => {
                let _res = hprintln!("A1 = LOW");
            }
        };
        cortex_m::asm::delay(2000000);
    }
}

When executing the above, pressing the button connected on PB8 causes the printed out text to toggle from A1 = HIGH to A1 = LOW and releasing the button reverts the printed out text to A1 = HIGH. The interrupt handler, however, is not being executed. :confused: