Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrupts on RISC-V #847

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions rp235x-hal-examples/riscv_examples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ block_loop
dht11
gpio_dyn_pin_array
gpio_in_out
gpio_irq_example
i2c
i2c_async
i2c_async_cancelled
lcd_display
mem_to_mem_dma
pio_blink
pio_dma
pio_proc_blink
pio_side_set
pio_synchronized
powman_test
pwm_blink
pwm_blink_embedded_hal_1
pwm_irq_input
rom_funcs
rosc_as_system_clock
spi
Expand Down
45 changes: 45 additions & 0 deletions rp235x-hal-examples/rp235x_riscv.x
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,51 @@ PROVIDE(__pre_init = default_pre_init);
/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */
PROVIDE(_setup_interrupts = default_setup_interrupts);

PROVIDE(TIMER0_IRQ_0 = DefaultIrqHandler);
PROVIDE(TIMER0_IRQ_1 = DefaultIrqHandler);
PROVIDE(TIMER0_IRQ_2 = DefaultIrqHandler);
PROVIDE(TIMER0_IRQ_3 = DefaultIrqHandler);
PROVIDE(TIMER1_IRQ_0 = DefaultIrqHandler);
PROVIDE(TIMER1_IRQ_1 = DefaultIrqHandler);
PROVIDE(TIMER1_IRQ_2 = DefaultIrqHandler);
PROVIDE(TIMER1_IRQ_3 = DefaultIrqHandler);
PROVIDE(PWM_IRQ_WRAP_0 = DefaultIrqHandler);
PROVIDE(PWM_IRQ_WRAP_1 = DefaultIrqHandler);
PROVIDE(DMA_IRQ_0 = DefaultIrqHandler);
PROVIDE(DMA_IRQ_1 = DefaultIrqHandler);
PROVIDE(DMA_IRQ_2 = DefaultIrqHandler);
PROVIDE(DMA_IRQ_3 = DefaultIrqHandler);
PROVIDE(USBCTRL_IRQ = DefaultIrqHandler);
PROVIDE(PIO0_IRQ_0 = DefaultIrqHandler);
PROVIDE(PIO0_IRQ_1 = DefaultIrqHandler);
PROVIDE(PIO1_IRQ_0 = DefaultIrqHandler);
PROVIDE(PIO1_IRQ_1 = DefaultIrqHandler);
PROVIDE(PIO2_IRQ_0 = DefaultIrqHandler);
PROVIDE(PIO2_IRQ_1 = DefaultIrqHandler);
PROVIDE(IO_IRQ_BANK0 = DefaultIrqHandler);
PROVIDE(IO_IRQ_BANK0_NS = DefaultIrqHandler);
PROVIDE(IO_IRQ_QSPI = DefaultIrqHandler);
PROVIDE(IO_IRQ_QSPI_NS = DefaultIrqHandler);
PROVIDE(SIO_IRQ_FIFO = DefaultIrqHandler);
PROVIDE(SIO_IRQ_BELL = DefaultIrqHandler);
PROVIDE(SIO_IRQ_FIFO_NS = DefaultIrqHandler);
PROVIDE(SIO_IRQ_BELL_NS = DefaultIrqHandler);
PROVIDE(SIO_IRQ_MTIMECMP = DefaultIrqHandler);
PROVIDE(CLOCKS_IRQ = DefaultIrqHandler);
PROVIDE(SPI0_IRQ = DefaultIrqHandler);
PROVIDE(SPI1_IRQ = DefaultIrqHandler);
PROVIDE(UART0_IRQ = DefaultIrqHandler);
PROVIDE(UART1_IRQ = DefaultIrqHandler);
PROVIDE(ADC_IRQ_FIFO = DefaultIrqHandler);
PROVIDE(I2C0_IRQ = DefaultIrqHandler);
PROVIDE(I2C1_IRQ = DefaultIrqHandler);
PROVIDE(OTP_IRQ = DefaultIrqHandler);
PROVIDE(TRNG_IRQ = DefaultIrqHandler);
PROVIDE(PLL_SYS_IRQ = DefaultIrqHandler);
PROVIDE(PLL_USB_IRQ = DefaultIrqHandler);
PROVIDE(POWMAN_IRQ_POW = DefaultIrqHandler);
PROVIDE(POWMAN_IRQ_TIMER = DefaultIrqHandler);

/* # Multi-processing hook function
fn _mp_hook() -> bool;

Expand Down
92 changes: 46 additions & 46 deletions rp235x-hal-examples/src/bin/gpio_irq_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,14 @@ use panic_halt as _;
// Alias for our HAL crate
use rp235x_hal as hal;

// Some things we need
// Some traits we need
use embedded_hal::digital::StatefulOutputPin;

// Our interrupt macro
use hal::pac::interrupt;

// Some short-cuts to useful types
// Some more helpful aliases
use core::cell::RefCell;
use critical_section::Mutex;
use hal::gpio;

// The GPIO interrupt type we're going to generate
use gpio::Interrupt::EdgeLow;

/// Tell the Boot ROM about our application
#[link_section = ".start_block"]
#[used]
Expand Down Expand Up @@ -70,7 +64,7 @@ type LedAndButton = (LedPin, ButtonPin);
/// This how we transfer our Led and Button pins into the Interrupt Handler.
/// We'll have the option hold both using the LedAndButton type.
/// This will make it a bit easier to unpack them later.
static GLOBAL_PINS: Mutex<RefCell<Option<LedAndButton>>> = Mutex::new(RefCell::new(None));
static GLOBAL_STATE: Mutex<RefCell<Option<LedAndButton>>> = Mutex::new(RefCell::new(None));

/// Entry point to our bare-metal application.
///
Expand Down Expand Up @@ -120,20 +114,25 @@ fn main() -> ! {

// Trigger on the 'falling edge' of the input pin.
// This will happen as the button is being pressed
in_pin.set_interrupt_enabled(EdgeLow, true);
in_pin.set_interrupt_enabled(gpio::Interrupt::EdgeLow, true);

// Give away our pins by moving them into the `GLOBAL_PINS` variable.
// We won't need to access them in the main thread again
critical_section::with(|cs| {
GLOBAL_PINS.borrow(cs).replace(Some((led, in_pin)));
GLOBAL_STATE.borrow(cs).replace(Some((led, in_pin)));
});

// Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller
// will jump to the interrupt function when the interrupt occurs.
// We do this last so that the interrupt can't go off while
// it is in the middle of being configured
// Unmask the IRQ for I/O Bank 0 so that the RP2350's interrupt controller
// (NVIC in Arm mode, or Xh3irq in RISC-V mode) will jump to the interrupt
// function when the interrupt occurs. We do this last so that the interrupt
// can't go off while it is in the middle of being configured
unsafe {
hal::arch::interrupt_unmask(hal::pac::Interrupt::IO_IRQ_BANK0);
}

// Enable interrupts on this core
unsafe {
cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::IO_IRQ_BANK0);
hal::arch::interrupt_enable();
}

loop {
Expand All @@ -142,38 +141,39 @@ fn main() -> ! {
}
}

#[allow(static_mut_refs)] // See https://github.com/rust-embedded/cortex-m/pull/561
#[interrupt]
/// This is the interrupt handler that fires when GPIO Bank 0 detects an event
/// (like an edge).
///
/// We give it an unmangled name so that it replaces the default (empty)
/// handler. These handlers are referred to by name from the Interrupt Vector
/// Table created by cortex-m-rt.
#[allow(non_snake_case)]
#[no_mangle]
fn IO_IRQ_BANK0() {
// The `#[interrupt]` attribute covertly converts this to `&'static mut Option<LedAndButton>`
static mut LED_AND_BUTTON: Option<LedAndButton> = None;

// This is one-time lazy initialisation. We steal the variables given to us
// via `GLOBAL_PINS`.
if LED_AND_BUTTON.is_none() {
critical_section::with(|cs| {
*LED_AND_BUTTON = GLOBAL_PINS.borrow(cs).take();
});
}

// Need to check if our Option<LedAndButtonPins> contains our pins
if let Some(gpios) = LED_AND_BUTTON {
// borrow led and button by *destructuring* the tuple
// these will be of type `&mut LedPin` and `&mut ButtonPin`, so we don't have
// to move them back into the static after we use them
let (led, button) = gpios;
// Check if the interrupt source is from the pushbutton going from high-to-low.
// Note: this will always be true in this example, as that is the only enabled GPIO interrupt source
if button.interrupt_status(EdgeLow) {
// toggle can't fail, but the embedded-hal traits always allow for it
// we can discard the return value by assigning it to an unnamed variable
let _ = led.toggle();

// Our interrupt doesn't clear itself.
// Do that now so we don't immediately jump back to this interrupt handler.
button.clear_interrupt(EdgeLow);
// Enter a critical section to ensure this code cannot be concurrently
// executed on the other core. This also protects us if the main thread
// decides to execute this function (which it shouldn't, but we can't stop
// them if they wanted to).
critical_section::with(|cs| {
// Grab a mutable reference to the global state, using the CS token as
// proof we have turned off interrupts. Performs a run-time borrow check
// of the RefCell to ensure no-one else is currently borrowing it (and
// they shouldn't, because we're in a critical section right now).
let mut maybe_state = GLOBAL_STATE.borrow_ref_mut(cs);
// Need to check if our Option<LedAndButtonPins> contains our pins
if let Some((led, button)) = maybe_state.as_mut() {
// Check if the interrupt source is from the pushbutton going from high-to-low.
// Note: this will always be true in this example, as that is the only enabled GPIO interrupt source
if button.interrupt_status(gpio::Interrupt::EdgeLow) {
// toggle can't fail, but the embedded-hal traits always allow for it
// we can discard the return value by assigning it to an unnamed variable
let _ = led.toggle();
// Our interrupt doesn't clear itself.
// Do that now so we don't immediately jump back to this interrupt handler.
button.clear_interrupt(gpio::Interrupt::EdgeLow);
}
}
}
});
}

/// Program metadata for `picotool info`
Expand Down
16 changes: 9 additions & 7 deletions rp235x-hal-examples/src/bin/i2c_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use hal::{
gpio::bank0::{Gpio20, Gpio21},
gpio::{FunctionI2C, Pin, PullUp},
i2c::Controller,
pac::interrupt,
Clock, I2C,
};

Expand All @@ -42,7 +41,8 @@ pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe();
const XTAL_FREQ_HZ: u32 = 12_000_000u32;

/// Bind the interrupt handler with the peripheral
#[interrupt]
#[no_mangle]
#[allow(non_snake_case)]
unsafe fn I2C0_IRQ() {
use hal::async_utils::AsyncPeripheral;
I2C::<hal::pac::I2C0, (Gpio20, Gpio21), Controller>::on_interrupt();
Expand Down Expand Up @@ -97,14 +97,16 @@ async fn demo() {
clocks.system_clock.freq(),
);

// Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler.
// Each core has its own NVIC so these needs to executed from the core where the IRQ are
// expected.
// Unmask the IRQ for I2C0. We do this after the driver init so that the
// interrupt can't go off while it is in the middle of being configured
unsafe {
cortex_m::peripheral::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ);
cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ);
hal::arch::interrupt_unmask(hal::pac::Interrupt::I2C0_IRQ);
}

// Enable interrupts on this core
unsafe {
hal::arch::interrupt_enable();
}
// Asynchronously write three bytes to the I²C device with 7-bit address 0x2C
i2c.write(0x76u8, &[1, 2, 3]).await.unwrap();

Expand Down
15 changes: 10 additions & 5 deletions rp235x-hal-examples/src/bin/i2c_async_cancelled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use hal::{
gpio::bank0::{Gpio20, Gpio21},
gpio::{FunctionI2C, Pin, PullUp},
i2c::Controller,
pac::interrupt,
Clock, I2C,
};

Expand All @@ -44,7 +43,8 @@ pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe();
/// Adjust if your board has a different frequency
const XTAL_FREQ_HZ: u32 = 12_000_000u32;

#[interrupt]
#[no_mangle]
#[allow(non_snake_case)]
unsafe fn I2C0_IRQ() {
use hal::async_utils::AsyncPeripheral;
I2C::<hal::pac::I2C0, (Gpio20, Gpio21), Controller>::on_interrupt();
Expand Down Expand Up @@ -99,10 +99,15 @@ async fn demo() {
clocks.system_clock.freq(),
);

// Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler.
// Unmask the IRQ for I2C0. We do this after the driver init so that the
// interrupt can't go off while it is in the middle of being configured
unsafe {
cortex_m::peripheral::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ);
cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ);
hal::arch::interrupt_unmask(hal::pac::Interrupt::I2C0_IRQ);
}

// Enable interrupts on this core
unsafe {
hal::arch::interrupt_enable();
}

let mut cnt = 0;
Expand Down
Loading
Loading