From 9008e4ca79a8cea093484f09d02c9ca67e7e2b1f Mon Sep 17 00:00:00 2001 From: liebman Date: Thu, 18 Dec 2025 15:04:45 -0800 Subject: low-power: stm32wl5x --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/low_power.rs | 47 ++++++++++++++++++------- embassy-stm32/src/rcc/mod.rs | 4 +-- embassy-stm32/src/rtc/low_power.rs | 4 +-- embassy-stm32/src/rtc/v3.rs | 4 +-- examples/stm32wl5x-lp/.cargo/config.toml | 9 +++++ examples/stm32wl5x-lp/Cargo.toml | 39 +++++++++++++++++++++ examples/stm32wl5x-lp/build.rs | 5 +++ examples/stm32wl5x-lp/memory.x | 15 ++++++++ examples/stm32wl5x-lp/src/bin/blinky.rs | 59 ++++++++++++++++++++++++++++++++ 10 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 examples/stm32wl5x-lp/.cargo/config.toml create mode 100644 examples/stm32wl5x-lp/Cargo.toml create mode 100644 examples/stm32wl5x-lp/build.rs create mode 100644 examples/stm32wl5x-lp/memory.x create mode 100644 examples/stm32wl5x-lp/src/bin/blinky.rs diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 9e0d69078..ae2c0168b 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -73,6 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - low-power: update rtc api to allow reconfig - adc: consolidate ringbuffer - feat: Added RTC low-power support for STM32WLEx ([#4716](https://github.com/embassy-rs/embassy/pull/4716)) +- feat: Added low-power support for STM32WL5x ([#5108](https://github.com/embassy-rs/embassy/pull/5108)) - fix: Correct STM32WBA VREFBUFTRIM values - low_power: remove stop_with rtc and initialize in init if low-power feature enabled. - feat: stm32/dsi support zero parameter commands in `write_cmd` ([#4847](https://github.com/embassy-rs/embassy/pull/4847)) diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 02116e08a..71befcf34 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -136,10 +136,10 @@ pub fn stop_ready(stop_mode: StopMode) -> bool { }) } -#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wb, stm32wlex, stm32u0))] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wb, stm32wl, stm32u0))] use crate::pac::pwr::vals::Lpms; -#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wb, stm32wlex, stm32u0))] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wb, stm32wl, stm32u0))] impl Into for StopMode { fn into(self) -> Lpms { match self { @@ -184,19 +184,28 @@ impl Executor { pub(crate) unsafe fn on_wakeup_irq_or_event() { if !get_driver().is_stopped() { + trace!("low power: time driver not stopped!"); return; } critical_section::with(|cs| { - #[cfg(any(stm32wlex, stm32wb))] + #[cfg(any(stm32wl, stm32wb))] { let es = crate::pac::PWR.extscr().read(); - #[cfg(stm32wlex)] + #[cfg(stm32wl)] match (es.c1stopf(), es.c1stop2f()) { - (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"), + (true, false) => debug!("low power: cpu1 wake from STOP1"), + (false, true) => debug!("low power: cpu1 wake from STOP2"), + (true, true) => debug!("low power: cpu1 wake from STOP1 and STOP2 ???"), + (false, false) => trace!("low power: cpu1 stop mode not entered"), + }; + #[cfg(stm32wl5x)] + // TODO: only for the current cpu + match (es.c2stopf(), es.c2stop2f()) { + (true, false) => debug!("low power: cpu2 wake from STOP1"), + (false, true) => debug!("low power: cpu2 wake from STOP2"), + (true, true) => debug!("low power: cpu2 wake from STOP1 and STOP2 ???"), + (false, false) => trace!("low power: cpu2 stop mode not entered"), }; #[cfg(stm32wb)] @@ -206,9 +215,6 @@ impl Executor { (true, true) => debug!("low power: cpu1 and cpu2 wake from STOP"), (false, false) => trace!("low power: stop mode not entered"), }; - crate::pac::PWR.extscr().modify(|w| { - w.set_c1cssf(false); - }); let _has_stopped2 = { #[cfg(stm32wb)] @@ -220,6 +226,12 @@ impl Executor { { es.c1stop2f() } + + #[cfg(stm32wl5x)] + { + // TODO: I think we could just use c1stop2f() here as it won't enter a stop mode unless BOTH cpus will enter it. + es.c1stop2f() | es.c2stop2f() + } }; #[cfg(not(stm32wb))] @@ -235,6 +247,13 @@ impl Executor { REFCOUNT_STOP1 = 0; } } + // Clear all stop flags + #[cfg(stm32wl)] + crate::pac::PWR.extscr().modify(|w| { + w.set_c1cssf(true); + #[cfg(stm32wl5x)] + w.set_c2cssf(true); + }); } get_driver().resume_time(cs); @@ -337,7 +356,7 @@ impl Executor { #[cfg(all(stm32wb, feature = "low-power"))] self.configure_stop_stm32wb(_cs)?; - #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wb, stm32wba, stm32wlex))] + #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wb, stm32wba, stm32wl))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); #[cfg(stm32h5)] crate::pac::PWR.pmcr().modify(|v| { @@ -352,9 +371,11 @@ impl Executor { fn configure_pwr(&self) { Self::get_scb().clear_sleepdeep(); // Clear any previous stop flags - #[cfg(stm32wlex)] + #[cfg(stm32wl)] crate::pac::PWR.extscr().modify(|w| { w.set_c1cssf(true); + #[cfg(stm32wl5x)] + w.set_c2cssf(true); }); #[cfg(feature = "low-power-pender")] diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 753ee1290..c0a50615e 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -313,7 +313,7 @@ impl RccInfo { #[allow(dead_code)] fn increment_minimum_stop_refcount_with_cs(&self, _cs: CriticalSection) { - #[cfg(all(any(stm32wlex, stm32wb), feature = "low-power"))] + #[cfg(all(any(stm32wl, stm32wb), feature = "low-power"))] match self.stop_mode { StopMode::Stop1 | StopMode::Stop2 => increment_stop_refcount(_cs, StopMode::Stop2), _ => {} @@ -334,7 +334,7 @@ impl RccInfo { #[allow(dead_code)] fn decrement_minimum_stop_refcount_with_cs(&self, _cs: CriticalSection) { - #[cfg(all(any(stm32wlex, stm32wb), feature = "low-power"))] + #[cfg(all(any(stm32wl, stm32wb), feature = "low-power"))] match self.stop_mode { StopMode::Stop1 | StopMode::Stop2 => decrement_stop_refcount(_cs, StopMode::Stop2), _ => {} diff --git a/embassy-stm32/src/rtc/low_power.rs b/embassy-stm32/src/rtc/low_power.rs index cd5cea081..e15eddc22 100644 --- a/embassy-stm32/src/rtc/low_power.rs +++ b/embassy-stm32/src/rtc/low_power.rs @@ -114,11 +114,11 @@ impl Rtc { use crate::pac::EXTI; EXTI.rtsr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); - #[cfg(not(stm32wb))] + #[cfg(not(any(stm32wb, stm32wl5x)))] { EXTI.imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); } - #[cfg(stm32wb)] + #[cfg(any(stm32wb, stm32wl5x))] { EXTI.cpu(0).imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); } diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs index f7ebea73e..49c9778df 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(any(stm32g4, stm32wlex))] { + if #[cfg(any(stm32g4, stm32wl))] { 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(any(stm32g4, stm32wlex))] { + if #[cfg(any(stm32g4, stm32wl))] { type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; } else if #[cfg(any(stm32g0, stm32u0))] { type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; diff --git a/examples/stm32wl5x-lp/.cargo/config.toml b/examples/stm32wl5x-lp/.cargo/config.toml new file mode 100644 index 000000000..969068656 --- /dev/null +++ b/examples/stm32wl5x-lp/.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 = "debug" diff --git a/examples/stm32wl5x-lp/Cargo.toml b/examples/stm32wl5x-lp/Cargo.toml new file mode 100644 index 000000000..b1823fb8a --- /dev/null +++ b/examples/stm32wl5x-lp/Cargo.toml @@ -0,0 +1,39 @@ +[package] +edition = "2024" +name = "embassy-stm32wl5x-lp" +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", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono", "low-power-pender"] } +embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["defmt"] } +embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +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 = { git = "https://github.com/gauteh/defmt-serial", rev = "411ae7fa909b4fd2667885aff687e009b9108190", 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"] } +chrono = { version = "^0.4", default-features = false } +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/stm32wl5x-lp/build.rs b/examples/stm32wl5x-lp/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32wl5x-lp/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/stm32wl5x-lp/memory.x b/examples/stm32wl5x-lp/memory.x new file mode 100644 index 000000000..4590867a8 --- /dev/null +++ b/examples/stm32wl5x-lp/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 256K + SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128 + RAM (rwx) : ORIGIN = 0x20000080, LENGTH = 64K - 128 +} + +SECTIONS +{ + .shared_data : + { + *(.shared_data) + } > SHARED_RAM +} diff --git a/examples/stm32wl5x-lp/src/bin/blinky.rs b/examples/stm32wl5x-lp/src/bin/blinky.rs new file mode 100644 index 000000000..068b65248 --- /dev/null +++ b/examples/stm32wl5x-lp/src/bin/blinky.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +#[cfg(feature = "defmt-rtt")] +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::SharedData; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use panic_probe as _; + +#[unsafe(link_section = ".shared_data")] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main(executor = "embassy_stm32::Executor", entry = "cortex_m_rt::entry")] +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; + } + let p = embassy_stm32::init_primary(config, &SHARED_DATA); + + #[cfg(feature = "defmt-serial")] + { + use embassy_stm32::mode::Blocking; + use embassy_stm32::usart::Uart; + use static_cell::StaticCell; + let config = embassy_stm32::usart::Config::default(); + let uart = Uart::new_blocking(p.LPUART1, p.PA3, p.PA2, config).expect("failed to configure UART!"); + static SERIAL: StaticCell> = StaticCell::new(); + defmt_serial::defmt_serial(SERIAL.init(uart)); + } + + info!("Hello World!"); + + let mut led = Output::new(p.PB15, Level::High, Speed::Low); + + loop { + info!("low"); + led.set_low(); + Timer::after_millis(5000).await; + + info!("high"); + led.set_high(); + Timer::after_millis(5000).await; + } +} -- cgit