How to clear interrupt with peripheral handle

Hi everyone! It's my first post.

Few weeks ago I started to learn Rust Embedded.
Now I'm stuck, and I would like to ask you for help. So..

I wanted to use TIM3 in my code to change variable (in future peripheral state) and clear (unpend?) interrupt via registers inside ISR.

In C I did something like this inside ISR:

void TIM3_IRQHandler(void)
{
  if (TIM3->SR & TIM_SR_UIF)
  {
    TIM3->SR &= ~(TIM_SR_UIF);
  }
}

..and now I'm stuck to do this in Rust.
At first I show what I've done so far.

#![no_std]
#![no_main]

use panic_halt as _;

use cortex_m_rt::entry;

use core::{cell::RefCell};
use core::ops::DerefMut;
use cortex_m::interrupt::{self, Mutex};
use stm32g0::stm32g071::{self, Interrupt, NVIC, TIM3};

static G_TIM: Mutex<RefCell<Option<stm32g071::TIM3>>> =
    Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    let p = stm32g071::Peripherals::take().unwrap();

    let rcc_r = &p.RCC;

    let timer_r = &p.TIM3;

    let tim3 = p.TIM3;

    unsafe {
        NVIC::unmask(Interrupt::TIM3);
    };

    rcc_r.apbenr1.write(|w| w.tim3en().set_bit());

    prepare_timer3(timer_r);

    interrupt::free(|cs| {
        G_TIM.borrow(cs).replace(Some(tim3))
    });

    loop {
    }
}

fn prepare_timer3(tim3_r_handle: &TIM3) {
    tim3_r_handle.cr1.write(|w| w.cen().clear_bit());
    tim3_r_handle.psc.write(|w| unsafe { w.psc().bits(16000) });
    tim3_r_handle.arr.write(|w| unsafe { w.arr_l().bits(100) });
    tim3_r_handle.egr.write(|w| w.ug().set_bit());
    tim3_r_handle.dier.write(|w| w.uie().set_bit());
    tim3_r_handle.cr1.write(|w| w.cen().set_bit());
}

#[interrupt]
fn TIM3() {
    interrupt::free(|cs| {
        if let Some(ref mut tim3) =  G_TIM.borrow(cs).borrow_mut().deref_mut() {
            tim3.sr.write(|w| w.uif().clear_bit());
        }
    })
}

And I get this compilation error:

error: cannot find attribute `interrupt` in this scope
  --> src/main.rs:51:3
   |
51 | #[interrupt]
   |   ^^^^^^^^^
   |
   = note: consider importing one of these items:
           cortex_m_rt::interrupt
           crate::stm32g071::interrupt
           stm32g0::stm32g071::interrupt
note: `interrupt` is imported here, but it is a module, not an attribute
  --> src/main.rs:10:27
   |
10 | use cortex_m::interrupt::{self, Mutex};
   |                           ^^^^

error: could not compile `blink-nucleo-g0` due to previous error

I have problem how to resolve those dependency problem.
Could you tell me also that what I did with this Mutex G_TIM is fine?
I mean I did this after read this article: https://docs.rust-embedded.org/book/concurrency/#sharing-peripherals
I also read this Rust embedded: STM32F303 timer interrupt hanging but I don't want to use hal crates.

