rp-rs / rp-hal

A Rust Embedded-HAL for the rp series microcontrollers
https://crates.io/crates/rp2040-hal
Apache License 2.0
1.46k stars 237 forks source link

Can't get alarm interrupts working #287

Closed luiswirth closed 2 years ago

luiswirth commented 2 years ago

I'm trying to get interrupts working in my application, but I wasn't able to do so.

I tried to create a minimal setup and I'm not sure what's wrong.

#![no_std]
#![no_main]
#![deny(clippy::all)]
#![deny(unsafe_code)]
#![deny(warnings)]

use core::{
  cell::RefCell,
  sync::atomic::{AtomicBool, Ordering},
};
use cortex_m::interrupt::Mutex;
use embedded_hal::digital::v2::OutputPin;
use embedded_time::duration::Extensions;
use panic_semihosting as _;
use rp_pico::{
  entry,
  hal::{
    self,
    gpio::{bank0::Gpio25, Output, Pin, PushPull},
    timer::Alarm0,
  },
  pac::{self, interrupt},
};

type LedPin = Pin<Gpio25, Output<PushPull>>;

static ALARM: Mutex<RefCell<Option<Alarm0>>> = Mutex::new(RefCell::new(None));
static LED: Mutex<RefCell<Option<LedPin>>> = Mutex::new(RefCell::new(None));
static LED_ON: AtomicBool = AtomicBool::new(false);
static TIMER: Mutex<RefCell<Option<hal::Timer>>> = Mutex::new(RefCell::new(None));

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

  let mut watchdog = hal::Watchdog::new(peripherals.WATCHDOG);

  let _clocks = hal::clocks::init_clocks_and_plls(
    rp_pico::XOSC_CRYSTAL_FREQ,
    peripherals.XOSC,
    peripherals.CLOCKS,
    peripherals.PLL_SYS,
    peripherals.PLL_USB,
    &mut peripherals.RESETS,
    &mut watchdog,
  )
  .ok()
  .unwrap();

  let mut timer = hal::Timer::new(peripherals.TIMER, &mut peripherals.RESETS);
  let alarm = timer.alarm_0().unwrap();

  let sio = hal::Sio::new(peripherals.SIO);

  let pins = rp_pico::Pins::new(
    peripherals.IO_BANK0,
    peripherals.PADS_BANK0,
    sio.gpio_bank0,
    &mut peripherals.RESETS,
  );

  let led = pins.led.into_push_pull_output();

  cortex_m::interrupt::free(|cs| {
    ALARM.borrow(cs).replace(Some(alarm));
    LED.borrow(cs).replace(Some(led));
    TIMER.borrow(cs).replace(Some(timer));

    let mut alarm = ALARM.borrow(cs).borrow_mut();
    let alarm = alarm.as_mut().unwrap();
    let mut timer = TIMER.borrow(cs).borrow_mut();
    let timer = timer.as_mut().unwrap();
    let mut led = LED.borrow(cs).borrow_mut();
    let led = led.as_mut().unwrap();

    alarm.schedule(500_000.microseconds()).unwrap();
    alarm.enable_interrupt(timer);

    led.set_high().unwrap();
    LED_ON.store(true, Ordering::Release);
  });

  #[allow(unsafe_code)]
  unsafe {
    pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0);
  }

  // loop and set timer interrupts to switch led on and off
  // led on after 1.5 seconds and off after 0.5 seconds
  loop {
    cortex_m::interrupt::free(|cs| {
      let mut alarm = ALARM.borrow(cs).borrow_mut();
      let alarm = alarm.as_mut().unwrap();

      if alarm.finished() {
        let mut timer = TIMER.borrow(cs).borrow_mut();
        let timer = timer.as_mut().unwrap();

        if LED_ON.load(Ordering::Acquire) {
          alarm.schedule(500_000.microseconds()).unwrap();
        } else {
          alarm.schedule(1_500_000.microseconds()).unwrap();
        }
        alarm.enable_interrupt(timer);
      }
    });
  }
}

#[allow(non_snake_case)]
#[interrupt]
fn TIMER_IRQ_0() {
  cortex_m::interrupt::free(|cs| {
    let mut alarm = ALARM.borrow(cs).borrow_mut();
    let alarm = alarm.as_mut().unwrap();
    let mut timer = TIMER.borrow(cs).borrow_mut();
    let timer = timer.as_mut().unwrap();
    let mut led = LED.borrow(cs).borrow_mut();
    let led = led.as_mut().unwrap();

    let is_high = LED_ON.load(Ordering::Acquire);
    if is_high {
      led.set_low().unwrap();
    } else {
      led.set_high().unwrap();
    }
    LED_ON.store(!is_high, Ordering::Release);

    alarm.clear_interrupt(timer);
  });
}

My pico doesn't blink and the main "thread" execution gets stuck on pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0);

jannic commented 2 years ago

It seems to work for me - at least, the LED starts blinking. However, it blinks irregularly, so I'm not sure if everything is working as intended. To make sure there is no difference in dependencies etc., I set up a git repo with a full binary crate at https://github.com/jannic/rp-hal-issue-287-test All I need to do to get the LED blinking is calling cargo run (with an rp pico connected via hs-probe, but should work with other probes the same way).

luiswirth commented 2 years ago

I cloned your repo and tested it using a picoprobe. I don't get any blinking (neither in debug nor release). The LED just stays on.

jannic commented 2 years ago

Found the reason for the irregular timing. When the alarm fires, there are two different things that could happen:

Because the loop is inside a critical section most of the time, these two cases happen with similar probabilities, leading to the strange irregular blinking.

While not really a solution, adding cortex_m::asm::delay(10000); at the beginning of the loop (but outside the critical section!) completely changes the timing, so the interrupt fires during that delay most of the times, and executes immediately.

BTW, line 104 is not necessary, the interrupt is already enabled since line 77.

jannic commented 2 years ago

I cloned your repo and tested it using a picoprobe. I don't get any blinking (neither in debug nor release). The LED just stays on.

Strange. For me, it also works when flashing the uf2 file (using bootsel mode, without a debug probe).

luiswirth commented 2 years ago

I got it working, with and without the delay!

For some reason loading from gdb and directly from openocd makes a difference... With gdb it's somehow broken. Maybe it prevents interrupts?

luiswirth commented 2 years ago

This seems to be gdb related and unrelated to rp-rs. Feel free to close.

luiswirth commented 2 years ago

What do you think of adding this or a similar interrupt_blinky example to the code base?

jannic commented 2 years ago

Great that it works! Not sure about gdb, I can't imagine it completely prevents interrupts, but of course it may cause some subtle differences. I don't know much about gdb and openocd, so I probably can't help with that. Also, not sure about such an example. There are already some interrupt examples in boards/rp-pico/examples/, and pico_rtic.rs is covering timer interrupts. That's together with RTIC, but we can't cover every combination of features with a separate example, I guess.

jannic commented 2 years ago

(BTW, I don't have the rights necessary to close the issue. I guess you can do it yourself, as you created it.)

luiswirth commented 2 years ago

Oh I didn't know about these examples! I only looked at the rp2040-hal examples. Thanks for letting me know and helping.