From 362eba9b97235b18cc67ca13328312dabb51c523 Mon Sep 17 00:00:00 2001 From: liebman Date: Sat, 27 Sep 2025 14:09:51 -0700 Subject: support low-power executor on STM32WLEx --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/low_power.rs | 23 ++++++++++++++++++++--- embassy-stm32/src/rtc/low_power.rs | 4 ++-- embassy-stm32/src/rtc/v3.rs | 4 ++-- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index a9ab78e31..71989eb3d 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: usart: fix race condition in ringbuffered usart - feat: stm32/fdcan: add ability to control automatic recovery from bus off ([#4821](https://github.com/embassy-rs/embassy/pull/4821)) - low-power: update rtc api to allow reconfig +- feat: Added RTC low-power support for STM32WLEx ([#4716](https://github.com/embassy-rs/embassy/pull/4716)) ## 0.4.0 - 2025-08-26 diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 74cd11b93..3eb626abe 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -127,10 +127,10 @@ pub enum StopMode { Stop2, } -#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32u0))] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] use stm32_metapac::pwr::vals::Lpms; -#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32u0))] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] impl Into for StopMode { fn into(self) -> Lpms { match self { @@ -207,7 +207,7 @@ impl Executor { #[allow(unused_variables)] fn configure_stop(&mut self, stop_mode: StopMode) { - #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba))] + #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba, stm32wlex))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); #[cfg(stm32h5)] crate::pac::PWR.pmcr().modify(|v| { @@ -219,6 +219,11 @@ impl Executor { fn configure_pwr(&mut self) { self.scb.clear_sleepdeep(); + // Clear any previous stop flags + #[cfg(stm32wlex)] + crate::pac::PWR.extscr().modify(|w| { + w.set_c1cssf(true); + }); compiler_fence(Ordering::SeqCst); @@ -270,8 +275,20 @@ impl Executor { loop { unsafe { executor.inner.poll(); + trace!("low power: calling configure_pwr"); self.configure_pwr(); + trace!("low power: after configure_pwr"); + asm!("wfe"); + + trace!("low power: awake from 'wfe'"); + #[cfg(stm32wlex)] + { + let es = crate::pac::PWR.extscr().read(); + trace!("low power: C1SBF: {}", es.c1sbf()); + trace!("low power: C1STOPF: {}", es.c1stopf()); + trace!("low power: C1STOP2F: {}", es.c1stop2f()); + } }; } } diff --git a/embassy-stm32/src/rtc/low_power.rs b/embassy-stm32/src/rtc/low_power.rs index 9e0f03879..e09d5afb0 100644 --- a/embassy-stm32/src/rtc/low_power.rs +++ b/embassy-stm32/src/rtc/low_power.rs @@ -68,7 +68,7 @@ pub(crate) enum WakeupPrescaler { } #[cfg(any( - stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba + stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba, stm32wlex ))] impl From for crate::pac::rtc::vals::Wucksel { fn from(val: WakeupPrescaler) -> Self { @@ -84,7 +84,7 @@ impl From for crate::pac::rtc::vals::Wucksel { } #[cfg(any( - stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba + stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba, stm32wlex ))] impl From for WakeupPrescaler { fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs index 528dc78b4..f7ebea73e 100644 --- a/embassy-stm32/src/rtc/v3.rs +++ b/embassy-stm32/src/rtc/v3.rs @@ -131,7 +131,7 @@ impl SealedInstance for crate::peripherals::RTC { #[cfg(feature = "low-power")] cfg_if::cfg_if!( - if #[cfg(stm32g4)] { + if #[cfg(any(stm32g4, stm32wlex))] { const EXTI_WAKEUP_LINE: usize = 20; } else if #[cfg(stm32g0)] { const EXTI_WAKEUP_LINE: usize = 19; @@ -142,7 +142,7 @@ impl SealedInstance for crate::peripherals::RTC { #[cfg(feature = "low-power")] cfg_if::cfg_if!( - if #[cfg(stm32g4)] { + if #[cfg(any(stm32g4, stm32wlex))] { type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; } else if #[cfg(any(stm32g0, stm32u0))] { type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; -- cgit From 676b1f3b67e36d73bac18d4235c8b2c6a4c4c972 Mon Sep 17 00:00:00 2001 From: liebman Date: Wed, 8 Oct 2025 13:54:17 -0700 Subject: rcc::reinit - only need RCC for the length of the call --- embassy-stm32/src/rcc/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index addfca3c3..c817dd4b7 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -390,7 +390,7 @@ pub fn disable() { /// /// This should only be called after `init`. #[cfg(not(feature = "_dual-core"))] -pub fn reinit<'a>(config: Config, _rcc: &'a mut crate::Peri<'a, crate::peripherals::RCC>) { +pub fn reinit(config: Config, _rcc: &'_ mut crate::Peri<'_, crate::peripherals::RCC>) { critical_section::with(|cs| init_rcc(cs, config)) } -- cgit From 2f1935e3051b1d5c1523b214349aabd198820f4f Mon Sep 17 00:00:00 2001 From: liebman Date: Sun, 12 Oct 2025 08:40:40 -0700 Subject: stm32wlex: init/restore timer config after STOP2 --- embassy-stm32/src/low_power.rs | 24 ++++++++++++++-------- embassy-stm32/src/rcc/l.rs | 43 ++++++++++++++++++++++++++++++++++++++++ embassy-stm32/src/time_driver.rs | 32 +++++++++++++++++++++++++++--- 3 files changed, 88 insertions(+), 11 deletions(-) diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 3eb626abe..4ebded8a5 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -180,6 +180,16 @@ impl Executor { } unsafe fn on_wakeup_irq(&mut self) { + #[cfg(stm32wlex)] + if crate::pac::PWR.extscr().read().c1stop2f() { + // when we wake from STOP2, we need to re-initialize the rcc and the time driver + // to restore the clocks to their last configured state + crate::rcc::apply_resume_config(); + critical_section::with(|cs| crate::time_driver::init_timer(cs)); + // reset the refcounts for STOP2 and STOP1 (initializing the time driver will increment one of them for the timer) + crate::rcc::REFCOUNT_STOP2 = 0; + crate::rcc::REFCOUNT_STOP1 = 0; + } self.time_driver.resume_time(); trace!("low power: resume"); } @@ -275,19 +285,17 @@ impl Executor { loop { unsafe { executor.inner.poll(); - trace!("low power: calling configure_pwr"); self.configure_pwr(); - trace!("low power: after configure_pwr"); - asm!("wfe"); - - trace!("low power: awake from 'wfe'"); #[cfg(stm32wlex)] { let es = crate::pac::PWR.extscr().read(); - trace!("low power: C1SBF: {}", es.c1sbf()); - trace!("low power: C1STOPF: {}", es.c1stopf()); - trace!("low power: C1STOP2F: {}", es.c1stop2f()); + match (es.c1stopf(), es.c1stop2f()) { + (true, false) => trace!("low power: wake from STOP1"), + (false, true) => trace!("low power: wake from STOP2"), + (true, true) => trace!("low power: wake from STOP1 and STOP2 ???"), + (false, false) => trace!("low power: stop mode not entered"), + }; } }; } diff --git a/embassy-stm32/src/rcc/l.rs b/embassy-stm32/src/rcc/l.rs index 2e1cbd702..584957c6d 100644 --- a/embassy-stm32/src/rcc/l.rs +++ b/embassy-stm32/src/rcc/l.rs @@ -1,3 +1,6 @@ +#[cfg(all(feature = "low-power", stm32wlex))] +use core::mem::MaybeUninit; + #[cfg(any(stm32l0, stm32l1))] pub use crate::pac::pwr::vals::Vos as VoltageScale; use crate::pac::rcc::regs::Cfgr; @@ -11,6 +14,42 @@ use crate::time::Hertz; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); +/// Saved RCC Config +/// +/// Used when exiting STOP2 to re-enable clocks to their last configured state +/// for chips that need it. +#[cfg(all(feature = "low-power", stm32wlex))] +static mut RESUME_RCC_CONFIG: MaybeUninit = MaybeUninit::uninit(); + +/// Set the rcc config to be restored when exiting STOP2 +/// +/// Safety: Sets a mutable global. +#[cfg(all(feature = "low-power", stm32wlex))] +pub(crate) unsafe fn set_resume_config(config: Config) { + trace!("rcc set_resume_config()"); + RESUME_RCC_CONFIG = MaybeUninit::new(config); +} + +/// Get the rcc config to be restored when exiting STOP2 +/// +/// Safety: Reads a mutable global. +#[cfg(all(feature = "low-power", stm32wlex))] +pub(crate) unsafe fn get_resume_config() -> Config { + *(*core::ptr::addr_of_mut!(RESUME_RCC_CONFIG)).assume_init_ref() +} + +#[cfg(all(feature = "low-power", stm32wlex))] +/// Safety: should only be called from low power executable just after resuming from STOP2 +pub(crate) unsafe fn apply_resume_config() { + trace!("rcc apply_resume_config()"); + + while RCC.cfgr().read().sws() != Sysclk::MSI {} + + let config = get_resume_config(); + + init(config); +} + #[derive(Clone, Copy, Eq, PartialEq)] pub enum HseMode { /// crystal/ceramic oscillator (HSEBYP=0) @@ -154,6 +193,10 @@ fn msi_enable(range: MSIRange) { } pub(crate) unsafe fn init(config: Config) { + // save the rcc config because if we enter stop 2 we need to re-apply it on wakeup + #[cfg(all(feature = "low-power", stm32wlex))] + set_resume_config(config); + // Switch to MSI to prevent problems with PLL configuration. if !RCC.cr().read().msion() { // Turn on MSI and configure it to 4MHz. diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 7fd5751b3..e1229c3e3 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -1,6 +1,8 @@ #![allow(non_snake_case)] use core::cell::{Cell, RefCell}; +#[cfg(all(feature = "low-power", stm32wlex))] +use core::sync::atomic::AtomicU16; use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; use critical_section::CriticalSection; @@ -214,6 +216,9 @@ pub(crate) struct RtcDriver { alarm: Mutex, #[cfg(feature = "low-power")] rtc: Mutex>>, + /// Saved count for the timer (its value is lost when entering STOP2) + #[cfg(all(feature = "low-power", stm32wlex))] + saved_count: AtomicU16, queue: Mutex>, } @@ -222,11 +227,15 @@ embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), #[cfg(feature = "low-power")] rtc: Mutex::const_new(CriticalSectionRawMutex::new(), RefCell::new(None)), + #[cfg(all(feature = "low-power", stm32wlex))] + saved_count: AtomicU16::new(0), queue: Mutex::new(RefCell::new(Queue::new())) }); impl RtcDriver { - fn init(&'static self, cs: critical_section::CriticalSection) { + /// initialize the timer, but don't start it. Used for chips like stm32wle5 + /// for low power where the timer config is lost in STOP2. + fn init_timer(&'static self, cs: critical_section::CriticalSection) { let r = regs_gp16(); rcc::enable_and_reset_with_cs::(cs); @@ -234,6 +243,9 @@ impl RtcDriver { let timer_freq = T::frequency(); r.cr1().modify(|w| w.set_cen(false)); + #[cfg(all(feature = "low-power", stm32wlex))] + r.cnt().write(|w| w.set_cnt(self.saved_count.load(Ordering::SeqCst))); + #[cfg(not(any(feature = "low-power", stm32wlex)))] r.cnt().write(|w| w.set_cnt(0)); let psc = timer_freq.0 / TICK_HZ as u32 - 1; @@ -261,8 +273,11 @@ impl RtcDriver { ::CaptureCompareInterrupt::unpend(); unsafe { ::CaptureCompareInterrupt::enable() }; + } - r.cr1().modify(|w| w.set_cen(true)); + fn init(&'static self, cs: CriticalSection) { + self.init_timer(cs); + regs_gp16().cr1().modify(|w| w.set_cen(true)); } fn on_interrupt(&self) { @@ -420,6 +435,10 @@ impl RtcDriver { let time_until_next_alarm = self.time_until_next_alarm(cs); if time_until_next_alarm < Self::MIN_STOP_PAUSE { + trace!( + "time_until_next_alarm < Self::MIN_STOP_PAUSE ({})", + time_until_next_alarm + ); Err(()) } else { self.rtc @@ -430,7 +449,9 @@ impl RtcDriver { .start_wakeup_alarm(time_until_next_alarm, cs); regs_gp16().cr1().modify(|w| w.set_cen(false)); - + #[cfg(all(feature = "low-power", stm32wlex))] + self.saved_count + .store(regs_gp16().cnt().read().cnt() as u16, Ordering::SeqCst); Ok(()) } }) @@ -528,3 +549,8 @@ pub(crate) fn get_driver() -> &'static RtcDriver { pub(crate) fn init(cs: CriticalSection) { DRIVER.init(cs) } + +#[cfg(all(feature = "low-power", stm32wlex))] +pub(crate) fn init_timer(cs: CriticalSection) { + DRIVER.init_timer(cs) +} -- cgit From e4eac457b3b86fd89c215c386216f7cf0873ba38 Mon Sep 17 00:00:00 2001 From: liebman Date: Mon, 13 Oct 2025 13:39:16 -0700 Subject: stm32wlex: restore timer near the end of RtcDriver::init_timer --- embassy-stm32/src/time_driver.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index e1229c3e3..4956d1f68 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -243,9 +243,6 @@ impl RtcDriver { let timer_freq = T::frequency(); r.cr1().modify(|w| w.set_cen(false)); - #[cfg(all(feature = "low-power", stm32wlex))] - r.cnt().write(|w| w.set_cnt(self.saved_count.load(Ordering::SeqCst))); - #[cfg(not(any(feature = "low-power", stm32wlex)))] r.cnt().write(|w| w.set_cnt(0)); let psc = timer_freq.0 / TICK_HZ as u32 - 1; @@ -271,6 +268,9 @@ impl RtcDriver { w.set_ccie(0, true); }); + #[cfg(all(feature = "low-power", stm32wlex))] + r.cnt().write(|w| w.set_cnt(self.saved_count.load(Ordering::SeqCst))); + ::CaptureCompareInterrupt::unpend(); unsafe { ::CaptureCompareInterrupt::enable() }; } @@ -449,7 +449,8 @@ impl RtcDriver { .start_wakeup_alarm(time_until_next_alarm, cs); regs_gp16().cr1().modify(|w| w.set_cen(false)); - #[cfg(all(feature = "low-power", stm32wlex))] + // save the count for the timer as its lost in STOP2 for stm32wlex + #[cfg(stm32wlex)] self.saved_count .store(regs_gp16().cnt().read().cnt() as u16, Ordering::SeqCst); Ok(()) -- cgit From 36aa3e10aaf27bb1bd1109a203b378dc93b90b02 Mon Sep 17 00:00:00 2001 From: liebman Date: Wed, 22 Oct 2025 07:16:43 -0700 Subject: low_power: i2c wakeup --- embassy-stm32/src/i2c/v2.rs | 5 +++++ embassy-stm32/src/low_power.rs | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 01b6b8800..a2184630f 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -70,6 +70,11 @@ fn debug_print_interrupts(isr: stm32_metapac::i2c::regs::Isr) { } pub(crate) unsafe fn on_interrupt() { + // restore the clocks to their last configured state as + // much is lost in STOP modes + #[cfg(all(feature = "low-power", stm32wlex))] + crate::low_power::on_wakeup_irq(); + let regs = T::info().regs; let isr = regs.isr().read(); diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 4ebded8a5..4cdaf6a00 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -180,16 +180,19 @@ impl Executor { } unsafe fn on_wakeup_irq(&mut self) { + // when we wake from STOP2, we need to re-initialize the rcc and the time driver + // to restore the clocks to their last configured state + #[cfg(stm32wlex)] + crate::rcc::apply_resume_config(); #[cfg(stm32wlex)] if crate::pac::PWR.extscr().read().c1stop2f() { - // when we wake from STOP2, we need to re-initialize the rcc and the time driver - // to restore the clocks to their last configured state - crate::rcc::apply_resume_config(); critical_section::with(|cs| crate::time_driver::init_timer(cs)); // reset the refcounts for STOP2 and STOP1 (initializing the time driver will increment one of them for the timer) + // and given that we just woke from STOP2, we can reset them crate::rcc::REFCOUNT_STOP2 = 0; crate::rcc::REFCOUNT_STOP1 = 0; } + self.time_driver.resume_time(); trace!("low power: resume"); } -- cgit From ea94cb58a2016b1ab408aa975192de7aefe52ec5 Mon Sep 17 00:00:00 2001 From: liebman Date: Wed, 22 Oct 2025 14:24:01 -0700 Subject: use DeviceBusy to mark when stop1 or stop2 is unavailable. --- embassy-stm32/src/i2c/v2.rs | 10 ++++++ embassy-stm32/src/low_power.rs | 78 +++++++++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index a2184630f..6f2d03bd1 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -819,6 +819,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Write. pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { + #[cfg(all(feature = "low-power", stm32wlex))] + let _device_busy = crate::low_power::DeviceBusy::new(); let timeout = self.timeout(); if write.is_empty() { self.write_internal(address.into(), write, true, timeout) @@ -833,6 +835,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// /// The buffers are concatenated in a single write transaction. pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> { + #[cfg(all(feature = "low-power", stm32wlex))] + let _device_busy = crate::low_power::DeviceBusy::new(); let timeout = self.timeout(); if write.is_empty() { @@ -856,6 +860,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Read. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + #[cfg(all(feature = "low-power", stm32wlex))] + let _device_busy = crate::low_power::DeviceBusy::new(); let timeout = self.timeout(); if buffer.is_empty() { @@ -868,6 +874,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// Write, restart, read. pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + #[cfg(all(feature = "low-power", stm32wlex))] + let _device_busy = crate::low_power::DeviceBusy::new(); let timeout = self.timeout(); if write.is_empty() { @@ -893,6 +901,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { /// /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { + #[cfg(all(feature = "low-power", stm32wlex))] + let _device_busy = crate::low_power::DeviceBusy::new(); let _ = addr; let _ = operations; todo!() diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 4cdaf6a00..0ee81fd18 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -68,6 +68,39 @@ use crate::rtc::Rtc; static mut EXECUTOR: Option = None; +#[cfg(stm32wlex)] +pub(crate) use self::busy::DeviceBusy; +#[cfg(stm32wlex)] +mod busy { + use core::sync::atomic::{AtomicU32, Ordering}; + + // Count of devices blocking STOP + static STOP_BLOCKED: AtomicU32 = AtomicU32::new(0); + + /// Check if STOP1 is blocked. + pub(crate) fn stop_blocked() -> bool { + STOP_BLOCKED.load(Ordering::SeqCst) > 0 + } + + /// When ca device goes busy it will construct one of these where it will be dropped when the device goes idle. + pub(crate) struct DeviceBusy {} + + impl DeviceBusy { + /// Create a new DeviceBusy. + pub(crate) fn new() -> Self { + STOP_BLOCKED.fetch_add(1, Ordering::SeqCst); + trace!("low power: device busy: Stop:{}", STOP_BLOCKED.load(Ordering::SeqCst)); + Self {} + } + } + + impl Drop for DeviceBusy { + fn drop(&mut self) { + STOP_BLOCKED.fetch_sub(1, Ordering::SeqCst); + trace!("low power: device idle: Stop:{}", STOP_BLOCKED.load(Ordering::SeqCst)); + } + } +} #[cfg(not(stm32u0))] foreach_interrupt! { (RTC, rtc, $block:ident, WKUP, $irq:ident) => { @@ -92,6 +125,7 @@ foreach_interrupt! { #[allow(dead_code)] pub(crate) unsafe fn on_wakeup_irq() { + info!("low power: on wakeup irq: extscr: {:?}", crate::pac::PWR.extscr().read()); EXECUTOR.as_mut().unwrap().on_wakeup_irq(); } @@ -180,19 +214,22 @@ impl Executor { } unsafe fn on_wakeup_irq(&mut self) { - // when we wake from STOP2, we need to re-initialize the rcc and the time driver - // to restore the clocks to their last configured state #[cfg(stm32wlex)] - crate::rcc::apply_resume_config(); - #[cfg(stm32wlex)] - if crate::pac::PWR.extscr().read().c1stop2f() { - critical_section::with(|cs| crate::time_driver::init_timer(cs)); - // reset the refcounts for STOP2 and STOP1 (initializing the time driver will increment one of them for the timer) - // and given that we just woke from STOP2, we can reset them - crate::rcc::REFCOUNT_STOP2 = 0; - crate::rcc::REFCOUNT_STOP1 = 0; + { + let extscr = crate::pac::PWR.extscr().read(); + if extscr.c1stop2f() || extscr.c1stopf() { + // when we wake from any stop mode we need to re-initialize the rcc + crate::rcc::apply_resume_config(); + if extscr.c1stop2f() { + // when we wake from STOP2, we need to re-initialize the time driver + critical_section::with(|cs| crate::time_driver::init_timer(cs)); + // reset the refcounts for STOP2 and STOP1 (initializing the time driver will increment one of them for the timer) + // and given that we just woke from STOP2, we can reset them + crate::rcc::REFCOUNT_STOP2 = 0; + crate::rcc::REFCOUNT_STOP1 = 0; + } + } } - self.time_driver.resume_time(); trace!("low power: resume"); } @@ -208,10 +245,22 @@ impl Executor { self.time_driver.reconfigure_rtc(f); } + fn stop1_ok_to_enter(&self) -> bool { + #[cfg(stm32wlex)] + if self::busy::stop_blocked() { + return false; + } + unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } + } + + fn stop2_ok_to_enter(&self) -> bool { + self.stop1_ok_to_enter() && unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } + } + fn stop_mode(&self) -> Option { - if unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } && unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { + if self.stop2_ok_to_enter() { Some(StopMode::Stop2) - } else if unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { + } else if self.stop1_ok_to_enter() { Some(StopMode::Stop1) } else { None @@ -299,6 +348,9 @@ impl Executor { (true, true) => trace!("low power: wake from STOP1 and STOP2 ???"), (false, false) => trace!("low power: stop mode not entered"), }; + crate::pac::PWR.extscr().modify(|w| { + w.set_c1cssf(false); + }); } }; } -- cgit From 57f0284f13859fd7b2fb9c3870f7624439924990 Mon Sep 17 00:00:00 2001 From: liebman Date: Sun, 26 Oct 2025 11:42:50 -0700 Subject: stm32wlex: low-power for adc --- embassy-stm32/src/adc/v3.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index d9a3ce21d..e5b9a5d85 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -430,6 +430,9 @@ impl<'d, T: Instance> Adc<'d, T> { "Asynchronous read sequence cannot be more than 16 in length" ); + #[cfg(all(feature = "low-power", stm32wlex))] + let _device_busy = crate::low_power::DeviceBusy::new(); + // Ensure no conversions are ongoing and ADC is enabled. Self::cancel_conversions(); self.enable(); -- cgit From 0d6e782b6405332ebc8251f18017d005dc3f370a Mon Sep 17 00:00:00 2001 From: liebman Date: Sun, 26 Oct 2025 11:44:10 -0700 Subject: stm32wlex: remove gratuitous log --- embassy-stm32/src/low_power.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 0ee81fd18..ecb36a8d7 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -125,7 +125,6 @@ foreach_interrupt! { #[allow(dead_code)] pub(crate) unsafe fn on_wakeup_irq() { - info!("low power: on wakeup irq: extscr: {:?}", crate::pac::PWR.extscr().read()); EXECUTOR.as_mut().unwrap().on_wakeup_irq(); } @@ -343,9 +342,9 @@ impl Executor { { let es = crate::pac::PWR.extscr().read(); match (es.c1stopf(), es.c1stop2f()) { - (true, false) => trace!("low power: wake from STOP1"), - (false, true) => trace!("low power: wake from STOP2"), - (true, true) => trace!("low power: wake from STOP1 and STOP2 ???"), + (true, false) => debug!("low power: wake from STOP1"), + (false, true) => debug!("low power: wake from STOP2"), + (true, true) => debug!("low power: wake from STOP1 and STOP2 ???"), (false, false) => trace!("low power: stop mode not entered"), }; crate::pac::PWR.extscr().modify(|w| { -- cgit From 9a10134df204875e9cf85b3113471c6b3c78e70c Mon Sep 17 00:00:00 2001 From: liebman Date: Sun, 26 Oct 2025 11:46:12 -0700 Subject: don't use embassy_time for blocking_delay_us on stm32wlex low-power --- embassy-stm32/src/adc/mod.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 22ed8295f..ea7341f75 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -87,14 +87,18 @@ pub(crate) trait SealedAdcChannel { /// Performs a busy-wait delay for a specified number of microseconds. #[allow(unused)] pub(crate) fn blocking_delay_us(us: u32) { - #[cfg(feature = "time")] - embassy_time::block_for(embassy_time::Duration::from_micros(us as u64)); - #[cfg(not(feature = "time"))] - { - let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; - let us = us as u64; - let cycles = freq * us / 1_000_000; - cortex_m::asm::delay(cycles as u32); + cfg_if::cfg_if! { + // this does strange things on stm32wlx in low power mode depending on exactly when it's called + // as in sometimes 15 us (1 tick) would take > 20 seconds. + if #[cfg(all(feature = "time", all(not(feature = "low-power"), not(stm32wlex))))] { + let duration = embassy_time::Duration::from_micros(us as u64); + embassy_time::block_for(duration); + } else { + let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; + let us = us as u64; + let cycles = freq * us / 1_000_000; + cortex_m::asm::delay(cycles as u32); + } } } -- cgit From ccce1478c161662521a77284222f5710b1ee34d9 Mon Sep 17 00:00:00 2001 From: liebman Date: Wed, 29 Oct 2025 14:33:50 -0700 Subject: low_power: on_wakeup_irq: don't call the executor if its not there --- embassy-stm32/src/low_power.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index ecb36a8d7..cde3153f6 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -125,7 +125,10 @@ foreach_interrupt! { #[allow(dead_code)] pub(crate) unsafe fn on_wakeup_irq() { - EXECUTOR.as_mut().unwrap().on_wakeup_irq(); + if EXECUTOR.is_some() { + trace!("low power: wakeup irq"); + EXECUTOR.as_mut().unwrap().on_wakeup_irq(); + } } /// Configure STOP mode with RTC. -- cgit From 46480285783390d90f8d99e530a1da28a292dc3c Mon Sep 17 00:00:00 2001 From: liebman Date: Wed, 29 Oct 2025 14:47:06 -0700 Subject: examples: add low-power examples for `stm32wlex` --- examples/stm32wle5/.cargo/config.toml | 9 +++ examples/stm32wle5/Cargo.toml | 38 ++++++++++ examples/stm32wle5/README.md | 45 ++++++++++++ examples/stm32wle5/build.rs | 5 ++ examples/stm32wle5/src/bin/adc.rs | 105 ++++++++++++++++++++++++++++ examples/stm32wle5/src/bin/blinky.rs | 95 +++++++++++++++++++++++++ examples/stm32wle5/src/bin/button_exti.rs | 96 +++++++++++++++++++++++++ examples/stm32wle5/stm32wle5.code-workspace | 13 ++++ 8 files changed, 406 insertions(+) create mode 100644 examples/stm32wle5/.cargo/config.toml create mode 100644 examples/stm32wle5/Cargo.toml create mode 100644 examples/stm32wle5/README.md create mode 100644 examples/stm32wle5/build.rs create mode 100644 examples/stm32wle5/src/bin/adc.rs create mode 100644 examples/stm32wle5/src/bin/blinky.rs create mode 100644 examples/stm32wle5/src/bin/button_exti.rs create mode 100644 examples/stm32wle5/stm32wle5.code-workspace diff --git a/examples/stm32wle5/.cargo/config.toml b/examples/stm32wle5/.cargo/config.toml new file mode 100644 index 000000000..0178d377c --- /dev/null +++ b/examples/stm32wle5/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32WLE5JCIx --connect-under-reset" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/examples/stm32wle5/Cargo.toml b/examples/stm32wle5/Cargo.toml new file mode 100644 index 000000000..f2fc4dd3d --- /dev/null +++ b/examples/stm32wle5/Cargo.toml @@ -0,0 +1,38 @@ +[package] +edition = "2024" +name = "embassy-stm32wl-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +# Change stm32wl55jc-cm4 to your chip name, if necessary. +embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "stm32wle5jc", "time-driver-any", "memory-x", "unstable-pac", "exti", "low-power"] } +embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-1_000"] } +embassy-embedded-hal = { version = "0.5.0", path = "../../embassy-embedded-hal" } + +defmt = "1.0.1" +defmt-rtt = { version = "1.1.0", optional = true } +defmt-serial = { version = "0.10.0", optional = true } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "1.0.0" +embedded-storage = "0.3.1" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +static_cell = { version = "2.1.1", default-features = false } + +[profile.release] +debug = 2 + +[package.metadata.embassy] +build = [ + { target = "thumbv7em-none-eabi", artifact-dir = "out/examples/stm32wl" } +] + +[features] +default = ["defmt-serial"] +defmt-rtt = ["dep:defmt-rtt"] +defmt-serial = ["dep:defmt-serial"] diff --git a/examples/stm32wle5/README.md b/examples/stm32wle5/README.md new file mode 100644 index 000000000..7435ed1be --- /dev/null +++ b/examples/stm32wle5/README.md @@ -0,0 +1,45 @@ +# Low Power Examples for STM32WLEx family + +Examples in this repo should work with [LoRa-E5 Dev Board](https://www.st.com/en/partner-products-and-services/lora-e5-development-kit.html) board. + +## Prerequsits + +- Connect a usb serial adapter to LPUart1 (this is where ALL logging will go) +- Optional: Connect an amp meter that ran measure down to 0.1uA to the power test pins + +## Example Notes + +All examples will set all pins to analog mode before configuring pins for the example, if any. This saves about 500uA!!!! + +- the `adc` example will sleep in STOP1 betwen samples and the chip will only draw about 13uA while sleeping +- the `blinky` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping +- the `button_exti` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping + +Run individual examples with +``` +cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin +``` +for example +``` +cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin blinky +``` + +You can also run them with with `run`. However in this case expect probe-rs to be disconnected as soon as flashing is done as all IO pins are set to analog input! +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L432KCU6 it should be `probe-rs run --chip STM32L432KCUx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L432KCU6 it should be `stm32l432kc`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32wle5/build.rs b/examples/stm32wle5/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32wle5/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32wle5/src/bin/adc.rs b/examples/stm32wle5/src/bin/adc.rs new file mode 100644 index 000000000..fabdb9cb3 --- /dev/null +++ b/examples/stm32wle5/src/bin/adc.rs @@ -0,0 +1,105 @@ +#![no_std] +#![no_main] + +use defmt::*; +#[cfg(feature = "defmt-rtt")] +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::low_power::Executor; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_time::Timer; +use panic_probe as _; +use static_cell::StaticCell; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("main: Starting!"); + Executor::take().run(|spawner| { + spawner.spawn(unwrap!(async_main(spawner))); + }); +} + +#[embassy_executor::task] +async fn async_main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + // enable HSI clock + config.rcc.hsi = true; + // enable LSI clock for RTC + config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); + config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); + config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; + // enable ADC with HSI clock + config.rcc.mux.adcsel = embassy_stm32::pac::rcc::vals::Adcsel::HSI; + #[cfg(feature = "defmt-serial")] + { + // disable debug during sleep to reduce power consumption since we are + // using defmt-serial on LPUART1. + config.enable_debug_during_sleep = false; + // if we are using defmt-serial on LPUART1, we need to use HSI for the clock + // so that its registers are preserved during STOP modes. + config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; + } + // Initialize STM32WL peripherals (use default config like wio-e5-async example) + let p = embassy_stm32::init(config); + + // start with all GPIOs as analog to reduce power consumption + for r in [ + embassy_stm32::pac::GPIOA, + embassy_stm32::pac::GPIOB, + embassy_stm32::pac::GPIOC, + embassy_stm32::pac::GPIOH, + ] { + r.moder().modify(|w| { + for i in 0..16 { + // don't reset these if probe-rs should stay connected! + #[cfg(feature = "defmt-rtt")] + if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { + continue; + } + w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); + } + }); + } + #[cfg(feature = "defmt-serial")] + { + use embassy_stm32::mode::Blocking; + use embassy_stm32::usart::Uart; + let mut config = embassy_stm32::usart::Config::default(); + config.baudrate = 115200; + config.assume_noise_free = true; + config.detect_previous_overrun = true; + let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); + static SERIAL: StaticCell> = StaticCell::new(); + defmt_serial::defmt_serial(SERIAL.init(uart)); + } + + // give the RTC to the low_power executor... + let rtc_config = RtcConfig::default(); + let rtc = Rtc::new(p.RTC, rtc_config); + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(rtc); + embassy_stm32::low_power::stop_with_rtc(rtc); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES79_5); + let mut pin = p.PA10; + + let mut vrefint = adc.enable_vrefint(); + let vrefint_sample = adc.blocking_read(&mut vrefint); + let convert_to_millivolts = |sample| { + // From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf + // 6.3.3 Embedded internal reference voltage + const VREFINT_MV: u32 = 1212; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.blocking_read(&mut pin); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_secs(1).await; + } +} diff --git a/examples/stm32wle5/src/bin/blinky.rs b/examples/stm32wle5/src/bin/blinky.rs new file mode 100644 index 000000000..f5ba97025 --- /dev/null +++ b/examples/stm32wle5/src/bin/blinky.rs @@ -0,0 +1,95 @@ +#![no_std] +#![no_main] + +use defmt::*; +#[cfg(feature = "defmt-rtt")] +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::low_power::Executor; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_time::Timer; +use panic_probe as _; +use static_cell::StaticCell; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("main: Starting!"); + Executor::take().run(|spawner| { + spawner.spawn(unwrap!(async_main(spawner))); + }); +} + +#[embassy_executor::task] +async fn async_main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + // enable HSI clock + config.rcc.hsi = true; + // enable LSI clock for RTC + config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); + config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); + config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; + #[cfg(feature = "defmt-serial")] + { + // disable debug during sleep to reduce power consumption since we are + // using defmt-serial on LPUART1. + config.enable_debug_during_sleep = false; + // if we are using defmt-serial on LPUART1, we need to use HSI for the clock + // so that its registers are preserved during STOP modes. + config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; + } + // Initialize STM32WL peripherals (use default config like wio-e5-async example) + let p = embassy_stm32::init(config); + + // start with all GPIOs as analog to reduce power consumption + for r in [ + embassy_stm32::pac::GPIOA, + embassy_stm32::pac::GPIOB, + embassy_stm32::pac::GPIOC, + embassy_stm32::pac::GPIOH, + ] { + r.moder().modify(|w| { + for i in 0..16 { + // don't reset these if probe-rs should stay connected! + #[cfg(feature = "defmt-rtt")] + if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { + continue; + } + w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); + } + }); + } + #[cfg(feature = "defmt-serial")] + { + use embassy_stm32::mode::Blocking; + use embassy_stm32::usart::Uart; + let mut config = embassy_stm32::usart::Config::default(); + config.baudrate = 115200; + config.assume_noise_free = true; + config.detect_previous_overrun = true; + let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); + static SERIAL: StaticCell> = StaticCell::new(); + defmt_serial::defmt_serial(SERIAL.init(uart)); + } + + // give the RTC to the low_power executor... + let rtc_config = RtcConfig::default(); + let rtc = Rtc::new(p.RTC, rtc_config); + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(rtc); + embassy_stm32::low_power::stop_with_rtc(rtc); + + info!("Hello World!"); + + let mut led = Output::new(p.PB5, Level::High, Speed::Low); + + loop { + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32wle5/src/bin/button_exti.rs b/examples/stm32wle5/src/bin/button_exti.rs new file mode 100644 index 000000000..dfa391a81 --- /dev/null +++ b/examples/stm32wle5/src/bin/button_exti.rs @@ -0,0 +1,96 @@ +#![no_std] +#![no_main] + +use defmt::*; +#[cfg(feature = "defmt-rtt")] +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::Pull; +use embassy_stm32::low_power::Executor; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use panic_probe as _; +use static_cell::StaticCell; + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("main: Starting!"); + Executor::take().run(|spawner| { + spawner.spawn(unwrap!(async_main(spawner))); + }); +} + +#[embassy_executor::task] +async fn async_main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + // enable HSI clock + config.rcc.hsi = true; + // enable LSI clock for RTC + config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); + config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); + config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; + // enable ADC with HSI clock + config.rcc.mux.adcsel = embassy_stm32::pac::rcc::vals::Adcsel::HSI; + #[cfg(feature = "defmt-serial")] + { + // disable debug during sleep to reduce power consumption since we are + // using defmt-serial on LPUART1. + config.enable_debug_during_sleep = false; + // if we are using defmt-serial on LPUART1, we need to use HSI for the clock + // so that its registers are preserved during STOP modes. + config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; + } + // Initialize STM32WL peripherals (use default config like wio-e5-async example) + let p = embassy_stm32::init(config); + + // start with all GPIOs as analog to reduce power consumption + for r in [ + embassy_stm32::pac::GPIOA, + embassy_stm32::pac::GPIOB, + embassy_stm32::pac::GPIOC, + embassy_stm32::pac::GPIOH, + ] { + r.moder().modify(|w| { + for i in 0..16 { + // don't reset these if probe-rs should stay connected! + #[cfg(feature = "defmt-rtt")] + if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { + continue; + } + w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); + } + }); + } + #[cfg(feature = "defmt-serial")] + { + use embassy_stm32::mode::Blocking; + use embassy_stm32::usart::Uart; + let mut config = embassy_stm32::usart::Config::default(); + config.baudrate = 115200; + config.assume_noise_free = true; + config.detect_previous_overrun = true; + let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); + static SERIAL: StaticCell> = StaticCell::new(); + defmt_serial::defmt_serial(SERIAL.init(uart)); + } + + // give the RTC to the low_power executor... + let rtc_config = RtcConfig::default(); + let rtc = Rtc::new(p.RTC, rtc_config); + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(rtc); + embassy_stm32::low_power::stop_with_rtc(rtc); + + info!("Hello World!"); + + let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up); + + info!("Press the USER button..."); + + loop { + button.wait_for_falling_edge().await; + info!("Pressed!"); + button.wait_for_rising_edge().await; + info!("Released!"); + } +} diff --git a/examples/stm32wle5/stm32wle5.code-workspace b/examples/stm32wle5/stm32wle5.code-workspace new file mode 100644 index 000000000..a7c4a0ebd --- /dev/null +++ b/examples/stm32wle5/stm32wle5.code-workspace @@ -0,0 +1,13 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "rust-analyzer.cargo.target": "thumbv7em-none-eabi", + "rust-analyzer.cargo.allTargets": false, + "rust-analyzer.cargo.targetDir": "target/rust-analyzer", + "rust-analyzer.checkOnSave": true, + } +} -- cgit From 5b70da2256747853ac4f866e60493241ac34bcd3 Mon Sep 17 00:00:00 2001 From: liebman Date: Wed, 29 Oct 2025 15:08:01 -0700 Subject: examples: : stm32wlex: mention `defmt-print` --- examples/stm32wle5/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/stm32wle5/README.md b/examples/stm32wle5/README.md index 7435ed1be..63507f490 100644 --- a/examples/stm32wle5/README.md +++ b/examples/stm32wle5/README.md @@ -6,6 +6,7 @@ Examples in this repo should work with [LoRa-E5 Dev Board](https://www.st.com/en - Connect a usb serial adapter to LPUart1 (this is where ALL logging will go) - Optional: Connect an amp meter that ran measure down to 0.1uA to the power test pins +- `cargo install defmt-print` so you can print log messahes from LPUart1 ## Example Notes @@ -15,6 +16,11 @@ All examples will set all pins to analog mode before configuring pins for the ex - the `blinky` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping - the `button_exti` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping +For each example you will need to start `defmt-print` with the example binary and the correct serial port in a seperate terminal. Example: +``` +defmt-print -w -v -e target/thumbv7em-none-eabi/debug/ serial --path /dev/cu.usbserial-00000000 --baud 115200 +``` + Run individual examples with ``` cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin -- cgit From 9131cbd9f2b28ff10be64162a3d55d288f27190a Mon Sep 17 00:00:00 2001 From: liebman Date: Thu, 30 Oct 2025 13:07:07 -0700 Subject: examples: : stm32wlex: add i2c example --- examples/stm32wle5/README.md | 1 + examples/stm32wle5/src/bin/adc.rs | 5 +- examples/stm32wle5/src/bin/blinky.rs | 5 +- examples/stm32wle5/src/bin/button_exti.rs | 5 +- examples/stm32wle5/src/bin/i2c.rs | 112 ++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 examples/stm32wle5/src/bin/i2c.rs diff --git a/examples/stm32wle5/README.md b/examples/stm32wle5/README.md index 63507f490..18c3b5071 100644 --- a/examples/stm32wle5/README.md +++ b/examples/stm32wle5/README.md @@ -15,6 +15,7 @@ All examples will set all pins to analog mode before configuring pins for the ex - the `adc` example will sleep in STOP1 betwen samples and the chip will only draw about 13uA while sleeping - the `blinky` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping - the `button_exti` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping +- the `i2c` examples will sleep in STOP1 between reads and the chip only draw about 10uA while sleeping For each example you will need to start `defmt-print` with the example binary and the correct serial port in a seperate terminal. Example: ``` diff --git a/examples/stm32wle5/src/bin/adc.rs b/examples/stm32wle5/src/bin/adc.rs index fabdb9cb3..b4af656ed 100644 --- a/examples/stm32wle5/src/bin/adc.rs +++ b/examples/stm32wle5/src/bin/adc.rs @@ -65,10 +65,7 @@ async fn async_main(_spawner: Spawner) { { use embassy_stm32::mode::Blocking; use embassy_stm32::usart::Uart; - let mut config = embassy_stm32::usart::Config::default(); - config.baudrate = 115200; - config.assume_noise_free = true; - config.detect_previous_overrun = true; + let config = embassy_stm32::usart::Config::default(); let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); static SERIAL: StaticCell> = StaticCell::new(); defmt_serial::defmt_serial(SERIAL.init(uart)); diff --git a/examples/stm32wle5/src/bin/blinky.rs b/examples/stm32wle5/src/bin/blinky.rs index f5ba97025..a7a571cb1 100644 --- a/examples/stm32wle5/src/bin/blinky.rs +++ b/examples/stm32wle5/src/bin/blinky.rs @@ -63,10 +63,7 @@ async fn async_main(_spawner: Spawner) { { use embassy_stm32::mode::Blocking; use embassy_stm32::usart::Uart; - let mut config = embassy_stm32::usart::Config::default(); - config.baudrate = 115200; - config.assume_noise_free = true; - config.detect_previous_overrun = true; + let config = embassy_stm32::usart::Config::default(); let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); static SERIAL: StaticCell> = StaticCell::new(); defmt_serial::defmt_serial(SERIAL.init(uart)); diff --git a/examples/stm32wle5/src/bin/button_exti.rs b/examples/stm32wle5/src/bin/button_exti.rs index dfa391a81..c8083a240 100644 --- a/examples/stm32wle5/src/bin/button_exti.rs +++ b/examples/stm32wle5/src/bin/button_exti.rs @@ -65,10 +65,7 @@ async fn async_main(_spawner: Spawner) { { use embassy_stm32::mode::Blocking; use embassy_stm32::usart::Uart; - let mut config = embassy_stm32::usart::Config::default(); - config.baudrate = 115200; - config.assume_noise_free = true; - config.detect_previous_overrun = true; + let config = embassy_stm32::usart::Config::default(); let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); static SERIAL: StaticCell> = StaticCell::new(); defmt_serial::defmt_serial(SERIAL.init(uart)); diff --git a/examples/stm32wle5/src/bin/i2c.rs b/examples/stm32wle5/src/bin/i2c.rs new file mode 100644 index 000000000..4a56773e9 --- /dev/null +++ b/examples/stm32wle5/src/bin/i2c.rs @@ -0,0 +1,112 @@ +#![no_std] +#![no_main] + +use defmt::*; +#[cfg(feature = "defmt-rtt")] +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::i2c::I2c; +use embassy_stm32::low_power::Executor; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::{Duration, Timer}; +use panic_probe as _; +use static_cell::StaticCell; + +bind_interrupts!(struct IrqsI2C{ + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; +}); + +#[cortex_m_rt::entry] +fn main() -> ! { + info!("main: Starting!"); + Executor::take().run(|spawner| { + spawner.spawn(unwrap!(async_main(spawner))); + }); +} + +#[embassy_executor::task] +async fn async_main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + // enable HSI clock + config.rcc.hsi = true; + // enable LSI clock for RTC + config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi(); + config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M); + config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI; + // enable ADC with HSI clock + config.rcc.mux.i2c2sel = embassy_stm32::pac::rcc::vals::I2c2sel::HSI; + #[cfg(feature = "defmt-serial")] + { + // disable debug during sleep to reduce power consumption since we are + // using defmt-serial on LPUART1. + config.enable_debug_during_sleep = false; + // if we are using defmt-serial on LPUART1, we need to use HSI for the clock + // so that its registers are preserved during STOP modes. + config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI; + } + // Initialize STM32WL peripherals (use default config like wio-e5-async example) + let p = embassy_stm32::init(config); + + // start with all GPIOs as analog to reduce power consumption + for r in [ + embassy_stm32::pac::GPIOA, + embassy_stm32::pac::GPIOB, + embassy_stm32::pac::GPIOC, + embassy_stm32::pac::GPIOH, + ] { + r.moder().modify(|w| { + for i in 0..16 { + // don't reset these if probe-rs should stay connected! + #[cfg(feature = "defmt-rtt")] + if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) { + continue; + } + w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG); + } + }); + } + #[cfg(feature = "defmt-serial")] + { + use embassy_stm32::mode::Blocking; + use embassy_stm32::usart::Uart; + let config = embassy_stm32::usart::Config::default(); + let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!"); + static SERIAL: StaticCell> = StaticCell::new(); + defmt_serial::defmt_serial(SERIAL.init(uart)); + } + + // give the RTC to the low_power executor... + let rtc_config = RtcConfig::default(); + let rtc = Rtc::new(p.RTC, rtc_config); + static RTC: StaticCell = StaticCell::new(); + let rtc = RTC.init(rtc); + embassy_stm32::low_power::stop_with_rtc(rtc); + + info!("Hello World!"); + let en3v3 = embassy_stm32::gpio::Output::new( + p.PA9, + embassy_stm32::gpio::Level::High, + embassy_stm32::gpio::Speed::High, + ); + core::mem::forget(en3v3); // keep the output pin enabled + + let mut i2c = I2c::new(p.I2C2, p.PB15, p.PA15, IrqsI2C, p.DMA1_CH6, p.DMA1_CH7, { + let mut config = i2c::Config::default(); + config.frequency = Hertz::khz(100); + config.timeout = Duration::from_millis(500); + config + }); + + loop { + let mut buffer = [0; 2]; + // read the temperature register of the onboard lm75 + match i2c.read(0x48, &mut buffer).await { + Ok(_) => info!("--> {:?}", buffer), + Err(e) => info!("--> Error: {:?}", e), + } + Timer::after_secs(5).await; + } +} -- cgit From 9f9ce2274a96f9d5395c814fc139abe594453625 Mon Sep 17 00:00:00 2001 From: liebman Date: Mon, 3 Nov 2025 12:59:06 -0800 Subject: stm32wle5 update low-power examples --- examples/stm32wle5/src/bin/adc.rs | 2 -- examples/stm32wle5/src/bin/blinky.rs | 2 -- examples/stm32wle5/src/bin/button_exti.rs | 2 -- examples/stm32wle5/src/bin/i2c.rs | 2 -- 4 files changed, 8 deletions(-) diff --git a/examples/stm32wle5/src/bin/adc.rs b/examples/stm32wle5/src/bin/adc.rs index b4af656ed..ff1a5fa16 100644 --- a/examples/stm32wle5/src/bin/adc.rs +++ b/examples/stm32wle5/src/bin/adc.rs @@ -74,8 +74,6 @@ async fn async_main(_spawner: Spawner) { // give the RTC to the low_power executor... let rtc_config = RtcConfig::default(); let rtc = Rtc::new(p.RTC, rtc_config); - static RTC: StaticCell = StaticCell::new(); - let rtc = RTC.init(rtc); embassy_stm32::low_power::stop_with_rtc(rtc); info!("Hello World!"); diff --git a/examples/stm32wle5/src/bin/blinky.rs b/examples/stm32wle5/src/bin/blinky.rs index a7a571cb1..1191a1157 100644 --- a/examples/stm32wle5/src/bin/blinky.rs +++ b/examples/stm32wle5/src/bin/blinky.rs @@ -72,8 +72,6 @@ async fn async_main(_spawner: Spawner) { // give the RTC to the low_power executor... let rtc_config = RtcConfig::default(); let rtc = Rtc::new(p.RTC, rtc_config); - static RTC: StaticCell = StaticCell::new(); - let rtc = RTC.init(rtc); embassy_stm32::low_power::stop_with_rtc(rtc); info!("Hello World!"); diff --git a/examples/stm32wle5/src/bin/button_exti.rs b/examples/stm32wle5/src/bin/button_exti.rs index c8083a240..f07f9724d 100644 --- a/examples/stm32wle5/src/bin/button_exti.rs +++ b/examples/stm32wle5/src/bin/button_exti.rs @@ -74,8 +74,6 @@ async fn async_main(_spawner: Spawner) { // give the RTC to the low_power executor... let rtc_config = RtcConfig::default(); let rtc = Rtc::new(p.RTC, rtc_config); - static RTC: StaticCell = StaticCell::new(); - let rtc = RTC.init(rtc); embassy_stm32::low_power::stop_with_rtc(rtc); info!("Hello World!"); diff --git a/examples/stm32wle5/src/bin/i2c.rs b/examples/stm32wle5/src/bin/i2c.rs index 4a56773e9..af07f911e 100644 --- a/examples/stm32wle5/src/bin/i2c.rs +++ b/examples/stm32wle5/src/bin/i2c.rs @@ -81,8 +81,6 @@ async fn async_main(_spawner: Spawner) { // give the RTC to the low_power executor... let rtc_config = RtcConfig::default(); let rtc = Rtc::new(p.RTC, rtc_config); - static RTC: StaticCell = StaticCell::new(); - let rtc = RTC.init(rtc); embassy_stm32::low_power::stop_with_rtc(rtc); info!("Hello World!"); -- cgit