I also asked at Stackoverflow [I can't put more links as new user]: how-to-clear-interrupt-with-perpiheral-handle-embedded-rust

Are you sure? Interrupt is an enum but I need interrupt as attribute but if I change line with use cortex_m::interrupt::{self, Mutex}; and add interrupt here use stm32g0::stm32g071::{self, Interrupt, NVIC, TIM3}; then I lost Mutex etc.

Maybe try use stm32g0::stm32g071::{self, Interrupt, interrupt, NVIC, TIM3};?

This is what the compile error suggests.

As I mentioned if I do that then I lost Mutex.

Maybe do that, but replace use cortex_m::interrupt::{self, Mutex}; with use cortex_m::interrupt::Mutex; as well?

If I do that then it's problem with interrupt::free.

error[E0599]: no variant or associated item named `free` found for enum `stm32g0::stm32g071::Interrupt` in the current scope
  --> src/main.rs:34:16
   |
34 |     interrupt::free(|cs| {
   |                ^^^^ variant or associated item not found in `stm32g0::stm32g071::Interrupt`

error[E0599]: no variant or associated item named `free` found for enum `stm32g0::stm32g071::Interrupt` in the current scope
  --> src/main.rs:53:16
   |
53 |     interrupt::free(|cs| {
   |                ^^^^ variant or associated item not found in `stm32g0::stm32g071::Interrupt`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `blink-nucleo-g0` due to 2 previous errors

You need to change those 2 lines to cortex_m::interrupt::free, since you don't import cortex_m::interrupt directly into the namespace any more.

use cortex_m::interrupt::free;
use cortex_m::interrupt::Mutex;
use stm32g0::stm32g071::{self, Interrupt, NVIC, TIM3, interrupt};

and changed calling interrupt::free to just free
I did this and it eventually compiled...
and now I think my ISR is hanging, I mean interrupt is invoking in loop.
How to properly "clear" interrupt?
It's my ISR of tim3.

#[interrupt]
fn TIM3() {
    free(|cs| {
        if let Some(ref mut tim3) =  G_TIM.borrow(cs).borrow_mut().deref_mut() {
            if tim3.sr.read().uif().bit_is_set() == true {
                tim3.sr.write(|w| w.uif().clear_bit());
            }
        }
    });
}

Try to toggle a gpio inside the if let but outside the nested if in your ISR and scope it to see how regularly the ISR triggers. Toggle a different pin inside the nested if to see if that ever evaluates to true and executes. (Or if you have a HW debugger with Rust support sorted out you can use breakpoints.)

I've changed code to TIM2:

    free(|cs| {
        if let Some(ref mut tim2) = G_TIM.borrow(cs).borrow_mut().deref_mut() {
            tim2.sr.write(|w| w.uif().clear_bit()); // cannot enter here
        }
    });

And with debugger I cannot reach line with that comment. Any clues?

Do you mean to make another mutex to GPIO and change value, right? :slight_smile:

I think that would kinda defeat the object. I'd go for static mut and unsafe, just for testing purposes. I suspect the mutex is part of the reason you never get there. I think the mutex takes too long to yield or something. But take it with a spoon of salt, I have effectively zero experience with Rust in embedded.

I don't know how to do it. But look here Concurrency - The Embedded Rust Book there is example with Mutex so... I think it is working example.

Is there any open-source big project with bare-metal Rust?

I'm no expert on embedded Rust, but I think the basic idea is something like this:

static mut GPIO_1: Option<stm32g071::GPIOA> = None;

// inside main:
unsafe { GPIO_1 = Some(p.GPIOA) }

// inside IRQ:
match GPIO_1 {
    Some(gpio) => unsafe { // toggle pin
    },
    _ => {}
}

Note that you need an unsafe block to write to a static mut.

The issue here is that you're having a lot of changes. You're using a different chip, with a different HAL, with a different kind of peripheral. And you don't hit your breakpoint. So something is causing that condition to be false.

Maybe you should take a step back. Answer these questions first:

  1. Do you have an oscilloscope. If not, it won't help to toggle GPIOs to see if something is happening. You won't be able to see it anyway. So scratch that idea if you don't have a scope.
  2. Put a breakpoint in the if let but not in the nested if and see if you ever hit the interrupt. Figure out where things go wrong. What does your code do. What is the values of expressions when.
  3. Copy that code from the link above as closely as possible. I.e. use GPIO instead of timer. Let the only difference be the chip model (g071 vs f405). First make sure Mutex works for g071 as well.
#![no_std]
#![no_main]

use panic_halt as _;

use core::cell::RefCell;
use core::ops::DerefMut;
use cortex_m::interrupt::free;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use stm32g0::stm32g071::{self, interrupt, Interrupt, NVIC};

static G_TIM: Mutex<RefCell<Option<stm32g071::TIM2>>> = Mutex::new(RefCell::new(None));
static G_GPIOA: Mutex<RefCell<Option<stm32g071::GPIOA>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    let p = stm32g071::Peripherals::take().unwrap();

    let gpioa = &p.GPIOA;
    let rcc_r = &p.RCC;
    let tim2_ref = &p.TIM2;

    // enable Clock for GPIOA
    rcc_r.iopenr.modify(|_, w| w.iopaen().set_bit());

    // enable Clock for TIM2
    rcc_r.apbenr1.write(|w| w.tim2en().set_bit());

    // Nucleo G071RB LED so need to set as output
    gpioa.moder.modify(|_, w| unsafe { w.moder5().bits(0b01) });

    // prescaler 16000, 16 MHz / 16kH = 1000 Hz
    tim2_ref.psc.write(|w| unsafe { w.psc().bits(16000) });
    // autoreload to 1000 to get 1 Hz interrrupt
    tim2_ref.arr.write(|w| unsafe { w.arr_h().bits(0) });
    tim2_ref.arr.write(|w| unsafe { w.arr_l().bits(1000) });
    // reinitialize timer
    tim2_ref.egr.write(|w| w.ug().set_bit());

    // update interrupt enable bit
    tim2_ref.dier.write(|w| w.uie().set_bit());

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

    let tim2 = p.TIM2;
    free(|cs| {
        G_TIM.borrow(cs).replace(Some(tim2));
    });

    let gpioa = p.GPIOA;
    free(|cs| {
        G_GPIOA.borrow(cs).replace(Some(gpioa));
    });

    let mut increment = 0;
    loop {
        increment += 1;
        if increment > 1000 {
            increment = 0;
        }
    }
}

#[interrupt]
fn TIM2() {
    free(|cs| {
        if let Some(ref mut tim2) = G_TIM.borrow(cs).borrow_mut().deref_mut() {
            tim2.sr.write(|w| w.uif().clear_bit());
        }
    });

    free(|cs| {
        if let Some(ref mut gpioa) = G_GPIOA.borrow(cs).borrow_mut().deref_mut() {
            if gpioa.odr.read().odr5().bit_is_set() {
                gpioa.odr.modify(|_, w| w.odr5().clear_bit());
            } else {
                gpioa.odr.modify(|_, w| w.odr5().set_bit());
            }
        }
    });
}

I wrote something like this to cut out unnecessary code. Both if let Some(...) returns false and I don't know why. I think I should give up and back to Embedded C. :smiley:

Is this for some product you have to get shipped or is it playing around with Rust in a side project?

If the former, go for embedded C all the time. The tools and learning resources are still much more mature than the Rust side.

If the latter, I'd try to stick with it and figure it out. Something is preventing your mutex to yield its contents. I'd start by cutting out all the interrupt stuff and just try to access it in main loop to see if it works in main loop context. Maybe some blocking delay followed by toggling an LED pin protected by the mutex.

However, as I said, I don't really have any experience with embedded Rust. So I'd also join the https://matrix.to/#/#rust-embedded:matrix.org and https://matrix.to/#/#stm32-rs:matrix.org matrix rooms and ask there.

If learning is your goal, then feel free to continue. But if you just want to get something working, I would highly recommend RTIC for this.

It's a lightweight framework to share resources across interrupt tasks. It handles all the Mutex bits for you, and it does static analysis to limit the amount of priority inversion when locking. It ends up being as good or better than you could do manually.

If you do look at RTIC, I would recommend using the v0.6 pre-release version. It's due to be stabilized in the next week or two. I also find it easier to understand and use than v0.5.

Edit: you could also get something working with RTIC, and then inspect the macro-expanded code to see how they implement it. That might also help identity your problem.

I followed instructions in GitHub issue and it worked. I wasn't using Mutex and interrupt::free properly. if let.. in ISR was returning false because interrupt was exectued before replace so... it has value of None in interrupt.

This is my code that works, after fix.

#![no_std]
#![no_main]

use panic_halt as _;

use core::cell::RefCell;
use core::ops::DerefMut;
use cortex_m::interrupt::free;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use stm32g0::stm32g071::{self, interrupt, Interrupt, NVIC};

static G_TIM: Mutex<RefCell<Option<stm32g071::TIM2>>> = Mutex::new(RefCell::new(None));
static G_GPIOA: Mutex<RefCell<Option<stm32g071::GPIOA>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    let p = stm32g071::Peripherals::take().unwrap();

    let gpioa = &p.GPIOA;
    let rcc_r = &p.RCC;

    // enable Clock for GPIOA
    rcc_r.iopenr.modify(|_, w| w.iopaen().set_bit());

    let tim2 = p.TIM2;

    // Nucleo G071RB LED so need to set as output
    gpioa.moder.modify(|_, w| unsafe { w.moder5().bits(0b01) });

    rcc_r.apbenr1.write(|w| w.tim2en().set_bit());
    free(|cs| {
        tim2.cr1.write(|w| w.cen().clear_bit());
        tim2.psc.write(|w| unsafe { w.psc().bits(16000) });
        tim2.arr.write(|w| unsafe { w.arr_l().bits(1000) });
        tim2.egr.write(|w| w.ug().set_bit());
        tim2.dier.write(|w| w.uie().set_bit());
        tim2.cr1.write(|w| w.cen().set_bit());
        G_TIM.borrow(cs).replace(Some(tim2));
    });

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

    let gpioa = p.GPIOA;
    free(|cs| {
        G_GPIOA.borrow(cs).replace(Some(gpioa));
    });

    let mut increment = 0;
    loop {
        increment += 1;
        if increment > 1000 {
            increment = 0;
        }
    }
}

#[interrupt]
fn TIM2() {
    free(|cs| {
        if let Some(ref mut tim2) = G_TIM.borrow(cs).borrow_mut().deref_mut() {
            tim2.sr.write(|w| w.uif().clear_bit());
        }
    });

    free(|cs| {
        if let Some(ref mut gpioa) = G_GPIOA.borrow(cs).borrow_mut().deref_mut() {
            if gpioa.odr.read().odr5().bit_is_set() {
                gpioa.odr.modify(|_, w| w.odr5().clear_bit());
            } else {
                gpioa.odr.modify(|_, w| w.odr5().set_bit());
            }
        }
    });
}

Thank you for your help L0uisc!

1 Like

I want to test strict bare-metal Rust Embedded code. :slight_smile:

1 Like