mirror of
https://github.com/italicsjenga/rp-hal-boards.git
synced 2025-01-10 04:21:32 +11:00
RAM-based interrupt vector tables (#321)
* Add struct VectorTable to represent an interrupt vector table * Add member function to VectorTable to initialise based on the current Interrupt Vector Table from VTOR * Add member function to VectorTable to register an extern "C" function to call on interrupt * Add example using VectorTable to demonstrate initialisation and interrupt function registration
This commit is contained in:
parent
575aba4bfe
commit
2bbc52ffce
190
rp2040-hal/examples/vector_table.rs
Normal file
190
rp2040-hal/examples/vector_table.rs
Normal file
|
@ -0,0 +1,190 @@
|
|||
//! # RAM Vector Table example
|
||||
//!
|
||||
//! This application demonstrates how to create a new Interrupt Vector Table in RAM.
|
||||
//! To demonstrate the extra utility of this, we also replace an entry in the Vector Table
|
||||
//! with a new one.
|
||||
//!
|
||||
//! See the `Cargo.toml` file for Copyright and license details.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
// The macro for our start-up function
|
||||
use cortex_m_rt::entry;
|
||||
|
||||
// Ensure we halt the program on panic
|
||||
use panic_halt as _;
|
||||
|
||||
// Alias for our HAL crate
|
||||
use rp2040_hal as hal;
|
||||
|
||||
// A shorter alias for the Peripheral Access Crate
|
||||
use hal::pac;
|
||||
|
||||
// Some traits we need
|
||||
use core::cell::RefCell;
|
||||
use cortex_m::interrupt::Mutex;
|
||||
use embedded_hal::digital::v2::ToggleableOutputPin;
|
||||
use embedded_time::duration::Microseconds;
|
||||
use embedded_time::fixed_point::FixedPoint;
|
||||
use pac::interrupt;
|
||||
use rp2040_hal::clocks::Clock;
|
||||
use rp2040_hal::timer::Alarm;
|
||||
use rp2040_hal::vector_table::VectorTable;
|
||||
|
||||
// Memory that will hold our vector table in RAM
|
||||
static mut RAM_VTABLE: VectorTable = VectorTable::new();
|
||||
|
||||
// Give our LED and Alarm a type alias to make it easier to refer to them
|
||||
type LedAndAlarm = (
|
||||
hal::gpio::Pin<hal::gpio::bank0::Gpio25, hal::gpio::PushPullOutput>,
|
||||
hal::timer::Alarm0,
|
||||
);
|
||||
|
||||
// Place our LED and Alarm type in a static variable, so we can access it from interrupts
|
||||
static mut LED_AND_ALARM: Mutex<RefCell<Option<LedAndAlarm>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
// Period that each of the alarms will be set for - 1 second and 300ms respectively
|
||||
const SLOW_BLINK_INTERVAL_US: u32 = 1_000_000;
|
||||
const FAST_BLINK_INTERVAL_US: u32 = 300_000;
|
||||
|
||||
/// The linker will place this boot block at the start of our program image. We
|
||||
/// need this to help the ROM bootloader get our code up and running.
|
||||
#[link_section = ".boot2"]
|
||||
#[used]
|
||||
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
|
||||
|
||||
/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust
|
||||
/// if your board has a different frequency
|
||||
const XTAL_FREQ_HZ: u32 = 12_000_000u32;
|
||||
|
||||
/// Entry point to our bare-metal application.
|
||||
///
|
||||
/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function
|
||||
/// as soon as all global variables are initialised.
|
||||
///
|
||||
/// The function configures the RP2040 peripherals, then toggles a GPIO pin in
|
||||
/// an infinite loop. If there is an LED connected to that pin, it will blink.
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
// Grab our singleton objects
|
||||
let mut pac = pac::Peripherals::take().unwrap();
|
||||
let core = pac::CorePeripherals::take().unwrap();
|
||||
|
||||
// Set up the watchdog driver - needed by the clock setup code
|
||||
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
|
||||
// The single-cycle I/O block controls our GPIO pins
|
||||
let sio = hal::Sio::new(pac.SIO);
|
||||
|
||||
// Need to make a reference to the Peripheral Base at this scope to avoid confusing the borrow checker
|
||||
let ppb = &mut pac.PPB;
|
||||
unsafe {
|
||||
// Copy the vector table that cortex_m_rt produced into the RAM vector table
|
||||
RAM_VTABLE.init(ppb);
|
||||
// Replace the function that is called on Alarm0 interrupts with a new one
|
||||
RAM_VTABLE.register_handler(pac::Interrupt::TIMER_IRQ_0 as usize, timer_irq0_replacement);
|
||||
}
|
||||
|
||||
// Configure the clocks
|
||||
let clocks = hal::clocks::init_clocks_and_plls(
|
||||
XTAL_FREQ_HZ,
|
||||
pac.XOSC,
|
||||
pac.CLOCKS,
|
||||
pac.PLL_SYS,
|
||||
pac.PLL_USB,
|
||||
&mut pac.RESETS,
|
||||
&mut watchdog,
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
|
||||
// Create simple delay
|
||||
let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer());
|
||||
|
||||
// Set the pins to their default state
|
||||
let pins = hal::gpio::Pins::new(
|
||||
pac.IO_BANK0,
|
||||
pac.PADS_BANK0,
|
||||
sio.gpio_bank0,
|
||||
&mut pac.RESETS,
|
||||
);
|
||||
|
||||
// Configure GPIO25 as an output
|
||||
let led_pin = pins.gpio25.into_push_pull_output();
|
||||
|
||||
let mut timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS);
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
let mut alarm = timer.alarm_0().unwrap();
|
||||
// Schedule an alarm in 1 second
|
||||
let _ = alarm.schedule(Microseconds(SLOW_BLINK_INTERVAL_US));
|
||||
// Enable generating an interrupt on alarm
|
||||
alarm.enable_interrupt();
|
||||
// Move alarm into ALARM, so that it can be accessed from interrupts
|
||||
unsafe {
|
||||
LED_AND_ALARM.borrow(cs).replace(Some((led_pin, alarm)));
|
||||
}
|
||||
});
|
||||
// Unmask the timer0 IRQ so that it will generate an interrupt
|
||||
unsafe {
|
||||
pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0);
|
||||
}
|
||||
|
||||
// After 5 seconds, switch to our modified vector rable
|
||||
delay.delay_ms(5000);
|
||||
unsafe {
|
||||
cortex_m::interrupt::free(|_| {
|
||||
RAM_VTABLE.activate(ppb);
|
||||
});
|
||||
}
|
||||
|
||||
loop {
|
||||
// Wait for an interrupt to fire before doing any more work
|
||||
cortex_m::asm::wfi();
|
||||
}
|
||||
}
|
||||
|
||||
// Regular interrupt handler for Alarm0. The `interrupt` macro will perform some transformations to ensure
|
||||
// that this interrupt entry ends up in the vector table.
|
||||
#[interrupt]
|
||||
fn TIMER_IRQ_0() {
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
// Temporarily take our LED_AND_ALARM
|
||||
let ledalarm = unsafe { LED_AND_ALARM.borrow(cs).take() };
|
||||
if let Some((mut led, mut alarm)) = ledalarm {
|
||||
// Clear the alarm interrupt or this interrupt service routine will keep firing
|
||||
alarm.clear_interrupt();
|
||||
// Schedule a new alarm after SLOW_BLINK_INTERVAL_US have passed (1 second)
|
||||
let _ = alarm.schedule(Microseconds(SLOW_BLINK_INTERVAL_US));
|
||||
// Blink the LED so we know we hit this interrupt
|
||||
led.toggle().unwrap();
|
||||
// Return LED_AND_ALARM into our static variable
|
||||
unsafe {
|
||||
LED_AND_ALARM
|
||||
.borrow(cs)
|
||||
.replace_with(|_| Some((led, alarm)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This is the function we will use to replace TIMER_IRQ_0 in our RAM Vector Table
|
||||
extern "C" fn timer_irq0_replacement() {
|
||||
cortex_m::interrupt::free(|cs| {
|
||||
let ledalarm = unsafe { LED_AND_ALARM.borrow(cs).take() };
|
||||
if let Some((mut led, mut alarm)) = ledalarm {
|
||||
// Clear the alarm interrupt or this interrupt service routine will keep firing
|
||||
alarm.clear_interrupt();
|
||||
// Schedule a new alarm after FAST_BLINK_INTERVAL_US have passed (300 milliseconds)
|
||||
let _ = alarm.schedule(Microseconds(FAST_BLINK_INTERVAL_US));
|
||||
led.toggle().unwrap();
|
||||
// Return LED_AND_ALARM into our static variable
|
||||
unsafe {
|
||||
LED_AND_ALARM
|
||||
.borrow(cs)
|
||||
.replace_with(|_| Some((led, alarm)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// End of file
|
|
@ -40,6 +40,7 @@ pub mod timer;
|
|||
pub mod typelevel;
|
||||
pub mod uart;
|
||||
pub mod usb;
|
||||
pub mod vector_table;
|
||||
pub mod watchdog;
|
||||
pub mod xosc;
|
||||
|
||||
|
|
84
rp2040-hal/src/vector_table.rs
Normal file
84
rp2040-hal/src/vector_table.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
//! Interrupt vector table utilities
|
||||
//!
|
||||
//! Provide functionality to switch to another vector table using the
|
||||
//! Vector Table Offset Register (VTOR) of the Cortex-M0+
|
||||
//! Also provides types and utilities for copying a vector table into RAM
|
||||
|
||||
/// Entry for a Vector in the Interrupt Vector Table.
|
||||
///
|
||||
/// Each entry in the Vector table is a union with usize to allow it to be 0 initialized via const initializer
|
||||
///
|
||||
/// Implementation borrowed from https://docs.rs/cortex-m-rt/0.7.1/cortex_m_rt/index.html#__interrupts
|
||||
#[derive(Clone, Copy)]
|
||||
union Vector {
|
||||
handler: extern "C" fn(),
|
||||
reserved: usize,
|
||||
}
|
||||
|
||||
/// Data type for a properly aligned interrupt vector table
|
||||
///
|
||||
/// The VTOR register can only point to a 256 byte offsets - see
|
||||
/// [Cortex-M0+ Devices Generic User Guide](https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Processor/Exception-model/Vector-table) -
|
||||
/// so that is our required alignment.
|
||||
/// The vector table length depends on the number of interrupts the system supports.
|
||||
/// The first 16 words are defined in the ARM Cortex-M spec.
|
||||
/// The M0+ cores on RP2040 have 32 interrupts, of which only 26 are wired to external interrupt
|
||||
/// signals - but the last 6 can be used for software interrupts so leave room for them
|
||||
#[repr(C, align(256))]
|
||||
pub struct VectorTable {
|
||||
/// SP + Reset vector + 14 exceptions + 32 interrupts = 48 entries (192 bytes) in an RP2040 core's VectorTable
|
||||
table: [Vector; 48],
|
||||
}
|
||||
|
||||
impl VectorTable {
|
||||
/// Create a new vector table. All entries will point to 0 - you must call init()
|
||||
/// on this to copy the current vector table before setting it as active
|
||||
pub const fn new() -> VectorTable {
|
||||
VectorTable {
|
||||
table: [Vector { reserved: 0 }; 48],
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise our vector table by copying the current table on top of it
|
||||
pub fn init(&mut self, ppb: &mut pac::PPB) {
|
||||
let vector_table = ppb.vtor.read().bits();
|
||||
unsafe {
|
||||
crate::rom_data::memcpy44(
|
||||
&mut self.table as *mut _ as *mut u32,
|
||||
vector_table as *const u32,
|
||||
192,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Dynamically register a function as being an interrupt handler
|
||||
pub fn register_handler(&mut self, interrupt_idx: usize, interrupt_fn: extern "C" fn()) {
|
||||
self.table[16 + interrupt_idx].handler = interrupt_fn;
|
||||
}
|
||||
|
||||
/// Set the stack pointer address in a VectorTable. This will be used on Reset
|
||||
///
|
||||
/// # Safety
|
||||
/// There is no checking whether this is a valid stack pointer address
|
||||
pub unsafe fn set_sp(&mut self, stack_pointer_address: usize) {
|
||||
self.table[0].reserved = stack_pointer_address;
|
||||
}
|
||||
|
||||
/// Set the entry-point address in a VectorTable. This will be used on Reset
|
||||
///
|
||||
/// # Safety
|
||||
/// There is no checking whether this is a valid entry point
|
||||
pub unsafe fn set_entry(&mut self, entry_address: usize) {
|
||||
self.table[1].reserved = entry_address;
|
||||
}
|
||||
|
||||
/// Switch the current core to use this Interrupt Vector Table
|
||||
///
|
||||
/// # Safety
|
||||
/// Until the vector table has valid entries, activating it will cause an unhandled hardfault!
|
||||
/// You must call init() first.
|
||||
pub unsafe fn activate(&mut self, ppb: &mut pac::PPB) {
|
||||
ppb.vtor
|
||||
.write(|w| w.bits(&mut self.table as *mut _ as *mut u32 as u32));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue