Skip to content

Commit

Permalink
RAM-based interrupt vector tables (rp-rs#321)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
9names authored and ExplodingWaffle committed Aug 14, 2022
1 parent 4e8e10e commit 4c870c5
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 0 deletions.
190 changes: 190 additions & 0 deletions rp2040-hal/examples/vector_table.rs
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions rp2040-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
84 changes: 84 additions & 0 deletions rp2040-hal/src/vector_table.rs
Original file line number Diff line number Diff line change
@@ -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));
}
}

0 comments on commit 4c870c5

Please sign in to comment.