diff options
| author | xoviat <[email protected]> | 2025-11-03 21:26:30 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-03 21:26:30 +0000 |
| commit | 345efc383fa9afabaf23b629f0937855ea4c754f (patch) | |
| tree | 9dd0ce1991da25ec8686412a93b0c08a52c86f76 | |
| parent | a967d77a0f0eedcc65778528cceee07edbba2813 (diff) | |
| parent | 9f9ce2274a96f9d5395c814fc139abe594453625 (diff) | |
Merge pull request #4716 from liebman/stm32wl-low-power
support low-power executor on stm32wlex
| -rw-r--r-- | embassy-stm32/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/adc/mod.rs | 20 | ||||
| -rw-r--r-- | embassy-stm32/src/adc/v3.rs | 3 | ||||
| -rw-r--r-- | embassy-stm32/src/i2c/v2.rs | 15 | ||||
| -rw-r--r-- | embassy-stm32/src/low_power.rs | 94 | ||||
| -rw-r--r-- | embassy-stm32/src/rcc/l.rs | 43 | ||||
| -rw-r--r-- | embassy-stm32/src/rcc/mod.rs | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/rtc/low_power.rs | 4 | ||||
| -rw-r--r-- | embassy-stm32/src/rtc/v3.rs | 4 | ||||
| -rw-r--r-- | embassy-stm32/src/time_driver.rs | 33 | ||||
| -rw-r--r-- | examples/stm32wle5/.cargo/config.toml | 9 | ||||
| -rw-r--r-- | examples/stm32wle5/Cargo.toml | 38 | ||||
| -rw-r--r-- | examples/stm32wle5/README.md | 52 | ||||
| -rw-r--r-- | examples/stm32wle5/build.rs | 5 | ||||
| -rw-r--r-- | examples/stm32wle5/src/bin/adc.rs | 100 | ||||
| -rw-r--r-- | examples/stm32wle5/src/bin/blinky.rs | 90 | ||||
| -rw-r--r-- | examples/stm32wle5/src/bin/button_exti.rs | 91 | ||||
| -rw-r--r-- | examples/stm32wle5/src/bin/i2c.rs | 110 | ||||
| -rw-r--r-- | examples/stm32wle5/stm32wle5.code-workspace | 13 |
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)] |
| 89 | pub(crate) fn blocking_delay_us(us: u32) { | 89 | pub(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 | ||
| 72 | pub(crate) unsafe fn on_interrupt<T: Instance>() { | 72 | pub(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 | ||
| 69 | static mut EXECUTOR: Option<Executor> = None; | 69 | static mut EXECUTOR: Option<Executor> = None; |
| 70 | 70 | ||
| 71 | #[cfg(stm32wlex)] | ||
| 72 | pub(crate) use self::busy::DeviceBusy; | ||
| 73 | #[cfg(stm32wlex)] | ||
| 74 | mod 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))] |
| 72 | foreach_interrupt! { | 105 | foreach_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)] |
| 94 | pub(crate) unsafe fn on_wakeup_irq() { | 127 | pub(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))] |
| 131 | use stm32_metapac::pwr::vals::Lpms; | 167 | use stm32_metapac::pwr::vals::Lpms; |
| 132 | 168 | ||
| 133 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32u0))] | 169 | #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] |
| 134 | impl Into<Lpms> for StopMode { | 170 | impl 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))] | ||
| 2 | use core::mem::MaybeUninit; | ||
| 3 | |||
| 1 | #[cfg(any(stm32l0, stm32l1))] | 4 | #[cfg(any(stm32l0, stm32l1))] |
| 2 | pub use crate::pac::pwr::vals::Vos as VoltageScale; | 5 | pub use crate::pac::pwr::vals::Vos as VoltageScale; |
| 3 | use crate::pac::rcc::regs::Cfgr; | 6 | use crate::pac::rcc::regs::Cfgr; |
| @@ -11,6 +14,42 @@ use crate::time::Hertz; | |||
| 11 | /// HSI speed | 14 | /// HSI speed |
| 12 | pub const HSI_FREQ: Hertz = Hertz(16_000_000); | 15 | pub 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))] | ||
| 22 | static 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))] | ||
| 28 | pub(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))] | ||
| 37 | pub(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 | ||
| 43 | pub(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)] |
| 15 | pub enum HseMode { | 54 | pub 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 | ||
| 156 | pub(crate) unsafe fn init(config: Config) { | 195 | pub(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"))] |
| 393 | pub fn reinit<'a>(config: Config, _rcc: &'a mut crate::Peri<'a, crate::peripherals::RCC>) { | 393 | pub 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 | ))] |
| 73 | impl From<WakeupPrescaler> for crate::pac::rtc::vals::Wucksel { | 73 | impl 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 | ))] |
| 89 | impl From<crate::pac::rtc::vals::Wucksel> for WakeupPrescaler { | 89 | impl 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 | ||
| 3 | use core::cell::{Cell, RefCell}; | 3 | use core::cell::{Cell, RefCell}; |
| 4 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 5 | use core::sync::atomic::AtomicU16; | ||
| 4 | use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; | 6 | use core::sync::atomic::{AtomicU32, Ordering, compiler_fence}; |
| 5 | 7 | ||
| 6 | use critical_section::CriticalSection; | 8 | use 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 | ||
| 228 | impl RtcDriver { | 235 | impl 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 { | |||
| 528 | pub(crate) fn init(cs: CriticalSection) { | 550 | pub(crate) fn init(cs: CriticalSection) { |
| 529 | DRIVER.init(cs) | 551 | DRIVER.init(cs) |
| 530 | } | 552 | } |
| 553 | |||
| 554 | #[cfg(all(feature = "low-power", stm32wlex))] | ||
| 555 | pub(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` | ||
| 3 | runner = "probe-rs run --chip STM32WLE5JCIx --connect-under-reset" | ||
| 4 | |||
| 5 | [build] | ||
| 6 | target = "thumbv7em-none-eabi" | ||
| 7 | |||
| 8 | [env] | ||
| 9 | 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 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2024" | ||
| 3 | name = "embassy-stm32wl-examples" | ||
| 4 | version = "0.1.0" | ||
| 5 | license = "MIT OR Apache-2.0" | ||
| 6 | publish = false | ||
| 7 | |||
| 8 | [dependencies] | ||
| 9 | # Change stm32wl55jc-cm4 to your chip name, if necessary. | ||
| 10 | embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = ["defmt", "stm32wle5jc", "time-driver-any", "memory-x", "unstable-pac", "exti", "low-power"] } | ||
| 11 | embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } | ||
| 12 | embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } | ||
| 13 | embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-1_000"] } | ||
| 14 | embassy-embedded-hal = { version = "0.5.0", path = "../../embassy-embedded-hal" } | ||
| 15 | |||
| 16 | defmt = "1.0.1" | ||
| 17 | defmt-rtt = { version = "1.1.0", optional = true } | ||
| 18 | defmt-serial = { version = "0.10.0", optional = true } | ||
| 19 | |||
| 20 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | ||
| 21 | cortex-m-rt = "0.7.0" | ||
| 22 | embedded-hal = "1.0.0" | ||
| 23 | embedded-storage = "0.3.1" | ||
| 24 | panic-probe = { version = "1.0.0", features = ["print-defmt"] } | ||
| 25 | static_cell = { version = "2.1.1", default-features = false } | ||
| 26 | |||
| 27 | [profile.release] | ||
| 28 | debug = 2 | ||
| 29 | |||
| 30 | [package.metadata.embassy] | ||
| 31 | build = [ | ||
| 32 | { target = "thumbv7em-none-eabi", artifact-dir = "out/examples/stm32wl" } | ||
| 33 | ] | ||
| 34 | |||
| 35 | [features] | ||
| 36 | default = ["defmt-serial"] | ||
| 37 | defmt-rtt = ["dep:defmt-rtt"] | ||
| 38 | defmt-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 | |||
| 3 | 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. | ||
| 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 | |||
| 13 | All 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 | |||
| 20 | For each example you will need to start `defmt-print` with the example binary and the correct serial port in a seperate terminal. Example: | ||
| 21 | ``` | ||
| 22 | defmt-print -w -v -e target/thumbv7em-none-eabi/debug/<module-name> serial --path /dev/cu.usbserial-00000000 --baud 115200 | ||
| 23 | ``` | ||
| 24 | |||
| 25 | Run individual examples with | ||
| 26 | ``` | ||
| 27 | cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin <module-name> | ||
| 28 | ``` | ||
| 29 | for example | ||
| 30 | ``` | ||
| 31 | cargo flash --chip STM32WLE5JCIx --connect-under-reset --bin blinky | ||
| 32 | ``` | ||
| 33 | |||
| 34 | 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! | ||
| 35 | ``` | ||
| 36 | cargo run --bin blinky | ||
| 37 | ``` | ||
| 38 | |||
| 39 | ## Checklist before running examples | ||
| 40 | 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. | ||
| 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 | |||
| 47 | If 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 | |||
| 52 | 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 @@ | |||
| 1 | fn 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 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::adc::{Adc, SampleTime}; | ||
| 9 | use embassy_stm32::low_power::Executor; | ||
| 10 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 11 | use embassy_time::Timer; | ||
| 12 | use panic_probe as _; | ||
| 13 | use static_cell::StaticCell; | ||
| 14 | |||
| 15 | #[cortex_m_rt::entry] | ||
| 16 | fn main() -> ! { | ||
| 17 | info!("main: Starting!"); | ||
| 18 | Executor::take().run(|spawner| { | ||
| 19 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 20 | }); | ||
| 21 | } | ||
| 22 | |||
| 23 | #[embassy_executor::task] | ||
| 24 | async 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 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 9 | use embassy_stm32::low_power::Executor; | ||
| 10 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 11 | use embassy_time::Timer; | ||
| 12 | use panic_probe as _; | ||
| 13 | use static_cell::StaticCell; | ||
| 14 | |||
| 15 | #[cortex_m_rt::entry] | ||
| 16 | fn main() -> ! { | ||
| 17 | info!("main: Starting!"); | ||
| 18 | Executor::take().run(|spawner| { | ||
| 19 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 20 | }); | ||
| 21 | } | ||
| 22 | |||
| 23 | #[embassy_executor::task] | ||
| 24 | async 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 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::exti::ExtiInput; | ||
| 9 | use embassy_stm32::gpio::Pull; | ||
| 10 | use embassy_stm32::low_power::Executor; | ||
| 11 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 12 | use panic_probe as _; | ||
| 13 | use static_cell::StaticCell; | ||
| 14 | |||
| 15 | #[cortex_m_rt::entry] | ||
| 16 | fn main() -> ! { | ||
| 17 | info!("main: Starting!"); | ||
| 18 | Executor::take().run(|spawner| { | ||
| 19 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 20 | }); | ||
| 21 | } | ||
| 22 | |||
| 23 | #[embassy_executor::task] | ||
| 24 | async 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 | |||
| 4 | use defmt::*; | ||
| 5 | #[cfg(feature = "defmt-rtt")] | ||
| 6 | use defmt_rtt as _; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::i2c::I2c; | ||
| 9 | use embassy_stm32::low_power::Executor; | ||
| 10 | use embassy_stm32::rtc::{Rtc, RtcConfig}; | ||
| 11 | use embassy_stm32::time::Hertz; | ||
| 12 | use embassy_stm32::{bind_interrupts, i2c, peripherals}; | ||
| 13 | use embassy_time::{Duration, Timer}; | ||
| 14 | use panic_probe as _; | ||
| 15 | use static_cell::StaticCell; | ||
| 16 | |||
| 17 | bind_interrupts!(struct IrqsI2C{ | ||
| 18 | I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>; | ||
| 19 | I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>; | ||
| 20 | }); | ||
| 21 | |||
| 22 | #[cortex_m_rt::entry] | ||
| 23 | fn main() -> ! { | ||
| 24 | info!("main: Starting!"); | ||
| 25 | Executor::take().run(|spawner| { | ||
| 26 | spawner.spawn(unwrap!(async_main(spawner))); | ||
| 27 | }); | ||
| 28 | } | ||
| 29 | |||
| 30 | #[embassy_executor::task] | ||
| 31 | async 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 | } | ||
