forked from rp-rs/rp-hal
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RAM-based interrupt vector tables (rp-rs#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
- Loading branch information
1 parent
4e8e10e
commit 4c870c5
Showing
3 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |