aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxoviat <[email protected]>2025-11-03 21:26:30 +0000
committerGitHub <[email protected]>2025-11-03 21:26:30 +0000
commit345efc383fa9afabaf23b629f0937855ea4c754f (patch)
tree9dd0ce1991da25ec8686412a93b0c08a52c86f76
parenta967d77a0f0eedcc65778528cceee07edbba2813 (diff)
parent9f9ce2274a96f9d5395c814fc139abe594453625 (diff)
Merge pull request #4716 from liebman/stm32wl-low-power
support low-power executor on stm32wlex
-rw-r--r--embassy-stm32/CHANGELOG.md1
-rw-r--r--embassy-stm32/src/adc/mod.rs20
-rw-r--r--embassy-stm32/src/adc/v3.rs3
-rw-r--r--embassy-stm32/src/i2c/v2.rs15
-rw-r--r--embassy-stm32/src/low_power.rs94
-rw-r--r--embassy-stm32/src/rcc/l.rs43
-rw-r--r--embassy-stm32/src/rcc/mod.rs2
-rw-r--r--embassy-stm32/src/rtc/low_power.rs4
-rw-r--r--embassy-stm32/src/rtc/v3.rs4
-rw-r--r--embassy-stm32/src/time_driver.rs33
-rw-r--r--examples/stm32wle5/.cargo/config.toml9
-rw-r--r--examples/stm32wle5/Cargo.toml38
-rw-r--r--examples/stm32wle5/README.md52
-rw-r--r--examples/stm32wle5/build.rs5
-rw-r--r--examples/stm32wle5/src/bin/adc.rs100
-rw-r--r--examples/stm32wle5/src/bin/blinky.rs90
-rw-r--r--examples/stm32wle5/src/bin/button_exti.rs91
-rw-r--r--examples/stm32wle5/src/bin/i2c.rs110
-rw-r--r--examples/stm32wle5/stm32wle5.code-workspace13
19 files changed, 705 insertions, 22 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
38- fix: usart: fix race condition in ringbuffered usart 38- fix: usart: fix race condition in ringbuffered usart
39- feat: stm32/fdcan: add ability to control automatic recovery from bus off ([#4821](https://github.com/embassy-rs/embassy/pull/4821)) 39- feat: stm32/fdcan: add ability to control automatic recovery from bus off ([#4821](https://github.com/embassy-rs/embassy/pull/4821))
40- low-power: update rtc api to allow reconfig 40- low-power: update rtc api to allow reconfig
41- feat: Added RTC low-power support for STM32WLEx ([#4716](https://github.com/embassy-rs/embassy/pull/4716))
41 42
42## 0.4.0 - 2025-08-26 43## 0.4.0 - 2025-08-26
43 44
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<T> {
87/// Performs a busy-wait delay for a specified number of microseconds. 87/// Performs a busy-wait delay for a specified number of microseconds.
88#[allow(unused)] 88#[allow(unused)]
89pub(crate) fn blocking_delay_us(us: u32) { 89pub(crate) fn blocking_delay_us(us: u32) {
90 #[cfg(feature = "time")] 90 cfg_if::cfg_if! {
91 embassy_time::block_for(embassy_time::Duration::from_micros(us as u64)); 91 // this does strange things on stm32wlx in low power mode depending on exactly when it's called
92 #[cfg(not(feature = "time"))] 92 // as in sometimes 15 us (1 tick) would take > 20 seconds.
93 { 93 if #[cfg(all(feature = "time", all(not(feature = "low-power"), not(stm32wlex))))] {
94 let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; 94 let duration = embassy_time::Duration::from_micros(us as u64);
95 let us = us as u64; 95 embassy_time::block_for(duration);
96 let cycles = freq * us / 1_000_000; 96 } else {
97 cortex_m::asm::delay(cycles as u32); 97 let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64;
98 let us = us as u64;
99 let cycles = freq * us / 1_000_000;
100 cortex_m::asm::delay(cycles as u32);
101 }
98 } 102 }
99} 103}
100 104
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> {
430 "Asynchronous read sequence cannot be more than 16 in length" 430 "Asynchronous read sequence cannot be more than 16 in length"
431 ); 431 );
432 432
433 #[cfg(all(feature = "low-power", stm32wlex))]
434 let _device_busy = crate::low_power::DeviceBusy::new();
435
433 // Ensure no conversions are ongoing and ADC is enabled. 436 // Ensure no conversions are ongoing and ADC is enabled.
434 Self::cancel_conversions(); 437 Self::cancel_conversions();
435 self.enable(); 438 self.enable();
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index 01b6b8800..6f2d03bd1 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) {
70} 70}
71 71
72pub(crate) unsafe fn on_interrupt<T: Instance>() { 72pub(crate) unsafe fn on_interrupt<T: Instance>() {
73 // restore the clocks to their last configured state as
74 // much is lost in STOP modes
75 #[cfg(all(feature = "low-power", stm32wlex))]
76 crate::low_power::on_wakeup_irq();
77
73 let regs = T::info().regs; 78 let regs = T::info().regs;
74 let isr = regs.isr().read(); 79 let isr = regs.isr().read();
75 80
@@ -814,6 +819,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> {
814 819
815 /// Write. 820 /// Write.
816 pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { 821 pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> {
822 #[cfg(all(feature = "low-power", stm32wlex))]
823 let _device_busy = crate::low_power::DeviceBusy::new();
817 let timeout = self.timeout(); 824 let timeout = self.timeout();
818 if write.is_empty() { 825 if write.is_empty() {
819 self.write_internal(address.into(), write, true, timeout) 826 self.write_internal(address.into(), write, true, timeout)
@@ -828,6 +835,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> {
828 /// 835 ///
829 /// The buffers are concatenated in a single write transaction. 836 /// The buffers are concatenated in a single write transaction.
830 pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> { 837 pub async fn write_vectored(&mut self, address: Address, write: &[&[u8]]) -> Result<(), Error> {
838 #[cfg(all(feature = "low-power", stm32wlex))]
839 let _device_busy = crate::low_power::DeviceBusy::new();
831 let timeout = self.timeout(); 840 let timeout = self.timeout();
832 841
833 if write.is_empty() { 842 if write.is_empty() {
@@ -851,6 +860,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> {
851 860
852 /// Read. 861 /// Read.
853 pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { 862 pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
863 #[cfg(all(feature = "low-power", stm32wlex))]
864 let _device_busy = crate::low_power::DeviceBusy::new();
854 let timeout = self.timeout(); 865 let timeout = self.timeout();
855 866
856 if buffer.is_empty() { 867 if buffer.is_empty() {
@@ -863,6 +874,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> {
863 874
864 /// Write, restart, read. 875 /// Write, restart, read.
865 pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { 876 pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
877 #[cfg(all(feature = "low-power", stm32wlex))]
878 let _device_busy = crate::low_power::DeviceBusy::new();
866 let timeout = self.timeout(); 879 let timeout = self.timeout();
867 880
868 if write.is_empty() { 881 if write.is_empty() {
@@ -888,6 +901,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> {
888 /// 901 ///
889 /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction 902 /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction
890 pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> { 903 pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> {
904 #[cfg(all(feature = "low-power", stm32wlex))]
905 let _device_busy = crate::low_power::DeviceBusy::new();
891 let _ = addr; 906 let _ = addr;
892 let _ = operations; 907 let _ = operations;
893 todo!() 908 todo!()
diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs
index 74cd11b93..cde3153f6 100644
--- a/embassy-stm32/src/low_power.rs
+++ b/embassy-stm32/src/low_power.rs
@@ -68,6 +68,39 @@ use crate::rtc::Rtc;
68 68
69static mut EXECUTOR: Option<Executor> = None; 69static mut EXECUTOR: Option<Executor> = None;
70 70
71#[cfg(stm32wlex)]
72pub(crate) use self::busy::DeviceBusy;
73#[cfg(stm32wlex)]
74mod busy {
75 use core::sync::atomic::{AtomicU32, Ordering};
76
77 // Count of devices blocking STOP
78 static STOP_BLOCKED: AtomicU32 = AtomicU32::new(0);
79
80 /// Check if STOP1 is blocked.
81 pub(crate) fn stop_blocked() -> bool {
82 STOP_BLOCKED.load(Ordering::SeqCst) > 0
83 }
84
85 /// When ca device goes busy it will construct one of these where it will be dropped when the device goes idle.
86 pub(crate) struct DeviceBusy {}
87
88 impl DeviceBusy {
89 /// Create a new DeviceBusy.
90 pub(crate) fn new() -> Self {
91 STOP_BLOCKED.fetch_add(1, Ordering::SeqCst);
92 trace!("low power: device busy: Stop:{}", STOP_BLOCKED.load(Ordering::SeqCst));
93 Self {}
94 }
95 }
96
97 impl Drop for DeviceBusy {
98 fn drop(&mut self) {
99 STOP_BLOCKED.fetch_sub(1, Ordering::SeqCst);
100 trace!("low power: device idle: Stop:{}", STOP_BLOCKED.load(Ordering::SeqCst));
101 }
102 }
103}
71#[cfg(not(stm32u0))] 104#[cfg(not(stm32u0))]
72foreach_interrupt! { 105foreach_interrupt! {
73 (RTC, rtc, $block:ident, WKUP, $irq:ident) => { 106 (RTC, rtc, $block:ident, WKUP, $irq:ident) => {
@@ -92,7 +125,10 @@ foreach_interrupt! {
92 125
93#[allow(dead_code)] 126#[allow(dead_code)]
94pub(crate) unsafe fn on_wakeup_irq() { 127pub(crate) unsafe fn on_wakeup_irq() {
95 EXECUTOR.as_mut().unwrap().on_wakeup_irq(); 128 if EXECUTOR.is_some() {
129 trace!("low power: wakeup irq");
130 EXECUTOR.as_mut().unwrap().on_wakeup_irq();
131 }
96} 132}
97 133
98/// Configure STOP mode with RTC. 134/// Configure STOP mode with RTC.
@@ -127,10 +163,10 @@ pub enum StopMode {
127 Stop2, 163 Stop2,
128} 164}
129 165
130#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32u0))] 166#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))]
131use stm32_metapac::pwr::vals::Lpms; 167use stm32_metapac::pwr::vals::Lpms;
132 168
133#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32u0))] 169#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))]
134impl Into<Lpms> for StopMode { 170impl Into<Lpms> for StopMode {
135 fn into(self) -> Lpms { 171 fn into(self) -> Lpms {
136 match self { 172 match self {
@@ -180,6 +216,22 @@ impl Executor {
180 } 216 }
181 217
182 unsafe fn on_wakeup_irq(&mut self) { 218 unsafe fn on_wakeup_irq(&mut self) {
219 #[cfg(stm32wlex)]
220 {
221 let extscr = crate::pac::PWR.extscr().read();
222 if extscr.c1stop2f() || extscr.c1stopf() {
223 // when we wake from any stop mode we need to re-initialize the rcc
224 crate::rcc::apply_resume_config();
225 if extscr.c1stop2f() {
226 // when we wake from STOP2, we need to re-initialize the time driver
227 critical_section::with(|cs| crate::time_driver::init_timer(cs));
228 // reset the refcounts for STOP2 and STOP1 (initializing the time driver will increment one of them for the timer)
229 // and given that we just woke from STOP2, we can reset them
230 crate::rcc::REFCOUNT_STOP2 = 0;
231 crate::rcc::REFCOUNT_STOP1 = 0;
232 }
233 }
234 }
183 self.time_driver.resume_time(); 235 self.time_driver.resume_time();
184 trace!("low power: resume"); 236 trace!("low power: resume");
185 } 237 }
@@ -195,10 +247,22 @@ impl Executor {
195 self.time_driver.reconfigure_rtc(f); 247 self.time_driver.reconfigure_rtc(f);
196 } 248 }
197 249
250 fn stop1_ok_to_enter(&self) -> bool {
251 #[cfg(stm32wlex)]
252 if self::busy::stop_blocked() {
253 return false;
254 }
255 unsafe { crate::rcc::REFCOUNT_STOP1 == 0 }
256 }
257
258 fn stop2_ok_to_enter(&self) -> bool {
259 self.stop1_ok_to_enter() && unsafe { crate::rcc::REFCOUNT_STOP2 == 0 }
260 }
261
198 fn stop_mode(&self) -> Option<StopMode> { 262 fn stop_mode(&self) -> Option<StopMode> {
199 if unsafe { crate::rcc::REFCOUNT_STOP2 == 0 } && unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { 263 if self.stop2_ok_to_enter() {
200 Some(StopMode::Stop2) 264 Some(StopMode::Stop2)
201 } else if unsafe { crate::rcc::REFCOUNT_STOP1 == 0 } { 265 } else if self.stop1_ok_to_enter() {
202 Some(StopMode::Stop1) 266 Some(StopMode::Stop1)
203 } else { 267 } else {
204 None 268 None
@@ -207,7 +271,7 @@ impl Executor {
207 271
208 #[allow(unused_variables)] 272 #[allow(unused_variables)]
209 fn configure_stop(&mut self, stop_mode: StopMode) { 273 fn configure_stop(&mut self, stop_mode: StopMode) {
210 #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba))] 274 #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba, stm32wlex))]
211 crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); 275 crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into()));
212 #[cfg(stm32h5)] 276 #[cfg(stm32h5)]
213 crate::pac::PWR.pmcr().modify(|v| { 277 crate::pac::PWR.pmcr().modify(|v| {
@@ -219,6 +283,11 @@ impl Executor {
219 283
220 fn configure_pwr(&mut self) { 284 fn configure_pwr(&mut self) {
221 self.scb.clear_sleepdeep(); 285 self.scb.clear_sleepdeep();
286 // Clear any previous stop flags
287 #[cfg(stm32wlex)]
288 crate::pac::PWR.extscr().modify(|w| {
289 w.set_c1cssf(true);
290 });
222 291
223 compiler_fence(Ordering::SeqCst); 292 compiler_fence(Ordering::SeqCst);
224 293
@@ -272,6 +341,19 @@ impl Executor {
272 executor.inner.poll(); 341 executor.inner.poll();
273 self.configure_pwr(); 342 self.configure_pwr();
274 asm!("wfe"); 343 asm!("wfe");
344 #[cfg(stm32wlex)]
345 {
346 let es = crate::pac::PWR.extscr().read();
347 match (es.c1stopf(), es.c1stop2f()) {
348 (true, false) => debug!("low power: wake from STOP1"),
349 (false, true) => debug!("low power: wake from STOP2"),
350 (true, true) => debug!("low power: wake from STOP1 and STOP2 ???"),
351 (false, false) => trace!("low power: stop mode not entered"),
352 };
353 crate::pac::PWR.extscr().modify(|w| {
354 w.set_c1cssf(false);
355 });
356 }
275 }; 357 };
276 } 358 }
277 } 359 }
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 @@
1#[cfg(all(feature = "low-power", stm32wlex))]
2use core::mem::MaybeUninit;
3
1#[cfg(any(stm32l0, stm32l1))] 4#[cfg(any(stm32l0, stm32l1))]
2pub use crate::pac::pwr::vals::Vos as VoltageScale; 5pub use crate::pac::pwr::vals::Vos as VoltageScale;
3use crate::pac::rcc::regs::Cfgr; 6use crate::pac::rcc::regs::Cfgr;
@@ -11,6 +14,42 @@ use crate::time::Hertz;
11/// HSI speed 14/// HSI speed
12pub const HSI_FREQ: Hertz = Hertz(16_000_000); 15pub const HSI_FREQ: Hertz = Hertz(16_000_000);
13 16
17/// Saved RCC Config
18///
19/// Used when exiting STOP2 to re-enable clocks to their last configured state
20/// for chips that need it.
21#[cfg(all(feature = "low-power", stm32wlex))]
22static mut RESUME_RCC_CONFIG: MaybeUninit<Config> = MaybeUninit::uninit();
23
24/// Set the rcc config to be restored when exiting STOP2
25///
26/// Safety: Sets a mutable global.
27#[cfg(all(feature = "low-power", stm32wlex))]
28pub(crate) unsafe fn set_resume_config(config: Config) {
29 trace!("rcc set_resume_config()");
30 RESUME_RCC_CONFIG = MaybeUninit::new(config);
31}
32
33/// Get the rcc config to be restored when exiting STOP2
34///
35/// Safety: Reads a mutable global.
36#[cfg(all(feature = "low-power", stm32wlex))]
37pub(crate) unsafe fn get_resume_config() -> Config {
38 *(*core::ptr::addr_of_mut!(RESUME_RCC_CONFIG)).assume_init_ref()
39}
40
41#[cfg(all(feature = "low-power", stm32wlex))]
42/// Safety: should only be called from low power executable just after resuming from STOP2
43pub(crate) unsafe fn apply_resume_config() {
44 trace!("rcc apply_resume_config()");
45
46 while RCC.cfgr().read().sws() != Sysclk::MSI {}
47
48 let config = get_resume_config();
49
50 init(config);
51}
52
14#[derive(Clone, Copy, Eq, PartialEq)] 53#[derive(Clone, Copy, Eq, PartialEq)]
15pub enum HseMode { 54pub enum HseMode {
16 /// crystal/ceramic oscillator (HSEBYP=0) 55 /// crystal/ceramic oscillator (HSEBYP=0)
@@ -154,6 +193,10 @@ fn msi_enable(range: MSIRange) {
154} 193}
155 194
156pub(crate) unsafe fn init(config: Config) { 195pub(crate) unsafe fn init(config: Config) {
196 // save the rcc config because if we enter stop 2 we need to re-apply it on wakeup
197 #[cfg(all(feature = "low-power", stm32wlex))]
198 set_resume_config(config);
199
157 // Switch to MSI to prevent problems with PLL configuration. 200 // Switch to MSI to prevent problems with PLL configuration.
158 if !RCC.cr().read().msion() { 201 if !RCC.cr().read().msion() {
159 // Turn on MSI and configure it to 4MHz. 202 // Turn on MSI and configure it to 4MHz.
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<T: RccPeripheral>() {
390/// 390///
391/// This should only be called after `init`. 391/// This should only be called after `init`.
392#[cfg(not(feature = "_dual-core"))] 392#[cfg(not(feature = "_dual-core"))]
393pub fn reinit<'a>(config: Config, _rcc: &'a mut crate::Peri<'a, crate::peripherals::RCC>) { 393pub fn reinit(config: Config, _rcc: &'_ mut crate::Peri<'_, crate::peripherals::RCC>) {
394 critical_section::with(|cs| init_rcc(cs, config)) 394 critical_section::with(|cs| init_rcc(cs, config))
395} 395}
396 396
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 {
68} 68}
69 69
70#[cfg(any( 70#[cfg(any(
71 stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba 71 stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba, stm32wlex
72))] 72))]
73impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel { 73impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel {
74 fn from(val: WakeupPrescaler) -> Self { 74 fn from(val: WakeupPrescaler) -> Self {
@@ -84,7 +84,7 @@ impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel {
84} 84}
85 85
86#[cfg(any( 86#[cfg(any(
87 stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba 87 stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0, stm32wba, stm32wlex
88))] 88))]
89impl From<crate::pac::rtc::vals::Wucksel> for WakeupPrescaler { 89impl From<crate::pac::rtc::vals::Wucksel> for WakeupPrescaler {
90 fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { 90 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 {
131 131
132 #[cfg(feature = "low-power")] 132 #[cfg(feature = "low-power")]
133 cfg_if::cfg_if!( 133 cfg_if::cfg_if!(
134 if #[cfg(stm32g4)] { 134 if #[cfg(any(stm32g4, stm32wlex))] {
135 const EXTI_WAKEUP_LINE: usize = 20; 135 const EXTI_WAKEUP_LINE: usize = 20;
136 } else if #[cfg(stm32g0)] { 136 } else if #[cfg(stm32g0)] {
137 const EXTI_WAKEUP_LINE: usize = 19; 137 const EXTI_WAKEUP_LINE: usize = 19;
@@ -142,7 +142,7 @@ impl SealedInstance for crate::peripherals::RTC {
142 142
143 #[cfg(feature = "low-power")] 143 #[cfg(feature = "low-power")]
144 cfg_if::cfg_if!( 144 cfg_if::cfg_if!(
145 if #[cfg(stm32g4)] { 145 if #[cfg(any(stm32g4, stm32wlex))] {
146 type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; 146 type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP;
147 } else if #[cfg(any(stm32g0, stm32u0))] { 147 } else if #[cfg(any(stm32g0, stm32u0))] {
148 type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; 148 type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP;
diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs
index 7fd5751b3..4956d1f68 100644
--- a/embassy-stm32/src/time_driver.rs
+++ b/embassy-stm32/src/time_driver.rs
@@ -1,6 +1,8 @@
1#![allow(non_snake_case)] 1#![allow(non_snake_case)]
2 2
3use core::cell::{Cell, RefCell}; 3use core::cell::{Cell, RefCell};
4#[cfg(all(feature = "low-power", stm32wlex))]
5use core::sync::atomic::AtomicU16;
4use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; 6use core::sync::atomic::{AtomicU32, Ordering, compiler_fence};
5 7
6use critical_section::CriticalSection; 8use critical_section::CriticalSection;
@@ -214,6 +216,9 @@ pub(crate) struct RtcDriver {
214 alarm: Mutex<CriticalSectionRawMutex, AlarmState>, 216 alarm: Mutex<CriticalSectionRawMutex, AlarmState>,
215 #[cfg(feature = "low-power")] 217 #[cfg(feature = "low-power")]
216 rtc: Mutex<CriticalSectionRawMutex, RefCell<Option<Rtc>>>, 218 rtc: Mutex<CriticalSectionRawMutex, RefCell<Option<Rtc>>>,
219 /// Saved count for the timer (its value is lost when entering STOP2)
220 #[cfg(all(feature = "low-power", stm32wlex))]
221 saved_count: AtomicU16,
217 queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>, 222 queue: Mutex<CriticalSectionRawMutex, RefCell<Queue>>,
218} 223}
219 224
@@ -222,11 +227,15 @@ embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver {
222 alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), 227 alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()),
223 #[cfg(feature = "low-power")] 228 #[cfg(feature = "low-power")]
224 rtc: Mutex::const_new(CriticalSectionRawMutex::new(), RefCell::new(None)), 229 rtc: Mutex::const_new(CriticalSectionRawMutex::new(), RefCell::new(None)),
230 #[cfg(all(feature = "low-power", stm32wlex))]
231 saved_count: AtomicU16::new(0),
225 queue: Mutex::new(RefCell::new(Queue::new())) 232 queue: Mutex::new(RefCell::new(Queue::new()))
226}); 233});
227 234
228impl RtcDriver { 235impl RtcDriver {
229 fn init(&'static self, cs: critical_section::CriticalSection) { 236 /// initialize the timer, but don't start it. Used for chips like stm32wle5
237 /// for low power where the timer config is lost in STOP2.
238 fn init_timer(&'static self, cs: critical_section::CriticalSection) {
230 let r = regs_gp16(); 239 let r = regs_gp16();
231 240
232 rcc::enable_and_reset_with_cs::<T>(cs); 241 rcc::enable_and_reset_with_cs::<T>(cs);
@@ -259,10 +268,16 @@ impl RtcDriver {
259 w.set_ccie(0, true); 268 w.set_ccie(0, true);
260 }); 269 });
261 270
271 #[cfg(all(feature = "low-power", stm32wlex))]
272 r.cnt().write(|w| w.set_cnt(self.saved_count.load(Ordering::SeqCst)));
273
262 <T as GeneralInstance1Channel>::CaptureCompareInterrupt::unpend(); 274 <T as GeneralInstance1Channel>::CaptureCompareInterrupt::unpend();
263 unsafe { <T as GeneralInstance1Channel>::CaptureCompareInterrupt::enable() }; 275 unsafe { <T as GeneralInstance1Channel>::CaptureCompareInterrupt::enable() };
276 }
264 277
265 r.cr1().modify(|w| w.set_cen(true)); 278 fn init(&'static self, cs: CriticalSection) {
279 self.init_timer(cs);
280 regs_gp16().cr1().modify(|w| w.set_cen(true));
266 } 281 }
267 282
268 fn on_interrupt(&self) { 283 fn on_interrupt(&self) {
@@ -420,6 +435,10 @@ impl RtcDriver {
420 435
421 let time_until_next_alarm = self.time_until_next_alarm(cs); 436 let time_until_next_alarm = self.time_until_next_alarm(cs);
422 if time_until_next_alarm < Self::MIN_STOP_PAUSE { 437 if time_until_next_alarm < Self::MIN_STOP_PAUSE {
438 trace!(
439 "time_until_next_alarm < Self::MIN_STOP_PAUSE ({})",
440 time_until_next_alarm
441 );
423 Err(()) 442 Err(())
424 } else { 443 } else {
425 self.rtc 444 self.rtc
@@ -430,7 +449,10 @@ impl RtcDriver {
430 .start_wakeup_alarm(time_until_next_alarm, cs); 449 .start_wakeup_alarm(time_until_next_alarm, cs);
431 450
432 regs_gp16().cr1().modify(|w| w.set_cen(false)); 451 regs_gp16().cr1().modify(|w| w.set_cen(false));
433 452 // save the count for the timer as its lost in STOP2 for stm32wlex
453 #[cfg(stm32wlex)]
454 self.saved_count
455 .store(regs_gp16().cnt().read().cnt() as u16, Ordering::SeqCst);
434 Ok(()) 456 Ok(())
435 } 457 }
436 }) 458 })
@@ -528,3 +550,8 @@ pub(crate) fn get_driver() -> &'static RtcDriver {
528pub(crate) fn init(cs: CriticalSection) { 550pub(crate) fn init(cs: CriticalSection) {
529 DRIVER.init(cs) 551 DRIVER.init(cs)
530} 552}
553
554#[cfg(all(feature = "low-power", stm32wlex))]
555pub(crate) fn init_timer(cs: CriticalSection) {
556 DRIVER.init_timer(cs)
557}
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 @@
1[target.'cfg(all(target_arch = "arm", target_os = "none"))']
2# replace your chip as listed in `probe-rs chip list`
3runner = "probe-rs run --chip STM32WLE5JCIx --connect-under-reset"
4
5[build]
6target = "thumbv7em-none-eabi"
7
8[env]
9DEFMT_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 @@
1[package]
2edition = "2024"
3name = "embassy-stm32wl-examples"
4version = "0.1.0"
5license = "MIT OR Apache-2.0"
6publish = false
7
8[dependencies]
9# Change stm32wl55jc-cm4 to your chip name, if necessary.
10embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "stm32wle5jc", "time-driver-any", "memory-x", "unstable-pac", "exti", "low-power"] }
11embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] }
12embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] }
13embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-1_000"] }
14embassy-embedded-hal = { version = "0.5.0", path = "../../embassy-embedded-hal" }
15
16defmt = "1.0.1"
17defmt-rtt = { version = "1.1.0", optional = true }
18defmt-serial = { version = "0.10.0", optional = true }
19
20cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
21cortex-m-rt = "0.7.0"
22embedded-hal = "1.0.0"
23embedded-storage = "0.3.1"
24panic-probe = { version = "1.0.0", features = ["print-defmt"] }
25static_cell = { version = "2.1.1", default-features = false }
26
27[profile.release]
28debug = 2
29
30[package.metadata.embassy]
31build = [
32 { target = "thumbv7em-none-eabi", artifact-dir = "out/examples/stm32wl" }
33]
34
35[features]
36default = ["defmt-serial"]
37defmt-rtt = ["dep:defmt-rtt"]
38defmt-serial = ["dep:defmt-serial"]
diff --git a/examples/stm32wle5/README.md b/examples/stm32wle5/README.md
new file mode 100644
index 000000000..18c3b5071
--- /dev/null
+++ b/examples/stm32wle5/README.md
@@ -0,0 +1,52 @@
1# Low Power Examples for STM32WLEx family
2
3Examples 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.
4
5## Prerequsits
6
7- Connect a usb serial adapter to LPUart1 (this is where ALL logging will go)
8- Optional: Connect an amp meter that ran measure down to 0.1uA to the power test pins
9- `cargo install defmt-print` so you can print log messahes from LPUart1
10
11## Example Notes
12
13All examples will set all pins to analog mode before configuring pins for the example, if any. This saves about 500uA!!!!
14
15- the `adc` example will sleep in STOP1 betwen samples and the chip will only draw about 13uA while sleeping
16- the `blinky` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping
17- the `button_exti` example will sleep in STOP2 and the chip will only draw 1uA or less while sleeping
18- the `i2c` examples will sleep in STOP1 between reads and the chip only draw about 10uA while sleeping
19
20For each example you will need to start `defmt-print` with the example binary and the correct serial port in a seperate terminal. Example:
21```
22defmt-print -w -v -e target/thumbv7em-none-eabi/debug/<module-name> serial --path /dev/cu.usbserial-00000000 --baud 115200
23```
24
25Run individual examples with
26```
27cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin <module-name>
28```
29for example
30```
31cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin blinky
32```
33
34You 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!
35```
36cargo run --bin blinky
37```
38
39## Checklist before running examples
40You 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.
41
42* [ ] 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)
43* [ ] 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.
44* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately.
45* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic
46
47If you are unsure, please drop by the Embassy Matrix chat for support, and let us know:
48
49* Which example you are trying to run
50* Which chip and board you are using
51
52Embassy 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 @@
1fn main() {
2 println!("cargo:rustc-link-arg-bins=--nmagic");
3 println!("cargo:rustc-link-arg-bins=-Tlink.x");
4 println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
5}
diff --git a/examples/stm32wle5/src/bin/adc.rs b/examples/stm32wle5/src/bin/adc.rs
new file mode 100644
index 000000000..ff1a5fa16
--- /dev/null
+++ b/examples/stm32wle5/src/bin/adc.rs
@@ -0,0 +1,100 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5#[cfg(feature = "defmt-rtt")]
6use defmt_rtt as _;
7use embassy_executor::Spawner;
8use embassy_stm32::adc::{Adc, SampleTime};
9use embassy_stm32::low_power::Executor;
10use embassy_stm32::rtc::{Rtc, RtcConfig};
11use embassy_time::Timer;
12use panic_probe as _;
13use static_cell::StaticCell;
14
15#[cortex_m_rt::entry]
16fn main() -> ! {
17 info!("main: Starting!");
18 Executor::take().run(|spawner| {
19 spawner.spawn(unwrap!(async_main(spawner)));
20 });
21}
22
23#[embassy_executor::task]
24async fn async_main(_spawner: Spawner) {
25 let mut config = embassy_stm32::Config::default();
26 // enable HSI clock
27 config.rcc.hsi = true;
28 // enable LSI clock for RTC
29 config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi();
30 config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M);
31 config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI;
32 // enable ADC with HSI clock
33 config.rcc.mux.adcsel = embassy_stm32::pac::rcc::vals::Adcsel::HSI;
34 #[cfg(feature = "defmt-serial")]
35 {
36 // disable debug during sleep to reduce power consumption since we are
37 // using defmt-serial on LPUART1.
38 config.enable_debug_during_sleep = false;
39 // if we are using defmt-serial on LPUART1, we need to use HSI for the clock
40 // so that its registers are preserved during STOP modes.
41 config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI;
42 }
43 // Initialize STM32WL peripherals (use default config like wio-e5-async example)
44 let p = embassy_stm32::init(config);
45
46 // start with all GPIOs as analog to reduce power consumption
47 for r in [
48 embassy_stm32::pac::GPIOA,
49 embassy_stm32::pac::GPIOB,
50 embassy_stm32::pac::GPIOC,
51 embassy_stm32::pac::GPIOH,
52 ] {
53 r.moder().modify(|w| {
54 for i in 0..16 {
55 // don't reset these if probe-rs should stay connected!
56 #[cfg(feature = "defmt-rtt")]
57 if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) {
58 continue;
59 }
60 w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG);
61 }
62 });
63 }
64 #[cfg(feature = "defmt-serial")]
65 {
66 use embassy_stm32::mode::Blocking;
67 use embassy_stm32::usart::Uart;
68 let config = embassy_stm32::usart::Config::default();
69 let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!");
70 static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new();
71 defmt_serial::defmt_serial(SERIAL.init(uart));
72 }
73
74 // give the RTC to the low_power executor...
75 let rtc_config = RtcConfig::default();
76 let rtc = Rtc::new(p.RTC, rtc_config);
77 embassy_stm32::low_power::stop_with_rtc(rtc);
78
79 info!("Hello World!");
80
81 let mut adc = Adc::new(p.ADC1);
82 adc.set_sample_time(SampleTime::CYCLES79_5);
83 let mut pin = p.PA10;
84
85 let mut vrefint = adc.enable_vrefint();
86 let vrefint_sample = adc.blocking_read(&mut vrefint);
87 let convert_to_millivolts = |sample| {
88 // From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf
89 // 6.3.3 Embedded internal reference voltage
90 const VREFINT_MV: u32 = 1212; // mV
91
92 (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16
93 };
94
95 loop {
96 let v = adc.blocking_read(&mut pin);
97 info!("--> {} - {} mV", v, convert_to_millivolts(v));
98 Timer::after_secs(1).await;
99 }
100}
diff --git a/examples/stm32wle5/src/bin/blinky.rs b/examples/stm32wle5/src/bin/blinky.rs
new file mode 100644
index 000000000..1191a1157
--- /dev/null
+++ b/examples/stm32wle5/src/bin/blinky.rs
@@ -0,0 +1,90 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5#[cfg(feature = "defmt-rtt")]
6use defmt_rtt as _;
7use embassy_executor::Spawner;
8use embassy_stm32::gpio::{Level, Output, Speed};
9use embassy_stm32::low_power::Executor;
10use embassy_stm32::rtc::{Rtc, RtcConfig};
11use embassy_time::Timer;
12use panic_probe as _;
13use static_cell::StaticCell;
14
15#[cortex_m_rt::entry]
16fn main() -> ! {
17 info!("main: Starting!");
18 Executor::take().run(|spawner| {
19 spawner.spawn(unwrap!(async_main(spawner)));
20 });
21}
22
23#[embassy_executor::task]
24async fn async_main(_spawner: Spawner) {
25 let mut config = embassy_stm32::Config::default();
26 // enable HSI clock
27 config.rcc.hsi = true;
28 // enable LSI clock for RTC
29 config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi();
30 config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M);
31 config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI;
32 #[cfg(feature = "defmt-serial")]
33 {
34 // disable debug during sleep to reduce power consumption since we are
35 // using defmt-serial on LPUART1.
36 config.enable_debug_during_sleep = false;
37 // if we are using defmt-serial on LPUART1, we need to use HSI for the clock
38 // so that its registers are preserved during STOP modes.
39 config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI;
40 }
41 // Initialize STM32WL peripherals (use default config like wio-e5-async example)
42 let p = embassy_stm32::init(config);
43
44 // start with all GPIOs as analog to reduce power consumption
45 for r in [
46 embassy_stm32::pac::GPIOA,
47 embassy_stm32::pac::GPIOB,
48 embassy_stm32::pac::GPIOC,
49 embassy_stm32::pac::GPIOH,
50 ] {
51 r.moder().modify(|w| {
52 for i in 0..16 {
53 // don't reset these if probe-rs should stay connected!
54 #[cfg(feature = "defmt-rtt")]
55 if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) {
56 continue;
57 }
58 w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG);
59 }
60 });
61 }
62 #[cfg(feature = "defmt-serial")]
63 {
64 use embassy_stm32::mode::Blocking;
65 use embassy_stm32::usart::Uart;
66 let config = embassy_stm32::usart::Config::default();
67 let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!");
68 static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new();
69 defmt_serial::defmt_serial(SERIAL.init(uart));
70 }
71
72 // give the RTC to the low_power executor...
73 let rtc_config = RtcConfig::default();
74 let rtc = Rtc::new(p.RTC, rtc_config);
75 embassy_stm32::low_power::stop_with_rtc(rtc);
76
77 info!("Hello World!");
78
79 let mut led = Output::new(p.PB5, Level::High, Speed::Low);
80
81 loop {
82 info!("low");
83 led.set_low();
84 Timer::after_millis(500).await;
85
86 info!("high");
87 led.set_high();
88 Timer::after_millis(500).await;
89 }
90}
diff --git a/examples/stm32wle5/src/bin/button_exti.rs b/examples/stm32wle5/src/bin/button_exti.rs
new file mode 100644
index 000000000..f07f9724d
--- /dev/null
+++ b/examples/stm32wle5/src/bin/button_exti.rs
@@ -0,0 +1,91 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5#[cfg(feature = "defmt-rtt")]
6use defmt_rtt as _;
7use embassy_executor::Spawner;
8use embassy_stm32::exti::ExtiInput;
9use embassy_stm32::gpio::Pull;
10use embassy_stm32::low_power::Executor;
11use embassy_stm32::rtc::{Rtc, RtcConfig};
12use panic_probe as _;
13use static_cell::StaticCell;
14
15#[cortex_m_rt::entry]
16fn main() -> ! {
17 info!("main: Starting!");
18 Executor::take().run(|spawner| {
19 spawner.spawn(unwrap!(async_main(spawner)));
20 });
21}
22
23#[embassy_executor::task]
24async fn async_main(_spawner: Spawner) {
25 let mut config = embassy_stm32::Config::default();
26 // enable HSI clock
27 config.rcc.hsi = true;
28 // enable LSI clock for RTC
29 config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi();
30 config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M);
31 config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI;
32 // enable ADC with HSI clock
33 config.rcc.mux.adcsel = embassy_stm32::pac::rcc::vals::Adcsel::HSI;
34 #[cfg(feature = "defmt-serial")]
35 {
36 // disable debug during sleep to reduce power consumption since we are
37 // using defmt-serial on LPUART1.
38 config.enable_debug_during_sleep = false;
39 // if we are using defmt-serial on LPUART1, we need to use HSI for the clock
40 // so that its registers are preserved during STOP modes.
41 config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI;
42 }
43 // Initialize STM32WL peripherals (use default config like wio-e5-async example)
44 let p = embassy_stm32::init(config);
45
46 // start with all GPIOs as analog to reduce power consumption
47 for r in [
48 embassy_stm32::pac::GPIOA,
49 embassy_stm32::pac::GPIOB,
50 embassy_stm32::pac::GPIOC,
51 embassy_stm32::pac::GPIOH,
52 ] {
53 r.moder().modify(|w| {
54 for i in 0..16 {
55 // don't reset these if probe-rs should stay connected!
56 #[cfg(feature = "defmt-rtt")]
57 if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) {
58 continue;
59 }
60 w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG);
61 }
62 });
63 }
64 #[cfg(feature = "defmt-serial")]
65 {
66 use embassy_stm32::mode::Blocking;
67 use embassy_stm32::usart::Uart;
68 let config = embassy_stm32::usart::Config::default();
69 let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!");
70 static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new();
71 defmt_serial::defmt_serial(SERIAL.init(uart));
72 }
73
74 // give the RTC to the low_power executor...
75 let rtc_config = RtcConfig::default();
76 let rtc = Rtc::new(p.RTC, rtc_config);
77 embassy_stm32::low_power::stop_with_rtc(rtc);
78
79 info!("Hello World!");
80
81 let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up);
82
83 info!("Press the USER button...");
84
85 loop {
86 button.wait_for_falling_edge().await;
87 info!("Pressed!");
88 button.wait_for_rising_edge().await;
89 info!("Released!");
90 }
91}
diff --git a/examples/stm32wle5/src/bin/i2c.rs b/examples/stm32wle5/src/bin/i2c.rs
new file mode 100644
index 000000000..af07f911e
--- /dev/null
+++ b/examples/stm32wle5/src/bin/i2c.rs
@@ -0,0 +1,110 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5#[cfg(feature = "defmt-rtt")]
6use defmt_rtt as _;
7use embassy_executor::Spawner;
8use embassy_stm32::i2c::I2c;
9use embassy_stm32::low_power::Executor;
10use embassy_stm32::rtc::{Rtc, RtcConfig};
11use embassy_stm32::time::Hertz;
12use embassy_stm32::{bind_interrupts, i2c, peripherals};
13use embassy_time::{Duration, Timer};
14use panic_probe as _;
15use static_cell::StaticCell;
16
17bind_interrupts!(struct IrqsI2C{
18 I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
19 I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
20});
21
22#[cortex_m_rt::entry]
23fn main() -> ! {
24 info!("main: Starting!");
25 Executor::take().run(|spawner| {
26 spawner.spawn(unwrap!(async_main(spawner)));
27 });
28}
29
30#[embassy_executor::task]
31async fn async_main(_spawner: Spawner) {
32 let mut config = embassy_stm32::Config::default();
33 // enable HSI clock
34 config.rcc.hsi = true;
35 // enable LSI clock for RTC
36 config.rcc.ls = embassy_stm32::rcc::LsConfig::default_lsi();
37 config.rcc.msi = Some(embassy_stm32::rcc::MSIRange::RANGE4M);
38 config.rcc.sys = embassy_stm32::rcc::Sysclk::MSI;
39 // enable ADC with HSI clock
40 config.rcc.mux.i2c2sel = embassy_stm32::pac::rcc::vals::I2c2sel::HSI;
41 #[cfg(feature = "defmt-serial")]
42 {
43 // disable debug during sleep to reduce power consumption since we are
44 // using defmt-serial on LPUART1.
45 config.enable_debug_during_sleep = false;
46 // if we are using defmt-serial on LPUART1, we need to use HSI for the clock
47 // so that its registers are preserved during STOP modes.
48 config.rcc.mux.lpuart1sel = embassy_stm32::pac::rcc::vals::Lpuart1sel::HSI;
49 }
50 // Initialize STM32WL peripherals (use default config like wio-e5-async example)
51 let p = embassy_stm32::init(config);
52
53 // start with all GPIOs as analog to reduce power consumption
54 for r in [
55 embassy_stm32::pac::GPIOA,
56 embassy_stm32::pac::GPIOB,
57 embassy_stm32::pac::GPIOC,
58 embassy_stm32::pac::GPIOH,
59 ] {
60 r.moder().modify(|w| {
61 for i in 0..16 {
62 // don't reset these if probe-rs should stay connected!
63 #[cfg(feature = "defmt-rtt")]
64 if config.enable_debug_during_sleep && r == embassy_stm32::pac::GPIOA && [13, 14].contains(&i) {
65 continue;
66 }
67 w.set_moder(i, embassy_stm32::pac::gpio::vals::Moder::ANALOG);
68 }
69 });
70 }
71 #[cfg(feature = "defmt-serial")]
72 {
73 use embassy_stm32::mode::Blocking;
74 use embassy_stm32::usart::Uart;
75 let config = embassy_stm32::usart::Config::default();
76 let uart = Uart::new_blocking(p.LPUART1, p.PC0, p.PC1, config).expect("failed to configure UART!");
77 static SERIAL: StaticCell<Uart<'static, Blocking>> = StaticCell::new();
78 defmt_serial::defmt_serial(SERIAL.init(uart));
79 }
80
81 // give the RTC to the low_power executor...
82 let rtc_config = RtcConfig::default();
83 let rtc = Rtc::new(p.RTC, rtc_config);
84 embassy_stm32::low_power::stop_with_rtc(rtc);
85
86 info!("Hello World!");
87 let en3v3 = embassy_stm32::gpio::Output::new(
88 p.PA9,
89 embassy_stm32::gpio::Level::High,
90 embassy_stm32::gpio::Speed::High,
91 );
92 core::mem::forget(en3v3); // keep the output pin enabled
93
94 let mut i2c = I2c::new(p.I2C2, p.PB15, p.PA15, IrqsI2C, p.DMA1_CH6, p.DMA1_CH7, {
95 let mut config = i2c::Config::default();
96 config.frequency = Hertz::khz(100);
97 config.timeout = Duration::from_millis(500);
98 config
99 });
100
101 loop {
102 let mut buffer = [0; 2];
103 // read the temperature register of the onboard lm75
104 match i2c.read(0x48, &mut buffer).await {
105 Ok(_) => info!("--> {:?}", buffer),
106 Err(e) => info!("--> Error: {:?}", e),
107 }
108 Timer::after_secs(5).await;
109 }
110}
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 @@
1{
2 "folders": [
3 {
4 "path": "."
5 }
6 ],
7 "settings": {
8 "rust-analyzer.cargo.target": "thumbv7em-none-eabi",
9 "rust-analyzer.cargo.allTargets": false,
10 "rust-analyzer.cargo.targetDir": "target/rust-analyzer",
11 "rust-analyzer.checkOnSave": true,
12 }
13}