Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
3daab42
Add IRQ handler, add custom R-type instruction macros, move out reset…
leighleighleigh Mar 17, 2026
5e2846e
Moving some riscv-rt stuff into here
leighleighleigh Mar 20, 2026
7f4bdd7
ULP is working, no interrupt handling yet
leighleighleigh Mar 21, 2026
3017228
Updated interrupt handler to save/restore TrapFrame like riscv-rt, bu…
leighleighleigh Mar 21, 2026
0503f7f
Really basic interrupt handling is working, adapted from picorv32-rt
leighleighleigh Mar 21, 2026
ef03250
Undo some non-functional formatting changes to reduce churn
leighleighleigh Mar 22, 2026
8f9e92f
Added some gross interrupt binding macros, re-used the SENS interrupt…
leighleighleigh Mar 23, 2026
6b4f2b5
Hacky .listen() and .unlisten() methods on GPIO pins
leighleighleigh Mar 23, 2026
6cccbf1
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Mar 23, 2026
ccfc8d8
Add GPIO wakeup method to LP gpio pins.
leighleighleigh Mar 23, 2026
8bd4cb6
Hide interrupt macros on non-supported chips
leighleighleigh Mar 23, 2026
f50d455
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Mar 27, 2026
8d0749e
Exploring a cleaner interrupt binding approach
leighleighleigh Mar 28, 2026
d344dfc
Satisfy clippy
leighleighleigh Mar 28, 2026
2c1a07e
Incomplete migration of a bunch of interrupt handling stuffs
leighleighleigh Mar 29, 2026
6df37f5
Semi-functional port of Io interrupt handling APIs
leighleighleigh Mar 29, 2026
f03e89b
Cleanup feature gating, add non-working critical section impl
leighleighleigh Mar 30, 2026
2a6ed84
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 2, 2026
fce1661
Updated critical section implementation to handle nesting properly
leighleighleigh Apr 2, 2026
250fe93
WIP Switch to using interrupt vectors exported by PAC
leighleighleigh Apr 2, 2026
2c31ed0
Update interrupt_counter.rs to align with ESP-HAL example
leighleighleigh Apr 3, 2026
f714b6f
Added interupt::disable() function
leighleighleigh Apr 3, 2026
d35fcbd
Make clippy happy again
leighleighleigh Apr 3, 2026
e081c2f
Remove duplicate example name causing havok in CI
leighleighleigh Apr 3, 2026
20e8d0e
Update CHANGELOG.md
leighleighleigh Apr 3, 2026
4e63d55
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 3, 2026
abdbca3
Update link-ulp.x
leighleighleigh Apr 3, 2026
407ca83
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 7, 2026
6c9d54d
Clear `DEVICE_PERIPHERALS` PAC variable to prevent `Peripheral::take(…
leighleighleigh Apr 7, 2026
fecb21f
Dont overwrite code region in lp_core::load_lp_code, until run is called
leighleighleigh Apr 7, 2026
d2343bf
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 11, 2026
899f0a2
Update interrupt_counter.rs
leighleighleigh Apr 12, 2026
d8c3623
Address comments, revert procmacro change to avoid breaking user code…
leighleighleigh Apr 12, 2026
d203bae
Added HP core example
leighleighleigh Apr 12, 2026
fc52c21
Update changelog
leighleighleigh Apr 12, 2026
83fbf87
Update HP core example
leighleighleigh Apr 12, 2026
c87caba
Add ULP-core APIs for GPIO wakeup
leighleighleigh Apr 12, 2026
30be234
Bump PAC version, add GPIO wakeup_enable to rtc_io pins, update examples
leighleighleigh Apr 13, 2026
c7b203c
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 13, 2026
12841ea
Update gpio_wakeup.rs
leighleighleigh Apr 13, 2026
5672c72
Update interrupt_counter.rs
leighleighleigh Apr 13, 2026
f5e008f
Remove feature flag requirement on lp examples, use CHIPS to gate the…
leighleighleigh Apr 14, 2026
53d72f1
Added 'interrupts' feature flag for ULP core interrupt support. Added…
leighleighleigh Apr 16, 2026
1e252b2
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh Apr 18, 2026
787b431
Tweak gpio_wakeup_enable, update deps
leighleighleigh Apr 18, 2026
a0b0b3f
Hide interrupt support from ESP32C6 until implemented
leighleighleigh Apr 18, 2026
013b2f2
Fix rtc_io.wakeup_enable on esp32s2
leighleighleigh Apr 18, 2026
ebfd35f
Add more stub implementations for esp32c6, so CI can pass
leighleighleigh Apr 18, 2026
f396396
Fix C6 compilation due to stray todo
leighleighleigh Apr 18, 2026
e7a3515
Clean-up of RTC/LP IO wakeup_enable method. Implementing WakeupFromLp…
leighleighleigh Apr 19, 2026
c0f1524
Start fixing up the ESP32C6-LP interrupt handling
leighleighleigh Apr 25, 2026
6e5fa4d
Added interrupt::clear() function
leighleighleigh Apr 27, 2026
294ba4b
Brought back gpio_wakeup_disable
leighleighleigh Apr 28, 2026
e6f5ef2
Merged main into leighleighleigh/ulp-interrupt-handler
leighleighleigh May 2, 2026
662d24f
Bump esp-pacs to use un-released commits
leighleighleigh May 2, 2026
9b066aa
Remove _start_trap assembly code when `interrupts` feature disabled
leighleighleigh May 3, 2026
7ac400f
Merge branch 'main' into leighleighleigh/ulp-interrupt-handler
leighleighleigh May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
13 changes: 12 additions & 1 deletion esp-hal/src/gpio/lp_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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<WakeEvent>) {
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
Expand Down
21 changes: 19 additions & 2 deletions esp-hal/src/gpio/rtc_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use core::marker::PhantomData;

use super::{InputPin, OutputPin, RtcPin};
use crate::{
gpio::RtcFunction,
gpio::{RtcFunction, WakeEvent},
peripherals::{GPIO, RTC_IO},
};

Expand Down Expand Up @@ -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
}
Expand All @@ -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<WakeEvent>) {
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
Expand Down
4 changes: 2 additions & 2 deletions esp-hal/src/rtc_cntl/sleep/esp32s2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{
Ext0WakeupSource,
Ext1WakeupSource,
TimerWakeupSource,
UlpWakeupSource,
WakeFromUlpCoreWakeupSource,
WakeSource,
WakeTriggers,
WakeupLevel,
Expand Down Expand Up @@ -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<'_>,
Expand Down
4 changes: 2 additions & 2 deletions esp-hal/src/rtc_cntl/sleep/esp32s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{
Ext0WakeupSource,
Ext1WakeupSource,
TimerWakeupSource,
UlpWakeupSource,
WakeFromUlpCoreWakeupSource,
WakeSource,
WakeTriggers,
WakeupLevel,
Expand Down Expand Up @@ -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<'_>,
Expand Down
8 changes: 4 additions & 4 deletions esp-hal/src/rtc_cntl/sleep/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -422,7 +422,7 @@ impl UlpWakeupSource {
}

#[cfg(any(esp32s2, esp32s3))]
impl Default for UlpWakeupSource {
impl Default for WakeFromUlpCoreWakeupSource {
fn default() -> Self {
Self::new()
}
Expand Down
4 changes: 4 additions & 0 deletions esp-hal/src/soc/esp32c6/lp_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) });
Expand All @@ -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 => {}
}
}
7 changes: 7 additions & 0 deletions esp-hal/src/soc/esp32s2/ulp_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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());
}
}
}
77 changes: 52 additions & 25 deletions esp-hal/src/soc/esp32s3/ulp_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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());
}
}
}
2 changes: 2 additions & 0 deletions esp-lp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
Loading
Loading