diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index a835bc879f6..9416d7bcdea 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GPIO: `Input::wait_for_with_options()` allows waking from light sleep while waiting for event (#5551) ### Changed +- ESP32-S2, ESP32-S3: Renamed `UlpWakeupSource` to `WakeFromUlpCoreWakeupSource`, to differentiate it from `UlpCoreWakeupSource` (#5206) - The clock frequency accessor functions no longer need to lock the clock tree (#5461) - SPI: `SpiDmaBus` has been merged into `SpiDma`. `with_buffers` now returns `SpiDma` directly, and the buffer-taking transfer methods have been renamed to `read_buffer`, `write_buffer`, `transfer_buffers`, `half_duplex_read_buffer` and `half_duplex_write_buffer` to avoid conflicts with the `SpiBus` trait methods. (#5272) @@ -102,6 +103,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A `PsramMode` option has been introduced for ESP32-S3. The default mode is `Auto` which will try to detect if PSRAM works via Octal or Quad SPI and configure it accordingly. (#5334) - Add I2S loopback logic to the peripheral driver. (#5349) - SPI master: added `min_async_transfer_size` config option to force small transfers to use blocking/CPU driven mode. (#5350) +- ESP32-S2, ESP32-S3: Add `wakeup_enable()` method to `LowPowerInput` and `LowPowerOutputOpenDrain`, allowing ULP core to be woken up from GPIO. (#5134) ### Changed diff --git a/esp-hal/src/gpio/lp_io.rs b/esp-hal/src/gpio/lp_io.rs index a48deb6dc53..90b393ab986 100644 --- a/esp-hal/src/gpio/lp_io.rs +++ b/esp-hal/src/gpio/lp_io.rs @@ -30,7 +30,10 @@ use core::marker::PhantomData; use super::{InputPin, OutputPin, RtcPin}; -use crate::peripherals::{GPIO, LP_AON, LP_IO}; +use crate::{ + gpio::WakeEvent, + peripherals::{GPIO, LP_AON, LP_IO}, +}; /// A GPIO output pin configured for low power operation pub struct LowPowerOutput<'d, const PIN: u8> { @@ -111,6 +114,14 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { .gpio(PIN as usize) .modify(|_, w| w.fun_wpd().bit(enable)); } + + /// Allows this pin to wakeup the LP core, when LpCoreWakeupSource::Gpio is used. + pub fn wakeup_enable(&self, event: Option) { + let pin_reg = LP_IO::regs().pin(PIN as usize); + let wake_en = event.is_some(); + let int_type = event.map_or(0, |e| e as u8); + pin_reg.write(|w| unsafe { w.int_type().bits(int_type).wakeup_enable().bit(wake_en) }); + } } /// A GPIO open-drain output pin configured for low power operation diff --git a/esp-hal/src/gpio/rtc_io.rs b/esp-hal/src/gpio/rtc_io.rs index 084f5236a5b..607ebc5b5d5 100644 --- a/esp-hal/src/gpio/rtc_io.rs +++ b/esp-hal/src/gpio/rtc_io.rs @@ -33,7 +33,7 @@ use core::marker::PhantomData; use super::{InputPin, OutputPin, RtcPin}; use crate::{ - gpio::RtcFunction, + gpio::{RtcFunction, WakeEvent}, peripherals::{GPIO, RTC_IO}, }; @@ -85,13 +85,13 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { P: InputPin + RtcPin + 'd, { pin.rtc_set_config(true, true, RtcFunction::Rtc); - let this = Self { phantom: PhantomData, }; this.input_enable(true); this.pullup_enable(false); this.pulldown_enable(false); + // this.wakeup_enable(None); this } @@ -115,6 +115,23 @@ impl<'d, const PIN: u8> LowPowerInput<'d, PIN> { .touch_pad(PIN as usize) .modify(|_, w| w.rde().bit(enable)); } + + /// Allows this pin to wakeup the ULP core, when UlpCoreWakeupSource::Gpio is used. + pub fn wakeup_enable(&self, event: Option) { + let pin_reg_rtc = RTC_IO::regs().pin(PIN as usize); + let wake_en = event.is_some(); + let int_type = event.map_or(0, |e| e as u8); + + #[cfg(esp32s3)] + pin_reg_rtc.write(|w| unsafe { w.int_type().bits(int_type).wakeup_enable().bit(wake_en) }); + #[cfg(esp32s2)] + pin_reg_rtc.write(|w| unsafe { + w.gpio_pin_int_type() + .bits(int_type) + .gpio_pin_wakeup_enable() + .bit(wake_en) + }); + } } /// A GPIO open-drain output pin configured for low power operation diff --git a/esp-hal/src/rtc_cntl/sleep/esp32s2.rs b/esp-hal/src/rtc_cntl/sleep/esp32s2.rs index 02f29ed89c6..bd0e3927dc6 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32s2.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32s2.rs @@ -2,7 +2,7 @@ use super::{ Ext0WakeupSource, Ext1WakeupSource, TimerWakeupSource, - UlpWakeupSource, + WakeFromUlpCoreWakeupSource, WakeSource, WakeTriggers, WakeupLevel, @@ -74,7 +74,7 @@ pub const RTC_MEM_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; /// RTC memory wait cycles. pub const RTC_MEM_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; -impl WakeSource for UlpWakeupSource { +impl WakeSource for WakeFromUlpCoreWakeupSource { fn apply( &self, _rtc: &Rtc<'_>, diff --git a/esp-hal/src/rtc_cntl/sleep/esp32s3.rs b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs index bd520c32724..4b215269059 100644 --- a/esp-hal/src/rtc_cntl/sleep/esp32s3.rs +++ b/esp-hal/src/rtc_cntl/sleep/esp32s3.rs @@ -2,7 +2,7 @@ use super::{ Ext0WakeupSource, Ext1WakeupSource, TimerWakeupSource, - UlpWakeupSource, + WakeFromUlpCoreWakeupSource, WakeSource, WakeTriggers, WakeupLevel, @@ -82,7 +82,7 @@ pub const RTC_MEM_POWERUP_CYCLES: u8 = OTHER_BLOCKS_POWERUP; /// RTC memory wait cycles. pub const RTC_MEM_WAIT_CYCLES: u16 = OTHER_BLOCKS_WAIT; -impl WakeSource for UlpWakeupSource { +impl WakeSource for WakeFromUlpCoreWakeupSource { fn apply( &self, _rtc: &Rtc<'_>, diff --git a/esp-hal/src/rtc_cntl/sleep/mod.rs b/esp-hal/src/rtc_cntl/sleep/mod.rs index 3381759edaf..e735cb09fda 100644 --- a/esp-hal/src/rtc_cntl/sleep/mod.rs +++ b/esp-hal/src/rtc_cntl/sleep/mod.rs @@ -376,15 +376,15 @@ impl Default for WakeFromLpCoreWakeupSource { /// /// This wakeup source can be used to wake up from both light and deep sleep. #[cfg(any(esp32s2, esp32s3))] -pub struct UlpWakeupSource { +pub struct WakeFromUlpCoreWakeupSource { wake_on_interrupt: bool, wake_on_trap: bool, clear_interrupts_on_sleep: bool, } #[cfg(any(esp32s2, esp32s3))] -impl UlpWakeupSource { - /// Create a new instance of `WakeFromUlpWakeupSource` +impl WakeFromUlpCoreWakeupSource { + /// Create a new instance of `WakeFromUlpCoreWakeupSource` pub const fn new() -> Self { Self { wake_on_interrupt: true, @@ -422,7 +422,7 @@ impl UlpWakeupSource { } #[cfg(any(esp32s2, esp32s3))] -impl Default for UlpWakeupSource { +impl Default for WakeFromUlpCoreWakeupSource { fn default() -> Self { Self::new() } diff --git a/esp-hal/src/soc/esp32c6/lp_core.rs b/esp-hal/src/soc/esp32c6/lp_core.rs index c0595827d15..1c4f1adab03 100644 --- a/esp-hal/src/soc/esp32c6/lp_core.rs +++ b/esp-hal/src/soc/esp32c6/lp_core.rs @@ -26,6 +26,8 @@ use crate::peripherals::{LP_AON, LP_CORE, LP_PERI, LPWR, PMU}; pub enum LpCoreWakeupSource { /// Wakeup source from the HP (High Performance) CPU. HpCpu, + /// Wakeup source from LP IO interrupt status + LpIo, } /// Clock sources for the LP core @@ -119,6 +121,7 @@ fn ulp_lp_core_run(wakeup_src: LpCoreWakeupSource) { // Set wake-up sources let src = match wakeup_src { LpCoreWakeupSource::HpCpu => 0x01, + LpCoreWakeupSource::LpIo => 0x04, }; pmu.lp_cpu_pwr1() .modify(|_, w| unsafe { w.lp_cpu_wakeup_en().bits(src) }); @@ -133,5 +136,6 @@ fn ulp_lp_core_run(wakeup_src: LpCoreWakeupSource) { LpCoreWakeupSource::HpCpu => { pmu.hp_lp_cpu_comm().write(|w| w.hp_trigger_lp().set_bit()); } + LpCoreWakeupSource::LpIo => {} } } diff --git a/esp-hal/src/soc/esp32s2/ulp_core.rs b/esp-hal/src/soc/esp32s2/ulp_core.rs index f36344042dd..1c9f23756c6 100644 --- a/esp-hal/src/soc/esp32s2/ulp_core.rs +++ b/esp-hal/src/soc/esp32s2/ulp_core.rs @@ -51,6 +51,8 @@ pub enum UlpCoreWakeupSource { /// Wakeup after the ULP Timer has elapsed. /// The actual period between wake-ups is affected by the runtime duration of the ULP program. Timer(UlpCoreTimerCycles), + /// Wakeup on GPIO state + Gpio, } /// ULP Timer cycles are clocked at a rate of approximately 8MHz / 32768 = ~244 Hz. @@ -192,5 +194,10 @@ fn ulp_config_wakeup_source(wakeup_src: UlpCoreWakeupSource) { // configure timer duration configure_timer(sleep_cycles.cycles()); } + UlpCoreWakeupSource::Gpio => { + LPWR::regs() + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().set_bit()); + } } } diff --git a/esp-hal/src/soc/esp32s3/ulp_core.rs b/esp-hal/src/soc/esp32s3/ulp_core.rs index ca505151efb..a5a4c6972d1 100644 --- a/esp-hal/src/soc/esp32s3/ulp_core.rs +++ b/esp-hal/src/soc/esp32s3/ulp_core.rs @@ -51,6 +51,8 @@ pub enum UlpCoreWakeupSource { /// Wakeup after the ULP Timer has elapsed. /// The actual period between wake-ups is affected by the runtime duration of the ULP program. Timer(UlpCoreTimerCycles), + /// Wakeup on GPIO state + Gpio, } /// ULP Timer cycles are clocked at a rate of approximately 17.5MHz / 32768 = ~534 Hz. @@ -103,7 +105,7 @@ impl<'d> UlpCore<'d> { /// Stops the ULP core. pub fn stop(&mut self) { - ulp_stop(); + ulp_halt(); } /// Runs the ULP core with the specified wakeup source. @@ -112,28 +114,61 @@ impl<'d> UlpCore<'d> { } } -fn ulp_stop() { +fn ulp_timer_resume() { + let rtc_cntl = LPWR::regs(); + rtc_cntl + .ulp_cp_timer() + .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); +} + +fn ulp_timer_stop() { let rtc_cntl = LPWR::regs(); rtc_cntl .ulp_cp_timer() .modify(|_, w| w.ulp_cp_slp_timer_en().clear_bit()); +} +fn ulp_timer_period(cycles: u32) { + let rtc_cntl = LPWR::regs(); + rtc_cntl + .ulp_cp_timer_1() + .write(|w| unsafe { w.ulp_cp_timer_slp_cycle().bits(cycles << 8) }); + rtc_cntl + .ulp_cp_ctrl() + .modify(|_, w| w.ulp_cp_force_start_top().clear_bit()); +} + +fn ulp_halt() { + ulp_timer_stop(); + let rtc_cntl = LPWR::regs(); // suspends the ulp operation rtc_cntl .cocpu_ctrl() .modify(|_, w| w.cocpu_done().set_bit()); - // Resets the processor rtc_cntl .cocpu_ctrl() .modify(|_, w| w.cocpu_shut_reset_en().set_bit()); +} + +fn ulp_reset() { + let rtc_cntl = LPWR::regs(); + + rtc_cntl.cocpu_ctrl().write(|w| { + w.cocpu_shut().clear_bit(); + w.cocpu_done().clear_bit(); + w.cocpu_shut_reset_en().clear_bit() + }); crate::rom::ets_delay_us(20); - // above doesn't seem to halt the ULP core - this will - rtc_cntl - .cocpu_ctrl() - .modify(|_, w| w.cocpu_clkgate_en().clear_bit()); + rtc_cntl.cocpu_ctrl().write(|w| { + w.cocpu_shut().set_bit(); + w.cocpu_done().set_bit(); + w.cocpu_shut_reset_en().set_bit() + }); + + crate::rom::ets_delay_us(20); } fn ulp_run(wakeup_src: UlpCoreWakeupSource) { @@ -194,34 +229,26 @@ fn ulp_run(wakeup_src: UlpCoreWakeupSource) { .ulp_cp() .clear_bit_by_one() }); - - rtc_cntl - .cocpu_ctrl() - .modify(|_, w| w.cocpu_clkgate_en().set_bit()); } fn ulp_config_wakeup_source(wakeup_src: UlpCoreWakeupSource) { // ESP-IDF source: https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_riscv.c#L87 - fn configure_timer(cycles: u32) { - LPWR::regs() - .ulp_cp_timer_1() - .write(|w| unsafe { w.ulp_cp_timer_slp_cycle().bits(cycles << 8) }); - // enable the timer - LPWR::regs() - .ulp_cp_ctrl() - .modify(|_, w| w.ulp_cp_force_start_top().clear_bit()); - LPWR::regs() - .ulp_cp_timer() - .modify(|_, w| w.ulp_cp_slp_timer_en().set_bit()); - } + match wakeup_src { UlpCoreWakeupSource::HpCpu => { // wake-up immediately - configure_timer(0); + ulp_timer_period(0); + ulp_timer_resume(); } UlpCoreWakeupSource::Timer(sleep_cycles) => { // configure timer duration - configure_timer(sleep_cycles.cycles()); + ulp_timer_period(sleep_cycles.cycles()); + ulp_timer_resume(); + } + UlpCoreWakeupSource::Gpio => { + LPWR::regs() + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().set_bit()); } } } diff --git a/esp-lp-hal/CHANGELOG.md b/esp-lp-hal/CHANGELOG.md index ea09a09a016..ffe4e0ca136 100644 --- a/esp-lp-hal/CHANGELOG.md +++ b/esp-lp-hal/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `wake_hp_core` for ESP32-S2 and ESP32-S3 (#5133) - Add `OutputOpenDrain` GPIO type (#5131) +- ESP32-S2, ESP32-S3: Added ULP interrupt support (#5206) ### Changed - Changed ULP entrypoint to allow `main()` to return, and halt, to the ULP Timer can be used (#5134) @@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix panic when handling buffers larger than 7 bytes in `LpI2c.write` and `LpI2c.read` (#4694) - ESP32-S2, ESP32-S3: Aligned `ulp_riscv_halt()` with ESP_IDF by setting the `cocpu_shut_reset_en` bit, squashing a rare bug where halting the chip would hang (#5134) +- ESP32-S2, ESP32-S3: Prevent `Peripherals::take().unwrap()` from panicking on second ULP Timer `main()`-loop, by clearing `DEVICE_PERIPHERALS` variable on start (#5206) ### Removed diff --git a/esp-lp-hal/Cargo.toml b/esp-lp-hal/Cargo.toml index 9317d03c699..71aecaeb0f1 100644 --- a/esp-lp-hal/Cargo.toml +++ b/esp-lp-hal/Cargo.toml @@ -43,11 +43,13 @@ embedded-io-06 = { package = "embedded-io", version = "0.6", optional = true embedded-io-07 = { package = "embedded-io", version = "0.7", optional = true } nb = { version = "1.1.0", optional = true } procmacros = { version = "0.22.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } -riscv = { version = "0.15", features = ["critical-section-single-hart"] } +critical-section = { version = "1.2.0", features = [ "restore-state-bool" ] } +riscv = { version = "0.15", default-features = false } +portable-atomic = { version = "1", default-features = false } esp-metadata-generated = { version = "0.4.0", path = "../esp-metadata-generated" } -esp32c6-lp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "fc3e6d4" } -esp32s2-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "fc3e6d4" } -esp32s3-ulp = { version = "0.3.0", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "fc3e6d4" } +esp32c6-lp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "46b575d9" } +esp32s2-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "c6f21b60" } +esp32s3-ulp = { version = "0.3.0", features = ["critical-section", "rt"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "c6f21b60" } [dev-dependencies] panic-halt = "0.2.0" @@ -56,7 +58,8 @@ panic-halt = "0.2.0" esp-metadata-generated = { version = "0.4.0", path = "../esp-metadata-generated", features = ["build-script"] } [features] -default = ["embedded-hal"] +default = ["embedded-hal", "interrupts"] +interrupts = [] ## Enable debug features in the HAL (used for development). debug = [ @@ -67,11 +70,11 @@ debug = [ # Chip Support Feature Flags # Target the ESP32-C6. -esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb"] +esp32c6 = ["dep:esp32c6-lp", "esp-metadata-generated/esp32c6", "procmacros/is-lp-core", "dep:nb" ] # Target the ESP32-S2. -esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core"] +esp32s2 = ["dep:esp32s2-ulp", "esp-metadata-generated/esp32s2", "procmacros/is-ulp-core" ] # Target the ESP32-S3. -esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core"] +esp32s3 = ["dep:esp32s3-ulp", "esp-metadata-generated/esp32s3", "procmacros/is-ulp-core" ] #! ### Trait Implementation Feature Flags ## Implement the traits defined in the `1.0.0` releases of `embedded-hal` and @@ -84,6 +87,14 @@ embedded-io = ["dep:embedded-io-06", "dep:embedded-io-07"] name = "blinky" required-features = [] +[[example]] +name = "interrupt_counter" +required-features = [] + +[[example]] +name = "gpio_wakeup" +required-features = [] + [[example]] name = "i2c" required-features = ["esp32c6"] diff --git a/esp-lp-hal/examples/gpio_wakeup.rs b/esp-lp-hal/examples/gpio_wakeup.rs new file mode 100644 index 00000000000..240e771c25f --- /dev/null +++ b/esp-lp-hal/examples/gpio_wakeup.rs @@ -0,0 +1,26 @@ +//! ULP GPIO wakeup example. +//! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is woken +//! up. The HP core will configure GPIO0 to wake up the ULP core, when pressed. + +//% CHIPS: esp32s3 esp32s2 + +#![no_std] +#![no_main] + +extern crate panic_halt; + +use esp_lp_hal::{gpio::Input, gpio_wakeup_clear, prelude::*}; + +const ADDRESS: usize = 0x1000; + +#[entry] +fn main(mut _button: Input<0>) { + // Clear the global GPIO wake-up flag, to prevent repeated wake-ups + gpio_wakeup_clear(); + + // Increment the counter + unsafe { + let counter = ADDRESS as *mut u32; + counter.write_volatile(counter.read_volatile() + 1); + } +} diff --git a/esp-lp-hal/examples/i2c.rs b/esp-lp-hal/examples/i2c.rs index 65e854d8bd5..9d07f744f62 100644 --- a/esp-lp-hal/examples/i2c.rs +++ b/esp-lp-hal/examples/i2c.rs @@ -17,7 +17,7 @@ use panic_halt as _; #[entry] fn main(mut i2c: LpI2c) -> ! { - let _peripherals = esp32c6_lp::Peripherals::take().unwrap(); + let _peripherals = esp_lp_hal::pac::Peripherals::take().unwrap(); loop { let mut data = [0u8; 22]; diff --git a/esp-lp-hal/examples/interrupt_counter.rs b/esp-lp-hal/examples/interrupt_counter.rs new file mode 100644 index 00000000000..64266729f25 --- /dev/null +++ b/esp-lp-hal/examples/interrupt_counter.rs @@ -0,0 +1,78 @@ +//! ULP interrupt-based counter example. +//! Increments a 32 bit counter value at a known point in memory, whenever the ULP program is run. +//! If GPIO0 is pressed, resets the counter. + +//% CHIPS: esp32s3 esp32s2 + +#![no_std] +#![no_main] + +extern crate panic_halt; + +use core::cell::RefCell; + +use critical_section::Mutex; +use esp_lp_hal::{ + gpio::{Event, Input, Io}, + interrupt::{self, Interrupt}, + pac::Peripherals, + prelude::*, +}; + +const ADDRESS: usize = 0x1000; + +static BUTTON: Mutex>>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main(mut button: Input<0>) { + let peripherals = Peripherals::take().unwrap(); + let mut io = Io::new(peripherals.RTC_IO); + io.set_interrupt_handler(gpio_interrupt_handler); + + critical_section::with(|cs| { + button.listen(Event::FallingEdge, false); + BUTTON.borrow_ref_mut(cs).replace(button); + }); + + interrupt::bind_handler(Interrupt::RISCV_START_INT, startup_interrupt_handler); +} + +#[handler] +fn startup_interrupt_handler() { + // Increment the counter every time RISCV_START_INT is triggered + unsafe { + let counter = ADDRESS as *mut u32; + counter.write_volatile(counter.read_volatile() + 1); + } + + // On entry, immediately disable any more start-up interrupts. + // This is needed, because RISCV_START_INT is driven from the ULP Timer, + // which may be called multiple times before main() has finished execution. + interrupt::set_enabled(Interrupt::RISCV_START_INT, false); +} + +#[handler] +fn gpio_interrupt_handler() { + // Check if BUTTON has an interrupt pending + if critical_section::with(|cs| { + BUTTON + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .is_interrupt_set() + }) { + // The button was the source of the interrupt, reset the counter to 0. + unsafe { + let counter = ADDRESS as *mut u32; + counter.write_volatile(0); + } + } + + critical_section::with(|cs| { + BUTTON + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .clear_interrupt() + }); +} diff --git a/esp-lp-hal/ld/link-lp.x b/esp-lp-hal/ld/link-lp.x index f0471d64e06..5699ea2d630 100644 --- a/esp-lp-hal/ld/link-lp.x +++ b/esp-lp-hal/ld/link-lp.x @@ -4,6 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* Provides interrupt vector symbols */ +INCLUDE device.x + ENTRY(reset_vector) VECTOR_TABLE_LENGTH = 0x80; @@ -36,6 +39,8 @@ SECTIONS KEEP(*(.init)); KEEP(*(.init.rust)); + KEEP(*(.trap)); + KEEP(*(.trap.rust)); *(.text) *(.text*) } > ram diff --git a/esp-lp-hal/ld/link-ulp.x b/esp-lp-hal/ld/link-ulp.x index 7e6926a7fa1..239fc7a4c3e 100644 --- a/esp-lp-hal/ld/link-ulp.x +++ b/esp-lp-hal/ld/link-ulp.x @@ -4,6 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* Provides interrupt vector symbols */ +INCLUDE device.x + ENTRY(reset_vector) CONFIG_ULP_COPROC_RESERVE_MEM = 8 * 1024; @@ -15,39 +18,43 @@ MEMORY SECTIONS { - . = ORIGIN(ram); - - .text : - { - *(.text.vectors) /* Default reset vector must link to offset 0x0 */ - - KEEP(*(.init)); - KEEP(*(.init.rust)); - *(.text) - *(.text*) - } >ram - - .rodata ALIGN(4): - { - *(.rodata) - *(.rodata*) - } > ram - - .data ALIGN(4): - { - *(.data) - *(.data*) - *(.sdata) - *(.sdata*) - } > ram - - .bss ALIGN(4) : - { - *(.bss) - *(.bss*) - *(.sbss) - *(.sbss*) - } >ram - - __stack_top = ORIGIN(ram) + LENGTH(ram); + . = ORIGIN(ram); + + .text : + { + /* Power-on-reset must be placed at address 0x0 */ + KEEP(*(.reset)); + /* ULP will jump to 0x10 when an interrupt trap occurs */ + . = ALIGN(0x10); + KEEP(*(.trap)); + KEEP(*(.init)); + KEEP(*(.init.rust)); + KEEP(*(.trap.rust)); + *(.text .text.*) + } >ram + + .rodata ALIGN(4): + { + *(.rodata) + *(.rodata*) + } >ram + + .data ALIGN(4): + { + PROVIDE(__global_pointer$ = . + 0x800); + *(.data) + *(.data*) + *(.sdata) + *(.sdata*) + } >ram + + .bss ALIGN(4): + { + *(.bss) + *(.bss*) + *(.sbss) + *(.sbss*) + } >ram + + __stack_top = ORIGIN(ram) + LENGTH(ram); } diff --git a/esp-lp-hal/src/asm/lp_start.S b/esp-lp-hal/src/asm/lp_start.S new file mode 100644 index 00000000000..77f3177e891 --- /dev/null +++ b/esp-lp-hal/src/asm/lp_start.S @@ -0,0 +1,47 @@ +/* Start-up assembly for ESP32C6-LP chip */ +.org 0x0 +.section .init.vector, "ax" + +/* Place exception handler at 0x0 */ +.global exception_vector +.type exception_vector, @function +exception_vector: + j _start_trap + +/* Place vector interrupt handler (number 30) at 0x78 */ +.org 0x78 +.global interrupt_vector +.type interrupt_vector, @function +interrupt_vector: + j _start_trap + +/* The reset vector, placed at 0x80, the default reset value. */ +.org 0x80 +.section .init, "ax" +.global reset_vector +reset_vector: + j __start + +__start: + /* Setup the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Setup the stack pointer & frame pointer */ + la sp, __stack_top + add s0, sp, zero + /* Begin the rust program */ + jal zero, rust_main + +/* Abort loop */ +abort: + jal zero, abort + +.section .trap, "ax" +.global _start_trap +_start_trap: + /* abort immediately */ + j ulp_riscv_halt + mret \ No newline at end of file diff --git a/esp-lp-hal/src/asm/lp_start_with_interrupts.S b/esp-lp-hal/src/asm/lp_start_with_interrupts.S new file mode 100644 index 00000000000..a90fdd68200 --- /dev/null +++ b/esp-lp-hal/src/asm/lp_start_with_interrupts.S @@ -0,0 +1,123 @@ +/* Start-up assembly for ESP32C6-LP chip */ +.org 0x0 +.section .init.vector, "ax" + +/* This is the vector table. For LP core, only vector 30 is used. + * Base of the vector table is set by 'mtvec', defaulting to 0x0. + * + * To enable interrupts globally, set the MIE bit of mstatus. + * To enable Interrupt 30, set the 30th bit of mie CSR. + * After the interrupt is triggered, the LP CPU jumps to mtvec + 4 ∗ 30 + * After enterring the interrupt handler, users need to read LPPERI_INTERRUPT_SOURCE_REG + * to get the peripheral that triggered the interrupt and process the interrupt. + * + * For exceptions, the handler address is the base address of the vector table in the mtvec CSR + * The core fetches instructions from address 0x50000080 after reset. + * When the core starts up, the base address of the vector table is initialized to the boot address 0x50000000. + */ + + +.equ SAVE_REGS, 16 +.equ CONTEXT_SIZE, (SAVE_REGS * 4) + +/* Macro which first allocates space on the stack to save general + * purpose registers, and then save them. GP register is excluded. + * The default size allocated on the stack is CONTEXT_SIZE, but it + * can be overridden. + * + * Note: We don't save the callee-saved s0-s11 registers to save space + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. + */ +.macro save_general_regs cxt_size=CONTEXT_SIZE + addi sp, sp, -\cxt_size + sw ra, 0(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw t3, 16(sp) + sw t4, 20(sp) + sw t5, 24(sp) + sw t6, 28(sp) + sw a0, 32(sp) + sw a1, 36(sp) + sw a2, 40(sp) + sw a3, 44(sp) + sw a4, 48(sp) + sw a5, 52(sp) + sw a6, 56(sp) + sw a7, 60(sp) +.endm + +/* Restore the general purpose registers (excluding gp) from the context on + * the stack. The context is then deallocated. The default size is CONTEXT_SIZE + * but it can be overridden. + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. + */ +.macro restore_general_regs cxt_size=CONTEXT_SIZE + lw ra, 0(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw t3, 16(sp) + lw t4, 20(sp) + lw t5, 24(sp) + lw t6, 28(sp) + lw a0, 32(sp) + lw a1, 36(sp) + lw a2, 40(sp) + lw a3, 44(sp) + lw a4, 48(sp) + lw a5, 52(sp) + lw a6, 56(sp) + lw a7, 60(sp) + addi sp,sp, \cxt_size +.endm + +/* Place exception handler at 0x0 */ +.global exception_vector +.type exception_vector, @function +exception_vector: + j _start_trap + +/* Place vector interrupt handler (number 30) at 0x78 */ +.org 0x78 +.global interrupt_vector +.type interrupt_vector, @function +interrupt_vector: + j _start_trap + +/* The reset vector, placed at 0x80, the default reset value. */ +.org 0x80 +.section .init, "ax" +.global reset_vector +reset_vector: + j __start + +__start: + /* Setup the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Setup the stack pointer & frame pointer */ + la sp, __stack_top + add s0, sp, zero + /* Begin the rust program */ + jal zero, rust_main + +.section .trap, "ax" +.global _start_trap +_start_trap: + /* Save the general gurpose register context before handling the interrupt */ + save_general_regs + + /* Default trap handler, delegates to _start_trap_rust. */ + jal ra,_start_trap_rust + + /* Restore the register context after returning from the C interrupt handler */ + restore_general_regs + /* mret to exit */ + mret \ No newline at end of file diff --git a/esp-lp-hal/src/asm/ulp_riscv_start.S b/esp-lp-hal/src/asm/ulp_riscv_start.S new file mode 100644 index 00000000000..c345b362a8b --- /dev/null +++ b/esp-lp-hal/src/asm/ulp_riscv_start.S @@ -0,0 +1,40 @@ + /* Disable compressed instructions */ + .option norvc + .section .reset, "ax" + .global reset_vector + /* Power-on reset vector, placed at address 0x0 */ +reset_vector: + /* no more than 16 bytes here */ + /* disable interrupts and jump to _start */ + /* lui a0,0x80000 */ + /* addi a0,a0,7 */ + /* .insn 4, 0x0605658b */ + jal zero, _start + + /* Initialisation routine */ + .section .init, "ax" + .global _start +_start: + /* Setup the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Setup the stack pointer & frame pointer */ + la sp, __stack_top + add s0, sp, zero + /* Begin the rust program */ + jal zero, rust_main + + /* Abort loop */ +abort: + jal zero, abort + + /* Hardware trap vector, placed at address 0x10 + * This will abort + */ + .section .trap, "ax" + .global _start_trap +_start_trap: + ret \ No newline at end of file diff --git a/esp-lp-hal/src/asm/ulp_riscv_start_with_interrupts.S b/esp-lp-hal/src/asm/ulp_riscv_start_with_interrupts.S new file mode 100644 index 00000000000..a8d323dd09c --- /dev/null +++ b/esp-lp-hal/src/asm/ulp_riscv_start_with_interrupts.S @@ -0,0 +1,233 @@ + /* Disable compressed instructions */ + .option norvc +/** + * Defines the assembly macros needed for custom instructions on the ULP RISCV core. + * Adapted from the following ESP-IDF header: + * https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_interrupt_ops.h + * With additional waitirq_insn from PicoRV32: + * https://github.com/YosysHQ/picorv32/blob/87c89acc18994c8cf9a2311e871818e87d304568/firmware/start.S + * A quote from the ESP-IDF header file, is shown below. + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * This header file defines custom instructions for interrupt handling on the * + * ULP RISC-V. The architecture of the processor and therefore, the interrupt * + * handling is based on the PicoRV32 CPU. Details about the operations are * + * available at https://github.com/YosysHQ/picorv32#custom-instructions-for-irq-handling * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + +.set regnum_zero,0 +.set regnum_ra, 1 +.set regnum_sp, 2 +.set regnum_gp, 3 +.set regnum_tp, 4 +.set regnum_t0, 5 +.set regnum_t1, 6 +.set regnum_t2, 7 +.set regnum_s0, 8 +.set regnum_s1, 9 +.set regnum_a0, 10 +.set regnum_a1, 11 +.set regnum_a2, 12 +.set regnum_a3, 13 +.set regnum_a4, 14 +.set regnum_a5, 15 +.set regnum_a6, 16 +.set regnum_a7, 17 +.set regnum_s2, 18 +.set regnum_s3, 19 +.set regnum_s4, 20 +.set regnum_s5, 21 +.set regnum_s6, 22 +.set regnum_s7, 23 +.set regnum_s8, 24 +.set regnum_s9, 25 +.set regnum_s10,26 +.set regnum_s11,27 +.set regnum_t3, 28 +.set regnum_t4, 29 +.set regnum_t5, 30 +.set regnum_t6, 31 + +/* Define encoding for special interrupt handling registers, viz., q0, q1, q2 and q3 */ +.set regnum_q0, 0 +.set regnum_q1, 1 +.set regnum_q2, 2 +.set regnum_q3, 3 + +/* All custom interrupt handling instructions follow the standard R-type instruction format from RISC-V ISA + * with the same opcode of custom0 (0001011). + */ +.macro r_type_insn _f7, _rs2, _rs1, _f3, _rd, _opc + .word (((\_f7) << 25) | ((\_rs2) << 20) | ((\_rs1) << 15) | ((\_f3) << 12) | ((\_rd) << 7) | ((\_opc) << 0)) +.endm + +/** + * Instruction: getq rd, qs + * Description: This instruction copies the value of Qx into a general purpose register rd + */ +.macro getq_insn _rd _qs + r_type_insn 0b0000000, 0, "regnum_\_qs", 0b100, "regnum_\_rd", 0b0001011 +.endm + +/** + * Instruction: setq qd, rs + * Description: This instruction copies the value of general purpose register rs to Qx + */ +.macro setq_insn _qd _rs + r_type_insn 0b0000001, 0, "regnum_\_rs", 0b010, "regnum_\_qd", 0b0001011 +.endm + +/** + * Instruction: retirq + * Description: This instruction copies the value of Q0 to CPU PC, and renables interrupts + */ +.macro retirq_insn + r_type_insn 0b0000010, 0, 0, 0b000, 0, 0b0001011 +.endm + +/** + * Instruction: maskirq rd, rs + * Description: This instruction copies the value of the register IRQ Mask to the register rd, and copies the value + * of register rs to to IRQ mask. + */ +.macro maskirq_insn _rd _rs + r_type_insn 0b0000011, 0, "regnum_\_rs", 0b110, "regnum_\_rd", 0b0001011 +.endm + +/** + * Instruction: waitirq rd + * Description: Pause execution until any interrupt (masked or unmasked) becomes pending. Stores the pending IRQ bitmask into register rd. + */ +.macro waitirq_insn _rd + r_type_insn 0b0000100, 0, 0, 0b100, "regnum_\_rd", 0b0001011 +.endm + +/* Much of the following assembly was sourced from the following ESP-IDF files... +* ...irq handler macros: +* https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_vectors.S +* ...critical section assembly +* https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_utils.h +* ...riscv halt code +* https://github.com/espressif/esp-idf/blob/12f36a021f511cd4de41d3fffff146c5336ac1e7/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c +*/ + +.equ SAVE_REGS, 16 +.equ CONTEXT_SIZE, (SAVE_REGS * 4) + +/* Macro which first allocates space on the stack to save general + * purpose registers, and then save them. GP register is excluded. + * The default size allocated on the stack is CONTEXT_SIZE, but it + * can be overridden. + * + * Note: We don't save the callee-saved s0-s11 registers to save space + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. + */ +.macro save_general_regs cxt_size=CONTEXT_SIZE + addi sp, sp, -\cxt_size + sw ra, 0(sp) + sw t0, 4(sp) + sw t1, 8(sp) + sw t2, 12(sp) + sw t3, 16(sp) + sw t4, 20(sp) + sw t5, 24(sp) + sw t6, 28(sp) + sw a0, 32(sp) + sw a1, 36(sp) + sw a2, 40(sp) + sw a3, 44(sp) + sw a4, 48(sp) + sw a5, 52(sp) + sw a6, 56(sp) + sw a7, 60(sp) +.endm + +/* Restore the general purpose registers (excluding gp) from the context on + * the stack. The context is then deallocated. The default size is CONTEXT_SIZE + * but it can be overridden. + * Note: This has been modified from ESP-IDF version, so that it matches + * the standard riscv_rt::TrapFrame. + */ +.macro restore_general_regs cxt_size=CONTEXT_SIZE + lw ra, 0(sp) + lw t0, 4(sp) + lw t1, 8(sp) + lw t2, 12(sp) + lw t3, 16(sp) + lw t4, 20(sp) + lw t5, 24(sp) + lw t6, 28(sp) + lw a0, 32(sp) + lw a1, 36(sp) + lw a2, 40(sp) + lw a3, 44(sp) + lw a4, 48(sp) + lw a5, 52(sp) + lw a6, 56(sp) + lw a7, 60(sp) + addi sp,sp, \cxt_size +.endm + +.section .reset, "ax" +.global reset_vector +/* Power-on reset vector, placed at address 0x0 */ +reset_vector: + /* no more than 16 bytes here */ + jal zero, _start + +/* Initialisation routine */ +.section .init, "ax" +.global _start +_start: + /* Setup the global pointer & thread pointer */ + .option push + .option norelax + la gp, __global_pointer$ + addi tp, gp, 0 + .option pop + /* Setup the stack pointer & frame pointer */ + la sp, __stack_top + add s0, sp, zero + /* Begin the rust program */ + jal zero, rust_main + +/* Hardware trap vector, placed at address 0x10 + * Handles all interrupts and exceptions for the ULP core. + * Saves registers ra,t0-6,a0-7, then calls _start_trap_rust. + */ +.section .trap, "ax" +.global _start_trap +_start_trap: + /* Save the general gurpose register context before handling the interrupt */ + save_general_regs + + /* Store the return address (x1) and stack pointer (x2) in the q registers. */ + setq_insn q2, ra + setq_insn q3, sp + + /* Store the pointer to the stored registers (TrapFrame) in a0. + * This will be the first argument to _start_trap_rust. + */ + addi a0, sp, 0 + + /* Fetch the interrupt status from the custom q1 register into a1. + * This will be the second argumet to _start_trap_rust. + */ + getq_insn a1, q1 + + /* Call the global interrupt handler. The interrupt status is passed as the argument in a0. + * We do not re-enable interrupts before calling the handler as ULP RISC-V does not + * support nested interrupts. + */ + jal ra,_start_trap_rust + + /* Restore the return address and stack pointer from the q registers */ + getq_insn ra, q2 + getq_insn sp, q3 + + /* Restore the register context after returning from the C interrupt handler */ + restore_general_regs + + /* Exit interrupt handler by executing the custom retirq instruction which will restore pc and re-enable interrupts */ + retirq_insn \ No newline at end of file diff --git a/esp-lp-hal/src/gpio/conjure.rs b/esp-lp-hal/src/gpio/conjure.rs new file mode 100644 index 00000000000..5721d96d0aa --- /dev/null +++ b/esp-lp-hal/src/gpio/conjure.rs @@ -0,0 +1,31 @@ +use super::{Input, MAX_GPIO_PIN, Output, OutputOpenDrain}; + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_output() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(Output::::new()) + } +} + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_output_open_drain() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(OutputOpenDrain::::new()) + } +} + +// Used by the `entry` procmacro: +#[doc(hidden)] +pub unsafe fn conjure_input() -> Option> { + if PIN > MAX_GPIO_PIN { + None + } else { + Some(Input::::new()) + } +} diff --git a/esp-lp-hal/src/gpio/ehal.rs b/esp-lp-hal/src/gpio/ehal.rs new file mode 100644 index 00000000000..f9c604ba285 --- /dev/null +++ b/esp-lp-hal/src/gpio/ehal.rs @@ -0,0 +1,77 @@ +use super::{Input, Level, Output, OutputOpenDrain}; + +impl embedded_hal::digital::ErrorType for Input { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::ErrorType for Output { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::ErrorType for OutputOpenDrain { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::InputPin for Input { + fn is_high(&mut self) -> Result { + Ok(self.level() == Level::High) + } + + fn is_low(&mut self) -> Result { + Ok(self.level() == Level::Low) + } +} + +impl embedded_hal::digital::OutputPin for Output { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::Low); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::High); + Ok(()) + } +} + +impl embedded_hal::digital::StatefulOutputPin for Output { + fn is_set_high(&mut self) -> Result { + Ok(self.output_level() == Level::High) + } + + fn is_set_low(&mut self) -> Result { + Ok(self.output_level() == Level::Low) + } +} + +impl embedded_hal::digital::InputPin for OutputOpenDrain { + fn is_high(&mut self) -> Result { + Ok(self.level() == Level::High) + } + + fn is_low(&mut self) -> Result { + Ok(self.level() == Level::Low) + } +} + +impl embedded_hal::digital::OutputPin for OutputOpenDrain { + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::Low); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_level(Level::High); + Ok(()) + } +} + +impl embedded_hal::digital::StatefulOutputPin for OutputOpenDrain { + fn is_set_high(&mut self) -> Result { + Ok(self.output_level() == Level::High) + } + + fn is_set_low(&mut self) -> Result { + Ok(self.output_level() == Level::Low) + } +} diff --git a/esp-lp-hal/src/gpio/interrupt.rs b/esp-lp-hal/src/gpio/interrupt.rs new file mode 100644 index 00000000000..bda570aab46 --- /dev/null +++ b/esp-lp-hal/src/gpio/interrupt.rs @@ -0,0 +1,111 @@ +pub use interrupt::{Interrupt, InterruptHandler, InterruptStatus}; +pub use procmacros::handler; + +#[allow(unused_imports)] +use super::{Io, LpIo}; +use crate::interrupt; + +/// Convenience constant for `Option::None` pin +pub static USER_INTERRUPT_HANDLER: interrupt::CFnPtr = interrupt::CFnPtr::new(); + +impl crate::interrupt::InterruptConfigurable for Io { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +/// The user GPIO interrupt handler, when the user has set one. +/// +/// The user handler is responsible for clearing the interrupt status bits or disabling +/// the interrupts. +#[handler] +pub fn user_gpio_interrupt_handler() { + // Call the user handler before clearing interrupts. The user can use the enable + // bits to determine which interrupts they are interested in. Clearing the + // interrupt status or enable bits have no effect on the rest of the + // interrupt handler. + USER_INTERRUPT_HANDLER.call(); +} + +/// Event type used to trigger interrupts. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub enum Event { + /// Interrupts trigger on rising pin edge. + RisingEdge = 1, + /// Interrupts trigger on falling pin edge. + FallingEdge = 2, + /// Interrupts trigger on either rising or falling pin edges. + AnyEdge = 3, + /// Interrupts trigger on low level + LowLevel = 4, + /// Interrupts trigger on high level + HighLevel = 5, +} + +/// Read the interrupt status of all pins +/// Bit 0 == GPIO0, in the returned value +#[inline] +pub fn gpio_interrupt_status() -> u32 { + #[cfg(any(esp32s2, esp32s3))] + { + unsafe { &*LpIo::PTR }.status().read().bits() >> 10 + } + + #[cfg(esp32c6)] + { + todo!() + } +} + +/// Clear the interrupt status for a bit mask of pins +/// Expects pinmask bit 0 == GPIO0 +#[allow(unused_variables)] +#[inline] +pub fn gpio_interrupt_clear(pinmask: u32) { + #[cfg(any(esp32s2, esp32s3))] + unsafe { &*LpIo::PTR } + .status_w1tc() + .write(|w| unsafe { w.bits(pinmask << 10) }); + #[cfg(esp32c6)] + todo!() +} + +/// Set GPIO event listening. +/// +/// - `N`: the pin to configure +/// - `int_type`: interrupt type code, value from [Event], 0 to disable interrupts. If None, will +/// leave the int_type setting as-is. +/// - `wake_up`: whether to wake up from light sleep. +#[allow(unused_variables)] +pub fn enable_pin_interrupt(int_type: u8) { + #[cfg(any(esp32s2, esp32s3))] + { + let gpio_pin = unsafe { &*LpIo::PTR }.pin(N as usize); + gpio_pin.write(|w| unsafe { w.int_type().bits(int_type) }); + } + #[cfg(esp32c6)] + todo!() +} + +/// Clear pin interrupt +pub fn clear_pin_interrupt() { + gpio_interrupt_clear(1 << N); +} + +/// Read pin interrupt status +pub fn is_interrupt_set() -> bool { + let stat = gpio_interrupt_status(); + (stat & (1 << N)) != 0 +} + +/// Enable / disable pin wakeup +#[allow(unused_variables)] +pub fn pin_wakeup_enable(en: bool) { + #[cfg(any(esp32s2, esp32s3))] + { + let gpio_pin = unsafe { &*LpIo::PTR }.pin(N as usize); + gpio_pin.write(|w| w.wakeup_enable().bit(en)); + } + #[cfg(esp32c6)] + todo!() +} diff --git a/esp-lp-hal/src/gpio.rs b/esp-lp-hal/src/gpio/mod.rs similarity index 52% rename from esp-lp-hal/src/gpio.rs rename to esp-lp-hal/src/gpio/mod.rs index 2b64136c795..278736a7951 100644 --- a/esp-lp-hal/src/gpio.rs +++ b/esp-lp-hal/src/gpio/mod.rs @@ -23,13 +23,80 @@ //! } //! ``` +/// Used by the `entry` procmacro +pub mod conjure; +pub use conjure::*; + +#[cfg(feature = "embedded-hal")] +/// Provides embedded-hal trait impls +pub mod ehal; + +/// Provides GPIO interrupt support +// TODO: Implement GPIO interrupts for ESP32C6 LP core +#[cfg(feature = "interrupts")] +pub mod interrupt; +#[cfg(feature = "interrupts")] +pub use interrupt::{ + Event, + Interrupt, + InterruptHandler, + USER_INTERRUPT_HANDLER, + gpio_interrupt_clear, + gpio_interrupt_status, + user_gpio_interrupt_handler, +}; + cfg_if::cfg_if! { if #[cfg(esp32c6)] { type LpIo = crate::pac::LP_IO; - const MAX_GPIO_PIN: u8 = 7; + /// Maximum GPIO pin number. + pub const MAX_GPIO_PIN: u8 = 7; } else { type LpIo = crate::pac::RTC_IO; - const MAX_GPIO_PIN: u8 = 21; + /// Maximum GPIO pin number. + pub const MAX_GPIO_PIN: u8 = 21; + } +} + +/// General Purpose Input/Output driver +pub struct Io { + _io_peripheral: LpIo, +} + +impl Io { + /// Initialize the I/O driver. + pub fn new(_io_peripheral: LpIo) -> Self { + Io { _io_peripheral } + } + + #[cfg(feature = "interrupts")] + #[doc = "Registers an interrupt handler for all GPIO pins."] + /// Note that when using interrupt handlers registered by this function, or + /// by defining a `#[no_mangle] unsafe extern "C" fn GPIO()` function, we do + /// **not** clear the interrupt status register or the interrupt enable + /// setting for you. Based on your use case, you need to do one of this + /// yourself: + /// + /// - Disabling the interrupt enable setting for the GPIO pin allows you to handle an event once + /// per call to [`listen()`]. Using this method, the [`is_interrupt_set()`] method will return + /// `true` if the interrupt is set even after your handler has finished running. + /// - Clearing the interrupt status register allows you to handle an event repeatedly after + /// [`listen()`] is called. Using this method, [`is_interrupt_set()`] will return `false` + /// after your handler has finished running. + /// + /// [`listen()`]: Input::listen + /// [`is_interrupt_set()`]: Input::is_interrupt_set + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + USER_INTERRUPT_HANDLER.store(handler.handler().callback()); + #[cfg(any(esp32s2, esp32s3))] + { + crate::interrupt::bind_handler(Interrupt::GPIO_INT, user_gpio_interrupt_handler); + } + + #[cfg(esp32c6)] + { + todo!() + } } } @@ -89,6 +156,9 @@ impl From for bool { /// Flexible pin driver. /// Provides a common implementation for input and output pins. +/// Currently hidden, as it is not intended to be used directly, +/// because it does not handle changing the pin modes. +#[doc(hidden)] pub struct Flex {} #[allow(clippy::new_without_default)] @@ -147,6 +217,46 @@ impl Flex { let level = self.output_level(); self.set_level(!level); } + + // Interrupt-related APIs + cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + /// Listen for interrupts. + pub fn listen(&mut self, event : Event, wakeup_enable : bool) { + // Validate the inputs + if wakeup_enable { + match event { + Event::LowLevel | Event::HighLevel => {}, + _ => { + panic!("GPIO wakeup is only supported with LowLevel or HighLevel event types."); + } + } + } + + // self.clear_interrupt(); + interrupt::enable_pin_interrupt::(event as u8); + interrupt::pin_wakeup_enable::(wakeup_enable); + } + + /// Un-listen for interrupts. + pub fn unlisten(&mut self) { + interrupt::enable_pin_interrupt::(0); + interrupt::pin_wakeup_enable::(false); + self.clear_interrupt(); + } + + /// Clear pin interrupts + pub fn clear_interrupt(&mut self) { + interrupt::clear_pin_interrupt::(); + } + + /// Read pin interrupt + pub fn is_interrupt_set(&mut self) -> bool { + interrupt::is_interrupt_set::() + } + } + } } /// Digital input. @@ -161,11 +271,33 @@ impl Input { let pin = Flex::::new(); Self { pin } } - /// Get the current pin input level. pub fn level(&self) -> Level { self.pin.level() } + + // Interrupt-related APIs + cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + /// Listen for interrupts. + pub fn listen(&mut self, event : Event, wakeup_enable : bool) { + self.pin.listen(event, wakeup_enable); + } + /// Un-listen for interrupts. + pub fn unlisten(&mut self) { + self.pin.unlisten(); + } + /// Clear pin interrupts + pub fn clear_interrupt(&mut self) { + interrupt::clear_pin_interrupt::(); + } + /// Read pin interrupt + pub fn is_interrupt_set(&mut self) -> bool { + interrupt::is_interrupt_set::() + } + } + } } /// Digital output. @@ -180,7 +312,6 @@ impl Output { let pin = Flex::::new(); Self { pin } } - /// Set the output level. pub fn set_level(&mut self, level: Level) { self.pin.set_level(level); @@ -224,120 +355,27 @@ impl OutputOpenDrain { pub fn toggle(&mut self) { self.pin.toggle() } -} - -// Used by the `entry` procmacro: -#[doc(hidden)] -pub unsafe fn conjure_output() -> Option> { - if PIN > MAX_GPIO_PIN { - None - } else { - Some(Output::::new()) - } -} - -// Used by the `entry` procmacro: -#[doc(hidden)] -pub unsafe fn conjure_output_open_drain() -> Option> { - if PIN > MAX_GPIO_PIN { - None - } else { - Some(OutputOpenDrain::::new()) - } -} - -// Used by the `entry` procmacro: -#[doc(hidden)] -pub unsafe fn conjure_input() -> Option> { - if PIN > MAX_GPIO_PIN { - None - } else { - Some(Input::::new()) - } -} -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::ErrorType for Input { - type Error = core::convert::Infallible; -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::ErrorType for Output { - type Error = core::convert::Infallible; -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::ErrorType for OutputOpenDrain { - type Error = core::convert::Infallible; -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::InputPin for Input { - fn is_high(&mut self) -> Result { - Ok(self.level() == Level::High) - } - - fn is_low(&mut self) -> Result { - Ok(self.level() == Level::Low) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::OutputPin for Output { - fn set_low(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::Low); - Ok(()) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::High); - Ok(()) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::StatefulOutputPin for Output { - fn is_set_high(&mut self) -> Result { - Ok(self.output_level() == Level::High) - } - - fn is_set_low(&mut self) -> Result { - Ok(self.output_level() == Level::Low) - } -} - -// OutputOpenDrain -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::InputPin for OutputOpenDrain { - fn is_high(&mut self) -> Result { - Ok(self.level() == Level::High) - } - - fn is_low(&mut self) -> Result { - Ok(self.level() == Level::Low) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::OutputPin for OutputOpenDrain { - fn set_low(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::Low); - Ok(()) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::High); - Ok(()) - } -} - -#[cfg(feature = "embedded-hal")] -impl embedded_hal::digital::StatefulOutputPin for OutputOpenDrain { - fn is_set_high(&mut self) -> Result { - Ok(self.output_level() == Level::High) - } - - fn is_set_low(&mut self) -> Result { - Ok(self.output_level() == Level::Low) + // Interrupt-related APIs + cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + /// Listen for interrupts. + pub fn listen(&mut self, event : Event, wakeup_enable : bool) { + self.pin.listen(event, wakeup_enable); + } + /// Un-listen for interrupts. + pub fn unlisten(&mut self) { + self.pin.unlisten(); + } + /// Clear pin interrupts + pub fn clear_interrupt(&mut self) { + interrupt::clear_pin_interrupt::(); + } + /// Read pin interrupt + pub fn is_interrupt_set(&mut self) -> bool { + interrupt::is_interrupt_set::() + } + } } } diff --git a/esp-lp-hal/src/interrupt/critical_section.rs b/esp-lp-hal/src/interrupt/critical_section.rs new file mode 100644 index 00000000000..d12f178e1f0 --- /dev/null +++ b/esp-lp-hal/src/interrupt/critical_section.rs @@ -0,0 +1,27 @@ +use critical_section::RawRestoreState; + +#[cfg(feature = "interrupts")] +use crate::interrupt; + +struct CriticalSection; +critical_section::set_impl!(CriticalSection); + +/// Machine-mode critical section implementation for +/// LP and ULP cores. +unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> RawRestoreState { + #[cfg(feature = "interrupts")] + { + interrupt::machine_interrupt_enable(false) + } + #[cfg(not(feature = "interrupts"))] + { + true + } + } + + unsafe fn release(_previous_state: RawRestoreState) { + #[cfg(feature = "interrupts")] + interrupt::machine_interrupt_enable(_previous_state); + } +} diff --git a/esp-lp-hal/src/interrupt/lp_core.rs b/esp-lp-hal/src/interrupt/lp_core.rs new file mode 100644 index 00000000000..1948d6db257 --- /dev/null +++ b/esp-lp-hal/src/interrupt/lp_core.rs @@ -0,0 +1,99 @@ +//! Interrupt handling for ESP32-C6 LP core +use riscv::{CoreInterruptNumber, InterruptNumber}; + +use super::Interrupt; + +/// Setup interrupt handlers, including any default ones +#[inline(always)] +pub fn setup_interrupts() { + machine_interrupt_enable(true); +} + +/// Enables or disables a peripheral interrupt. +/// +/// Note that interrupts still need to be enabled globally for interrupts +/// to be serviced. +/// +/// Internally, this function maps the interrupt to the appropriate CPU interrupt. +#[inline] +pub fn set_enabled(_interrupt: Interrupt, _enable: bool) { + // todo!() +} + +/// Clears a peripheral interrupt. +pub fn clear(_interrupt: Interrupt) { + todo!() +} + +/// Returns a bitmask of active peripheral interrupts +pub fn status() -> u32 { + todo!() +} + +/// Returns the cause of a machine interrupt, +/// which contains the exception code, and if a peripheral interrupt was flagged. +#[unsafe(link_section = ".trap.rust")] +#[inline(always)] +pub fn trap_cause() -> riscv::interrupt::Trap { + let mcause_reg = riscv::register::mcause::read(); + mcause_reg.cause() +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +enum LpMachineInterrupt { + MachineSoft = 3, // Global enable/disable bit in mstatus + Peripheral = 30, // Custom interrupt for peripherals +} + +unsafe impl InterruptNumber for LpMachineInterrupt { + const MAX_INTERRUPT_NUMBER: usize = 31_usize; + + #[inline] + fn number(self) -> usize { + self as usize + } + + #[inline] + fn from_number(value: usize) -> riscv::result::Result { + match value { + 3 => Ok(Self::MachineSoft), + 30 => Ok(Self::Peripheral), + _ => Err(riscv::result::Error::InvalidVariant(value)), + } + } +} + +unsafe impl CoreInterruptNumber for LpMachineInterrupt {} + +/// Set the mie register (or equivalent), +/// returning the previous value. +#[inline(always)] +pub fn machine_interrupt_enable(enable: bool) -> bool { + // ESP32C6-LP must enable both the + // global machine soft interrupt, (mie bit of the mstatus register), + // and also the external peripheral interrupt (bit 30 in mie register). + + let mut mstatus_reg = riscv::register::mstatus::read(); + let mut mie_reg = riscv::register::mie::read(); + + // The original value of the global interrupt enable bit, will be returned. + let old_mie_enable = mstatus_reg.mie(); + + // Update register values + if enable { + mstatus_reg.set_mie(true); + mie_reg.enable(LpMachineInterrupt::Peripheral); + } else { + mstatus_reg.set_mie(false); + mie_reg.disable(LpMachineInterrupt::Peripheral); + } + + // Write them back to memory + unsafe { + riscv::register::mstatus::write(mstatus_reg); + riscv::register::mie::write(mie_reg); + } + + old_mie_enable +} diff --git a/esp-lp-hal/src/interrupt/mod.rs b/esp-lp-hal/src/interrupt/mod.rs new file mode 100644 index 00000000000..2ee7f6d41b0 --- /dev/null +++ b/esp-lp-hal/src/interrupt/mod.rs @@ -0,0 +1,30 @@ +cfg_if::cfg_if! { + if #[cfg(feature="interrupts")] + { + // RISCV ULP specific interrupt handlers, critical section + #[cfg(any(esp32s2, esp32s3))] + pub mod ulp_core; + #[cfg(any(esp32s2, esp32s3))] + pub use ulp_core::*; + + #[cfg(esp32c6)] + pub mod lp_core; + #[cfg(esp32c6)] + pub use lp_core::*; + + /// Portable interrupt binding and handling code + pub mod shared; + pub use shared::*; + } else { + // If interrupt handling is not enabled, need to provide a stub DefaultHandler, + // to make the linker happy. + #[doc(hidden)] + #[unsafe(export_name = "DefaultHandler")] + #[unsafe(link_section = ".trap.rust")] + pub fn stub_default_handler() {} + } +} + +/// LP core critical section implementation. +/// Works with or without the interrupts feature. +mod critical_section; diff --git a/esp-lp-hal/src/interrupt/shared.rs b/esp-lp-hal/src/interrupt/shared.rs new file mode 100644 index 00000000000..576ecf4760a --- /dev/null +++ b/esp-lp-hal/src/interrupt/shared.rs @@ -0,0 +1,325 @@ +use core::ptr::NonNull; + +use portable_atomic::{AtomicPtr, Ordering}; + +use super::{set_enabled, status, trap_cause}; +pub use crate::pac::Interrupt; +use crate::pac::{__EXTERNAL_INTERRUPTS, Vector}; + +/// Hard-coded limit of a single u32 interrupt status bank, +/// for all ULP / LP chips. +const MAX_IRQ_STATUS_WORDS: usize = 1; + +/// Returns the currently bound interrupt handler. +pub fn bound_handler(interrupt: Interrupt) -> Option { + unsafe { + let vector = vector_entry(interrupt); + + let addr = vector._handler; + if addr as usize == 0 { + return None; + } + + Some(IsrCallback::new(core::mem::transmute::< + unsafe extern "C" fn(), + extern "C" fn(), + >(addr))) + } +} + +fn vector_entry(interrupt: Interrupt) -> &'static Vector { + &__EXTERNAL_INTERRUPTS[interrupt as usize] +} + +/// Binds the given handler to a peripheral interrupt. +/// +/// The interrupt handler will be enabled at the specified priority level. +/// +/// The interrupt handler will be called on the core where it is registered. +/// Only one interrupt handler can be bound to a peripheral interrupt. +pub fn bind_handler(interrupt: Interrupt, handler: InterruptHandler) { + unsafe { + let vector = vector_entry(interrupt); + let ptr = (&raw const vector._handler).cast::().cast_mut(); + ptr.write_volatile(handler.handler().address()); + } + set_enabled(interrupt, true); +} + +/// Trait implemented by drivers which allow the user to set an +/// [InterruptHandler] +pub trait InterruptConfigurable { + #[doc = "Registers an interrupt handler for the peripheral."] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. Some peripherals offer a shared interrupt handler for + /// multiple purposes. It's the users duty to honor this. + fn set_interrupt_handler(&mut self, handler: InterruptHandler); +} + +/// Represents an ISR callback function +#[derive(Copy, Clone)] +pub struct IsrCallback { + f: extern "C" fn(), +} + +impl IsrCallback { + /// Construct a new callback from the callback function. + pub fn new(f: extern "C" fn()) -> Self { + // a valid fn pointer is non zero + Self { f } + } + + /// Returns the address of the callback. + pub fn address(self) -> usize { + self.f as usize + } + + /// The callback function. + /// + /// This is aligned and can be called. + pub fn callback(self) -> extern "C" fn() { + self.f + } +} + +impl PartialEq for IsrCallback { + fn eq(&self, other: &Self) -> bool { + core::ptr::fn_addr_eq(self.callback(), other.callback()) + } +} + +/// Interrupt priority levels. +/// For esp-lp-hal there is only one priority level, and this is only included +/// so that the API is compatible with esp-hal handler macro. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +#[non_exhaustive] +pub enum Priority { + /// Priority level 1. + Priority1 = 1, +} + +impl Priority { + /// Maximum interrupt priority + pub const fn max() -> Priority { + Priority::Priority1 + } + + /// Minimum interrupt priority + pub const fn min() -> Priority { + Priority::Priority1 + } +} + +/// An interrupt handler +#[derive(Copy, Clone)] +pub struct InterruptHandler { + f: extern "C" fn(), + prio: Priority, +} + +impl InterruptHandler { + /// Creates a new [InterruptHandler] which will call the given function + pub const fn new(f: extern "C" fn(), prio: Priority) -> Self { + Self { f, prio } + } + + /// The Isr callback. + #[inline] + pub fn handler(&self) -> IsrCallback { + IsrCallback::new(self.f) + } + + /// Priority to be used when registering the interrupt + #[inline] + pub fn priority(&self) -> Priority { + self.prio + } +} + +/// Iterator over set interrupt status bits +#[doc(hidden)] +#[derive(Debug, Clone)] +pub struct InterruptStatusIterator { + status: InterruptStatus, + idx: usize, +} + +impl InterruptStatusIterator { + /// Create a new InterruptStatusIterator + pub fn new(status: InterruptStatus) -> Self { + Self { status, idx: 0 } + } +} + +impl Iterator for InterruptStatusIterator { + type Item = u8; + + fn next(&mut self) -> Option { + for i in self.idx..MAX_IRQ_STATUS_WORDS { + if self.status.status[i] != 0 { + let bit = self.status.status[i].trailing_zeros(); + self.status.status[i] ^= 1 << bit; + self.idx = i; + return Some((bit + 32 * i as u32) as u8); + } + } + self.idx = usize::MAX; + None + } +} + +/// Ergonomic C function pointer wrapper, with null-pointer checking +pub struct CFnPtr(AtomicPtr<()>); + +#[allow(clippy::new_without_default)] +impl CFnPtr { + /// Create a new null pointer + pub const fn new() -> Self { + Self(AtomicPtr::new(core::ptr::null_mut())) + } + + /// Store pointer to a C function + pub fn store(&self, f: extern "C" fn()) { + self.0.store(f as *mut (), Ordering::Relaxed); + } + + /// Call the stored C function, if set + pub fn call(&self) { + let ptr = self.0.load(Ordering::Relaxed); + if !ptr.is_null() { + unsafe { (core::mem::transmute::<*mut (), extern "C" fn()>(ptr))() }; + } + } +} + +/// Representation of peripheral-interrupt status bits. +#[doc(hidden)] +#[derive(Clone, Copy, Default, Debug)] +pub struct InterruptStatus { + status: [u32; MAX_IRQ_STATUS_WORDS], +} + +impl InterruptStatus { + /// Get status of interrupts + /// This bitmask should match the Interrupt enum. + pub fn current() -> Self { + Self { status: [status()] } + } + + /// Is the given interrupt bit set + pub fn is_set(&self, interrupt: u8) -> bool { + (self.status[interrupt as usize / 32] & (1 << (interrupt % 32))) != 0 + } + + /// Set the given interrupt status bit + pub fn set(&mut self, interrupt: u8) { + self.status[interrupt as usize / 32] |= 1 << (interrupt % 32); + } + + /// Return an iterator over the set interrupt status bits + pub fn iterator(&self) -> InterruptStatusIterator { + InterruptStatusIterator::new(*self) + } +} + +impl From for InterruptStatus { + fn from(value: u32) -> Self { + Self { status: [value] } + } +} + +/// Trap entry point rust (_start_trap_rust) +#[doc(hidden)] +#[unsafe(link_section = ".trap.rust")] +#[unsafe(export_name = "_start_trap_rust")] +pub extern "C" fn start_trap_rust(trap_frame: *const u32) { + unsafe { + match trap_cause() { + riscv::interrupt::Trap::Interrupt(code) => { + dispatch_interrupt(code); + } + riscv::interrupt::Trap::Exception(code) => { + dispatch_exception( + NonNull::new_unchecked(trap_frame as *mut TrapFrame).as_ref(), + code, + ); + } + } + } +} + +#[doc(hidden)] +#[unsafe(link_section = ".trap.rust")] +pub fn dispatch_exception(_trap_frame: &TrapFrame, _code: usize) { + #[allow(clippy::empty_loop)] + loop {} +} + +/// Called by _start_trap_rust, this trap handler will call other interrupt handling +/// functions depending on the bits set in pending_irqs. +#[doc(hidden)] +#[unsafe(link_section = ".trap.rust")] +pub fn dispatch_interrupt(_code: usize) { + // Dispatch peripheral interrupt + let status = InterruptStatus::current(); + + // Iterate the active interrupts, fetch their handler, and call it if set. + for interrupt_nr in status.iterator() { + if let Ok(i) = Interrupt::try_from(interrupt_nr) + && let Some(handler) = bound_handler(i) + { + handler.callback()(); + } + } +} + +/// Default interrupt handler, does nothing. +#[allow(dead_code)] +#[doc(hidden)] +#[allow(non_snake_case)] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".trap.rust")] +pub fn DefaultHandler() {} + +/// TODO: Write store_trap / load_trap functions, to generate the context saving assembly code, +// which is currently hand-written in ulp_riscv_vectors.S +/// Registers saved in trap handler +#[doc(hidden)] +#[repr(C)] +#[derive(Debug)] +pub struct TrapFrame { + /// `x1`: return address, stores the address to return to after a function call or interrupt. + pub ra: usize, + /// `x5`: temporary register `t0`, used for intermediate values. + pub t0: usize, + /// `x6`: temporary register `t1`, used for intermediate values. + pub t1: usize, + /// `x7`: temporary register `t2`, used for intermediate values. + pub t2: usize, + /// `x28`: temporary register `t3`, used for intermediate values. + pub t3: usize, + /// `x29`: temporary register `t4`, used for intermediate values. + pub t4: usize, + /// `x30`: temporary register `t5`, used for intermediate values. + pub t5: usize, + /// `x31`: temporary register `t6`, used for intermediate values. + pub t6: usize, + /// `x10`: argument register `a0`. Used to pass the first argument to a function. + pub a0: usize, + /// `x11`: argument register `a1`. Used to pass the second argument to a function. + pub a1: usize, + /// `x12`: argument register `a2`. Used to pass the third argument to a function. + pub a2: usize, + /// `x13`: argument register `a3`. Used to pass the fourth argument to a function. + pub a3: usize, + /// `x14`: argument register `a4`. Used to pass the fifth argument to a function. + pub a4: usize, + /// `x15`: argument register `a5`. Used to pass the sixth argument to a function. + pub a5: usize, + /// `x16`: argument register `a6`. Used to pass the seventh argument to a function. + pub a6: usize, + /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. + pub a7: usize, +} diff --git a/esp-lp-hal/src/interrupt/ulp_core.rs b/esp-lp-hal/src/interrupt/ulp_core.rs new file mode 100644 index 00000000000..01f6c7f2ad3 --- /dev/null +++ b/esp-lp-hal/src/interrupt/ulp_core.rs @@ -0,0 +1,170 @@ +//! Interrupt handling for ESP32-S2 & ESP32-S3 RISCV ULP cores. +//! Uses custom R-type instructions for ESP32-S2 & ESP32-S3 RISCV ULP cores. +use super::Interrupt; + +/// Setup interrupt handlers, including any default ones +pub fn setup_interrupts() { + machine_interrupt_enable(true); +} + +/// Enables or disables a peripheral interrupt. +/// +/// Note that interrupts still need to be enabled globally for interrupts +/// to be serviced. +/// +/// Internally, this function maps the interrupt to the appropriate CPU interrupt. +pub fn set_enabled(interrupt: Interrupt, enable: bool) { + // Enable/disable SENS interrupts + unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_ena() + .write(|w| { + #[cfg(esp32s3)] + match interrupt { + Interrupt::TOUCH_DONE_INT => w.sar_cocpu_touch_done_int_ena().bit(enable), + Interrupt::TOUCH_INACTIVE_INT => w.sar_cocpu_touch_inactive_int_ena().bit(enable), + Interrupt::TOUCH_ACTIVE_INT => w.sar_cocpu_touch_active_int_ena().bit(enable), + Interrupt::SARADC1_DONE_INT => w.sar_cocpu_saradc1_int_ena().bit(enable), + Interrupt::SARADC2_DONE_INT => w.sar_cocpu_saradc2_int_ena().bit(enable), + Interrupt::TSENS_DONE_INT => w.sar_cocpu_tsens_int_ena().bit(enable), + Interrupt::RISCV_START_INT => w.sar_cocpu_start_int_ena().bit(enable), + Interrupt::SW_INT => w.sar_cocpu_sw_int_ena().bit(enable), + Interrupt::SWD_INT => w.sar_cocpu_swd_int_ena().bit(enable), + Interrupt::TOUCH_TIME_OUT_INT => w.sar_cocpu_touch_timeout_int_ena().bit(enable), + Interrupt::TOUCH_APPROACH_LOOP_DONE_INT => { + w.sar_cocpu_touch_approach_loop_done_int_ena().bit(enable) + } + Interrupt::TOUCH_SCAN_DONE_INT => w.sar_cocpu_touch_scan_done_int_ena().bit(enable), + // Ignore any other non-SENS interrupts + _ => w, + } + #[cfg(esp32s2)] + match interrupt { + Interrupt::TOUCH_DONE_INT => w.cocpu_touch_done_int_ena().bit(enable), + Interrupt::TOUCH_INACTIVE_INT => w.cocpu_touch_inactive_int_ena().bit(enable), + Interrupt::TOUCH_ACTIVE_INT => w.cocpu_touch_active_int_ena().bit(enable), + Interrupt::SARADC1_DONE_INT => w.cocpu_saradc1_int_ena().bit(enable), + Interrupt::SARADC2_DONE_INT => w.cocpu_saradc2_int_ena().bit(enable), + Interrupt::TSENS_DONE_INT => w.cocpu_tsens_int_ena().bit(enable), + Interrupt::RISCV_START_INT => w.cocpu_start_int_ena().bit(enable), + Interrupt::SW_INT => w.cocpu_sw_int_ena().bit(enable), + Interrupt::SWD_INT => w.cocpu_swd_int_ena().bit(enable), + // Ignore any other non-SENS interrupts + _ => w, + } + }); + + // GPIO interrupt requires no enable/disable handling here, + // as it is handled by the gpio module on a per-pin basis. +} + +/// Clears a peripheral interrupt. +/// For GPIO_INT, clears all interrupts. +pub fn clear(interrupt: Interrupt) { + // Enable/disable SENS interrupts + match interrupt { + Interrupt::GPIO_INT => { + crate::gpio::interrupt::gpio_interrupt_clear( + crate::gpio::interrupt::gpio_interrupt_status(), + ); + } + _ => { + unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_clr() + .write(|w| unsafe { w.bits(interrupt as u32) }); + } + } +} + +/// Returns a bitmask of active interrupts +pub fn status() -> u32 { + let mut status_bits: u32 = 0b0; + // The SENS peripheral status is broken out into individual bit flags + let sens_status = unsafe { &*crate::pac::SENS::PTR } + .sar_cocpu_int_st() + .read() + .bits(); + status_bits |= sens_status; + + // The GPIO peripheral status, is a single bit result, which is 1 if any GPIO interrupt status + // bit is 1. + let gpio_status = unsafe { &*crate::pac::RTC_IO::PTR }.status().read().bits(); + let any_gpio_interrupt = gpio_status != 0; + + #[cfg(esp32s2)] + const SENS_BITFLAGS_LEN: usize = 9; + #[cfg(esp32s3)] + const SENS_BITFLAGS_LEN: usize = 12; + + if any_gpio_interrupt { + status_bits |= 1 << SENS_BITFLAGS_LEN; + } + + status_bits +} + +/// Set the mie register (or equivalent), +/// returning the previous value. +#[inline(always)] +pub fn machine_interrupt_enable(enable: bool) -> bool { + // Does not affect the internal exception bits, + // which are always enabled (unmasked, value here is 0). + // IRQ Type Bit Description + // Internal 0 Internal timer interrupt + // Internal 1 EBREAK/ECALL or Illegal Instruction + // Internal 2 BUS Error (Unaligned Memory Access) + // External 31 RTC peripheral interrupts + let old_mask: u32; + + let disable_exceptions: u32 = 0b111; + let disable_bit: u32 = 1 << 31; + let new_mask: u32 = if enable { + 0b0 + } else { + disable_bit | disable_exceptions + }; + + unsafe { + core::arch::asm!( + "maskirq_insn {}, {}", + out(reg) old_mask, + in(reg) new_mask + ); + } + + // Return previous enabled value, + // where enable == 0 (unmasked) + (old_mask & disable_bit) == 0 +} + +/// Returns the cause of a machine interrupt, +/// which contains the exception code, and if a peripheral interrupt was flagged. +#[unsafe(link_section = ".trap.rust")] +#[inline(always)] +pub fn trap_cause() -> riscv::interrupt::Trap { + // Exception ID, Description + // 2, Illegal instructions + // 3, Breakpoints (EBREAK) + // 6, Misaligned atomic instructions + // + // Bit 31 is used to indicate an interrupt. + + // mcause register does not exist on ULP cores, + // so the Trap must be formed using q-registers + let cause: u32; + unsafe { + core::arch::asm!( + "getq_insn {}, q1", + out(reg) cause + ); + } + + let interrupt: bool = (cause & (0b1 << 31)) != 0; + + if interrupt { + let code = (cause & 0b111) as usize; + riscv::interrupt::Trap::Interrupt(code) + } else { + let code = (cause & 0b1111) as usize; + riscv::interrupt::Trap::Exception(code) + } +} diff --git a/esp-lp-hal/src/lib.rs b/esp-lp-hal/src/lib.rs index 6652c21a6ef..034ccb46c6c 100644 --- a/esp-lp-hal/src/lib.rs +++ b/esp-lp-hal/src/lib.rs @@ -38,9 +38,12 @@ pub use esp32s2_ulp as pac; #[cfg(esp32s3)] pub use esp32s3_ulp as pac; +/// Interrupt handling APIs +pub mod interrupt; + /// The prelude pub mod prelude { - pub use procmacros::entry; + pub use procmacros::{entry, handler}; } cfg_if::cfg_if! { @@ -57,6 +60,20 @@ cfg_if::cfg_if! { pub(crate) static mut CPU_CLOCK: u32 = LP_FAST_CLK_HZ; +// Assembly containing the reset, trap, and startup assembly procedures.. +#[cfg(not(feature = "interrupts"))] +#[cfg(esp32c6)] +global_asm!(include_str!("./asm/lp_start.S")); +#[cfg(feature = "interrupts")] +#[cfg(esp32c6)] +global_asm!(include_str!("./asm/lp_start_with_interrupts.S")); +#[cfg(not(feature = "interrupts"))] +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./asm/ulp_riscv_start.S")); +#[cfg(feature = "interrupts")] +#[cfg(any(esp32s2, esp32s3))] +global_asm!(include_str!("./asm/ulp_riscv_start_with_interrupts.S")); + /// Wake up the HP core pub fn wake_hp_core() { #[cfg(esp32c6)] @@ -73,72 +90,45 @@ pub fn wake_hp_core() { .write(|w| w.rtc_sw_cpu_int().set_bit()); } -#[cfg(esp32c6)] -global_asm!( - r#" - .section .init.vector, "ax" - /* This is the vector table. It is currently empty, but will be populated - * with exception and interrupt handlers when this is supported - */ - - .align 0x4, 0xff - .global _vector_table - .type _vector_table, @function -_vector_table: - .option push - .option norvc - - .rept 32 - nop - .endr - - .option pop - .size _vector_table, .-_vector_table - - .section .init, "ax" - .global reset_vector - -/* The reset vector, jumps to startup code */ -reset_vector: - j __start - -__start: - /* setup the stack pointer */ - la sp, __stack_top - call rust_main -loop: - j loop -"# -); +/// Global GPIO wakeup enable +pub fn gpio_wakeup_enable() { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(true)); + #[cfg(esp32s3)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(true)); +} -#[cfg(any(esp32s2, esp32s3))] -global_asm!( - r#" - .section .text.vectors - .global irq_vector - .global reset_vector - -/* The reset vector, jumps to startup code */ -reset_vector: - j __start - -/* Interrupt handler */ -.balign 16 -irq_vector: - ret - - .section .text - -__start: - /* setup the stack pointer */ - la sp, __stack_top - - call ulp_riscv_rescue_from_monitor - call rust_main -loop: - j loop -"# -); +/// Global GPIO wakeup disable +pub fn gpio_wakeup_disable() { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(false)); + #[cfg(esp32s3)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_ena().bit(false)); +} + +/// Clear the Global GPIO wakeup status +pub fn gpio_wakeup_clear() { + #[cfg(esp32s2)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); + #[cfg(esp32s3)] + unsafe { &*crate::pac::RTC_CNTL::PTR } + .rtc_ulp_cp_timer() + .write(|w| w.ulp_cp_gpio_wakeup_clr().set_bit()); + #[cfg(esp32c6)] + unsafe { &*crate::pac::LP_IO::PTR } + .status_w1tc() + .write(|w| unsafe { w.status_w1tc().bits(0xff) }); +} /// Entry point to the ULP program #[unsafe(link_section = ".init.rust")] @@ -146,9 +136,26 @@ loop: unsafe extern "C" fn lp_core_startup() -> ! { unsafe { unsafe extern "Rust" { + // This symbol will be provided by the user via `#[entry]` fn main(); + + // This variable is provided by the PAC, and used to + // detect multiple calls to Peripherals::take(). + static mut DEVICE_PERIPHERALS: bool; } + #[cfg(any(esp32s2, esp32s3))] + ulp_riscv_rescue_from_monitor(); + + #[cfg(feature = "interrupts")] + interrupt::setup_interrupts(); + + // The pac::DEVICE_PERIPHERALS variable is re-zero-ed on start, + // to prevent it from persisting between calls to main(). + // This prevents ULP-Timer-triggered-main()-calls from panicking + // on their second loop. + DEVICE_PERIPHERALS = false; + #[cfg(esp32c6)] if (*pac::LP_CLKRST::PTR) .lp_clk_conf() @@ -176,7 +183,8 @@ unsafe extern "C" fn ulp_riscv_rescue_from_monitor() { /// Stops the ULP core, called from itself. #[unsafe(link_section = ".init.rust")] -fn ulp_riscv_halt() -> ! { +#[unsafe(no_mangle)] +unsafe extern "C" fn ulp_riscv_halt() -> ! { #[cfg(any(esp32s2, esp32s3))] { unsafe { &*pac::RTC_CNTL::PTR } @@ -188,10 +196,9 @@ fn ulp_riscv_halt() -> ! { }); } - // All chips will enter a no-op loop, when halting. - loop { - unsafe { - core::arch::asm!("addi x0, x0, 0"); // no-op - } - } + // All chips will enter an infinite, when halting. + // It's important that no 'nop' or 'wfi' is performed inside this loop, + // so that the chip silicon can properly detect a ULP halt. + #[allow(clippy::empty_loop)] + loop {} } diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs index ef3079b2551..5998d71d144 100644 --- a/esp-metadata-generated/src/_build_script_utils.rs +++ b/esp-metadata-generated/src/_build_script_utils.rs @@ -2529,6 +2529,7 @@ impl Chip { "gpio_support_deepsleep_wakeup", "uart_support_wakeup_int", "pm_support_ext1_wakeup", + "riscv_coproc_supported", "adc_driver_supported", "aes_driver_supported", "assist_debug_driver_supported", @@ -2815,6 +2816,7 @@ impl Chip { "cargo:rustc-cfg=gpio_support_deepsleep_wakeup", "cargo:rustc-cfg=uart_support_wakeup_int", "cargo:rustc-cfg=pm_support_ext1_wakeup", + "cargo:rustc-cfg=riscv_coproc_supported", "cargo:rustc-cfg=adc_driver_supported", "cargo:rustc-cfg=aes_driver_supported", "cargo:rustc-cfg=assist_debug_driver_supported", @@ -6691,6 +6693,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_twai1)"); println!("cargo:rustc-check-cfg=cfg(lp_core)"); println!("cargo:rustc-check-cfg=cfg(pm_support_beacon_wakeup)"); + println!("cargo:rustc-check-cfg=cfg(riscv_coproc_supported)"); println!("cargo:rustc-check-cfg=cfg(etm_driver_supported)"); println!("cargo:rustc-check-cfg=cfg(lp_uart_driver_supported)"); println!("cargo:rustc-check-cfg=cfg(ulp_riscv_driver_supported)"); @@ -6751,6 +6754,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_dma_copy)"); println!("cargo:rustc-check-cfg=cfg(soc_has_ulp_riscv_core)"); println!("cargo:rustc-check-cfg=cfg(ulp_riscv_core)"); + println!("cargo:rustc-check-cfg=cfg(usb_otg_driver_supported)"); println!("cargo:rustc-check-cfg=cfg(riscv_coproc_supported)"); println!("cargo:rustc-check-cfg=cfg(aes_dma_mode_gcm)"); println!("cargo:rustc-check-cfg=cfg(dedicated_gpio_needs_initialization)"); diff --git a/esp-metadata/devices/esp32c6.toml b/esp-metadata/devices/esp32c6.toml index 95db69085b8..f9c9bf1b841 100644 --- a/esp-metadata/devices/esp32c6.toml +++ b/esp-metadata/devices/esp32c6.toml @@ -30,6 +30,7 @@ symbols = [ "gpio_support_deepsleep_wakeup", "uart_support_wakeup_int", "pm_support_ext1_wakeup", + "riscv_coproc_supported", ] [device.soc] diff --git a/examples/peripheral/lp_core/ulp_gpio_wakeup/.cargo/config.toml b/examples/peripheral/lp_core/ulp_gpio_wakeup/.cargo/config.toml new file mode 100644 index 00000000000..44dacc4278b --- /dev/null +++ b/examples/peripheral/lp_core/ulp_gpio_wakeup/.cargo/config.toml @@ -0,0 +1,24 @@ +[target.'cfg(target_arch = "riscv32")'] +runner = "espflash flash --monitor" +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "force-frame-pointers", +] + +[target.'cfg(target_arch = "xtensa")'] +runner = "espflash flash --monitor" +rustflags = [ + # GNU LD + "-C", "link-arg=-Wl,-Tlinkall.x", + "-C", "link-arg=-nostartfiles", + + # LLD + # "-C", "link-arg=-Tlinkall.x", + # "-C", "linker=rust-lld", +] + +[env] +ESP_LOG = "info" + +[unstable] +build-std = ["core", "alloc"] diff --git a/examples/peripheral/lp_core/ulp_gpio_wakeup/Cargo.toml b/examples/peripheral/lp_core/ulp_gpio_wakeup/Cargo.toml new file mode 100644 index 00000000000..147eac5bfe6 --- /dev/null +++ b/examples/peripheral/lp_core/ulp_gpio_wakeup/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "ulp-gpio-wakeup" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +esp-backtrace = { path = "../../../../esp-backtrace", features = [ + "panic-handler", + "println", +] } +esp-bootloader-esp-idf = { path = "../../../../esp-bootloader-esp-idf" } +esp-hal = { path = "../../../../esp-hal", features = ["log-04", "unstable"] } +esp-println = { path = "../../../../esp-println", features = ["log-04"] } + +[features] +esp32s3 = [ + "esp-backtrace/esp32s3", + "esp-bootloader-esp-idf/esp32s3", + "esp-hal/esp32s3", +] +esp32s2 = [ + "esp-backtrace/esp32s2", + "esp-bootloader-esp-idf/esp32s2", + "esp-hal/esp32s2", +] + +[profile.release] +debug = true +debug-assertions = true +lto = "fat" +codegen-units = 1 diff --git a/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs b/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs new file mode 100644 index 00000000000..23744277378 --- /dev/null +++ b/examples/peripheral/lp_core/ulp_gpio_wakeup/src/main.rs @@ -0,0 +1,57 @@ +//! Demonstrates running the ULP core being woken up from GPIO0 button press. +//! +//! Code on ULP core increments a counter at a fixed memory address, every time it runs. +//! The current value is printed by the HP core. +//! When the user presses the GPIO0 button, the ULP core will be woken up, incrementing the counter. +//! +//! ⚠️ Make sure to first compile the `esp-lp-hal/examples/gpio_wakeup.rs` example ⚠️ +//! +//! The following wiring is assumed: +//! - BUTTON => GPIO0 + +//% CHIPS: esp32s3 esp32s2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + gpio::{WakeEvent, rtc_io::LowPowerInput}, + load_lp_code, + main, + ulp_core::{UlpCore, UlpCoreWakeupSource}, +}; +use esp_println::{print, println}; + +esp_bootloader_esp_idf::esp_app_desc!(); + +#[main] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init(esp_hal::Config::default()); + + // configure GPIO0 as LP input pin, and enable wakeup on it. + let ulp_wakeup_btn = LowPowerInput::new(peripherals.GPIO0); + ulp_wakeup_btn.wakeup_enable(Some(WakeEvent::LowLevel)); + + let mut lp_core = UlpCore::new(peripherals.ULP_RISCV_CORE); + + // load code to LP core + let lp_core_code = load_lp_code!( + "../../../../esp-lp-hal/target/riscv32imc-unknown-none-elf/release/examples/gpio_wakeup" + ); + + // start LP core + lp_core_code.run(&mut lp_core, UlpCoreWakeupSource::Gpio, ulp_wakeup_btn); + + println!("ulp core run"); + + const ADDR: usize = 0x5000_1000; + + let data = (ADDR) as *const u32; + loop { + print!("Current {:x} \u{000d}", unsafe { + data.read_volatile() + }); + } +} diff --git a/examples/peripheral/lp_core/ulp_timer_counter/.cargo/config.toml b/examples/peripheral/lp_core/ulp_timer_counter/.cargo/config.toml new file mode 100644 index 00000000000..44dacc4278b --- /dev/null +++ b/examples/peripheral/lp_core/ulp_timer_counter/.cargo/config.toml @@ -0,0 +1,24 @@ +[target.'cfg(target_arch = "riscv32")'] +runner = "espflash flash --monitor" +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "force-frame-pointers", +] + +[target.'cfg(target_arch = "xtensa")'] +runner = "espflash flash --monitor" +rustflags = [ + # GNU LD + "-C", "link-arg=-Wl,-Tlinkall.x", + "-C", "link-arg=-nostartfiles", + + # LLD + # "-C", "link-arg=-Tlinkall.x", + # "-C", "linker=rust-lld", +] + +[env] +ESP_LOG = "info" + +[unstable] +build-std = ["core", "alloc"] diff --git a/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml new file mode 100644 index 00000000000..94c28bc8eac --- /dev/null +++ b/examples/peripheral/lp_core/ulp_timer_counter/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "ulp-timer-counter" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +esp-backtrace = { path = "../../../../esp-backtrace", features = [ + "panic-handler", + "println", +] } +esp-bootloader-esp-idf = { path = "../../../../esp-bootloader-esp-idf" } +esp-hal = { path = "../../../../esp-hal", features = ["log-04", "unstable"] } +esp-println = { path = "../../../../esp-println", features = ["log-04"] } + +[features] +esp32s3 = [ + "esp-backtrace/esp32s3", + "esp-bootloader-esp-idf/esp32s3", + "esp-hal/esp32s3", +] +esp32s2 = [ + "esp-backtrace/esp32s2", + "esp-bootloader-esp-idf/esp32s2", + "esp-hal/esp32s2", +] + +[profile.release] +debug = true +debug-assertions = true +lto = "fat" +codegen-units = 1 diff --git a/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs new file mode 100644 index 00000000000..38b92b06411 --- /dev/null +++ b/examples/peripheral/lp_core/ulp_timer_counter/src/main.rs @@ -0,0 +1,68 @@ +//! Demonstrates running the ULP core on a roughly 1Hz timer. +//! +//! Code on ULP core increments a counter at a fixed memory address, every time it runs. +//! The current value is printed by the HP core. +//! When the user presses the GPIO0 button, it will fire an interrupt on the ULP core, and reset the +//! counter. +//! +//! ⚠️ Make sure to first compile the `esp-lp-hal/examples/interrupt_counter.rs` example ⚠️ +//! +//! The following wiring is assumed: +//! - BUTTON => GPIO0 + +//% CHIPS: esp32s3 esp32s2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + gpio::rtc_io::LowPowerInput, + load_lp_code, + main, + ulp_core::{UlpCore, UlpCoreTimerCycles, UlpCoreWakeupSource}, +}; +use esp_println::{print, println}; + +esp_bootloader_esp_idf::esp_app_desc!(); + +// Roughly 1Hz on ESP32S3 +#[cfg(feature = "esp32s3")] +const ULP_SLEEP_CYCLES: u32 = 530; +// Roughly 1Hz on ESP32S2 +#[cfg(feature = "esp32s2")] +const ULP_SLEEP_CYCLES: u32 = 1060; + +#[main] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init(esp_hal::Config::default()); + + // configure GPIO0 as LP input pin, which will reset the counter + let ulp_counter_reset_btn = LowPowerInput::new(peripherals.GPIO0); + + let mut lp_core = UlpCore::new(peripherals.ULP_RISCV_CORE); + + // load code to LP core + let lp_core_code = load_lp_code!( + "../../../../esp-lp-hal/target/riscv32imc-unknown-none-elf/release/examples/interrupt_counter" + ); + + // start LP core + lp_core_code.run( + &mut lp_core, + UlpCoreWakeupSource::Timer(UlpCoreTimerCycles::new(ULP_SLEEP_CYCLES)), + ulp_counter_reset_btn, + ); + + println!("ulp core run"); + + const ADDR: usize = 0x5000_1000; + + let data = (ADDR) as *const u32; + loop { + print!("Current {:x} \u{000d}", unsafe { + data.read_volatile() + }); + } +}