Interrupts on multi-core
Now let’s combine the single-core interrupt code with our previous wake the second processor code.
This time however, we will have the GPIO in output mode and make it go from high to low ourselves, this so that we generate exactly one interrupt as the physical button can bounce and cause multiple interrupts to trigger, but here I wanted to make it clear that we get exactly one interrupt triggered for each CPU.
#![no_std]
#![no_main]
use aux::start_second_cpu;
use cortex_m::{
asm, interrupt::InterruptNumber,
peripheral::NVIC,
};
pub use cortex_m_rt::entry;
use defmt::info;
mod aux;
mod interrupt {
pub const IO_IRQ_BANK0: isize = 21;
}
#[derive(Clone, Copy)]
struct Interrupt;
unsafe impl InterruptNumber for Interrupt {
fn number(self) -> u16 {
21
}
}
#[cortex_m_rt_macros::interrupt]
fn IO_IRQ_BANK0() {
unsafe {
info!(
"Hello from interrupt at CPU {}!",
// SIO: CPUID Register
core::ptr::read_volatile(
0xd0000000 as *const u32
)
);
// IO_BANK0: INTR2 Register
// 3 GPIO16_EDGE_HIGH WC 0x0
// 2 GPIO16_EDGE_LOW WC 0x0
// 1 GPIO16_LEVEL_HIGH RO 0x0
// 0 GPIO16_LEVEL_LOW RO 0x0
core::ptr::write_volatile(
0x40028238 as *mut u32,
1 << 2 | 1 << 3,
);
}
}
fn second_cpu_entry_point() -> ! {
unsafe {
NVIC::unmask(Interrupt);
}
loop {
asm::wfi();
}
}
#[entry]
fn main() -> ! {
unsafe {
start_second_cpu(second_cpu_entry_point);
NVIC::unmask(Interrupt);
// IO_BANK0: GPIO16_CTRL Register
// Set to use SIO
core::ptr::write_volatile(
0x40028084 as *mut u32,
0x5,
);
// PADS_BANK0: GPIO16 Register
// 0x40: set bit 6: IE: Input enable
core::ptr::write_volatile(
0x40038044 as *mut u32,
0x40,
);
// GPIO_OE_SET
// 0x10000: enable output for bit 16 (pin 16)
core::ptr::write_volatile(
0xd0000038 as *mut u32,
1 << 16,
);
// IO_BANK0: PROC0_INTE2 Register
// Interrupt Enable for processor 0
// Bit 3: GPIO16_EDGE_HIGH
// Bit 2: GPIO16_EDGE_LOW
// Bit 1: GPIO16_LEVEL_HIGH
// Bit 0: GPIO16_LEVEL_LOW
core::ptr::write_volatile(
0x40028250 as *mut u32,
1 << 2,
);
// IO_BANK0: PROC0_INTE2 Register
// Interrupt Enable for processor 1
// Bit 3: GPIO16_EDGE_HIGH
// Bit 2: GPIO16_EDGE_LOW
// Bit 1: GPIO16_LEVEL_HIGH
// Bit 0: GPIO16_LEVEL_LOW
core::ptr::write_volatile(
0x40028298 as *mut u32,
1 << 2,
);
// 0x018 GPIO_OUT_SET
// High
core::ptr::write_volatile(
0xd0000018 as *mut u32,
1 << 16,
);
// 0x020 GPIO_OUT_CLEAR
// Low
core::ptr::write_volatile(
0xd0000020 as *mut u32,
1 << 16,
);
};
loop {
asm::wfi();
}
}
This should give us:
[INFO ] Hello from interrupt at CPU 1! (pico_simple multi-core/src/main.rs:38)
[INFO ] Hello from interrupt at CPU 0! (pico_simple multi-core/src/main.rs:38)
Note that for interrupt to trigger on both CPUs, we need to unmask the interrupt individually for each CPU and also need to set the PROCx_INTE2 registers for each CPU (not that, in practice, you want that the same event to trigger an interrupt on both CPUs, but it’s the case here for didactic purposes).
Cargo.toml changes
To make this work, we had again to make some changes in our Cargo.toml to incorporate again the critical section for multi-core:
[package]
name = "pico-simple"
version = "0.1.0"
edition = "2024"
[dependencies]
cortex-m = "0.7.6"
cortex-m-rt = { version = "0.7.0", features = ["device"] }
cortex-m-rt-macros = "0.7.5"
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
defmt = "1.0.1"
defmt-rtt = "1.0.0"
rp235x-hal = { version = "0.4.0", features = ["critical-section-impl"] }