From e0b6bcb13bb3c5b419ace922b8f9955a0c620d35 Mon Sep 17 00:00:00 2001 From: Filip Brozovic Date: Tue, 18 Nov 2025 14:01:07 +0100 Subject: stm32: async flash erase/write for h7 --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/flash/h7.rs | 190 +++++++++++++++++++++++++++++++++-------- embassy-stm32/src/flash/mod.rs | 4 +- 3 files changed, 158 insertions(+), 37 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 8e3e802a4..ed523debd 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -66,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: add flash support for c0 family ([#4874](https://github.com/embassy-rs/embassy/pull/4874)) - fix: fixing channel numbers on vbat and vddcore for adc on adc - adc: adding disable to vbat +- feat: stm32/flash: add async support for h7 family ## 0.4.0 - 2025-08-26 diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs index 8a43cce3f..b342f4a83 100644 --- a/embassy-stm32/src/flash/h7.rs +++ b/embassy-stm32/src/flash/h7.rs @@ -1,10 +1,31 @@ use core::ptr::write_volatile; use core::sync::atomic::{Ordering, fence}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::flash::regs::Sr; + use super::{BANK1_REGION, FLASH_REGIONS, FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; +static WAKER: AtomicWaker = AtomicWaker::new(); + +pub(crate) unsafe fn on_interrupt() { + // Clear IRQ flags + pac::FLASH.bank(0).ccr().write(|w| { + w.set_clr_eop(true); + w.set_clr_operr(true); + }); + if is_dual_bank() { + pac::FLASH.bank(1).ccr().write(|w| { + w.set_clr_eop(true); + w.set_clr_operr(true); + }); + } + + WAKER.wake(); +} + const fn is_dual_bank() -> bool { FLASH_REGIONS.len() >= 2 } @@ -29,12 +50,68 @@ pub(crate) unsafe fn unlock() { } } +pub(crate) unsafe fn enable_write() { + enable_blocking_write(); +} + +pub(crate) unsafe fn disable_write() { + disable_blocking_write(); +} + pub(crate) unsafe fn enable_blocking_write() { assert_eq!(0, WRITE_SIZE % 4); } pub(crate) unsafe fn disable_blocking_write() {} +pub(crate) async unsafe fn write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + // We cannot have the write setup sequence in begin_write as it depends on the address + let bank = if start_address < BANK1_REGION.end() { + pac::FLASH.bank(0) + } else { + pac::FLASH.bank(1) + }; + bank.cr().write(|w| { + w.set_pg(true); + #[cfg(flash_h7)] + w.set_psize(2); // 32 bits at once + w.set_eopie(true); + w.set_operrie(true); + }); + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let mut res = None; + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + res = Some(wait_ready(bank).await); + bank.sr().modify(|w| { + if w.eop() { + w.set_eop(true); + } + }); + if unwrap!(res).is_err() { + break; + } + } + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + bank.cr().write(|w| { + w.set_pg(false); + w.set_eopie(false); + w.set_operrie(false); + }); + + unwrap!(res) +} + pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { // We cannot have the write setup sequence in begin_write as it depends on the address let bank = if start_address < BANK1_REGION.end() { @@ -77,6 +154,36 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) unwrap!(res) } +pub(crate) async unsafe fn erase_sector(sector: &FlashSector) -> Result<(), Error> { + let bank = pac::FLASH.bank(sector.bank as usize); + bank.cr().modify(|w| { + w.set_ser(true); + #[cfg(flash_h7)] + w.set_snb(sector.index_in_bank); + #[cfg(flash_h7ab)] + w.set_ssn(sector.index_in_bank); + w.set_eopie(true); + w.set_operrie(true); + }); + + bank.cr().modify(|w| { + w.set_start(true); + }); + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let ret: Result<(), Error> = wait_ready(bank).await; + bank.cr().modify(|w| { + w.set_ser(false); + w.set_eopie(false); + w.set_operrie(false); + }); + bank_clear_all_err(bank); + ret +} + pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { let bank = pac::FLASH.bank(sector.bank as usize); bank.cr().modify(|w| { @@ -112,46 +219,59 @@ unsafe fn bank_clear_all_err(bank: pac::flash::Bank) { bank.sr().modify(|_| {}); } +async fn wait_ready(bank: pac::flash::Bank) -> Result<(), Error> { + use core::future::poll_fn; + use core::task::Poll; + + poll_fn(|cx| { + WAKER.register(cx.waker()); + + let sr = bank.sr().read(); + if !sr.bsy() && !sr.qw() { + Poll::Ready(get_result(sr)) + } else { + return Poll::Pending; + } + }) + .await +} + unsafe fn blocking_wait_ready(bank: pac::flash::Bank) -> Result<(), Error> { loop { let sr = bank.sr().read(); if !sr.bsy() && !sr.qw() { - if sr.wrperr() { - return Err(Error::Protected); - } - if sr.pgserr() { - error!("pgserr"); - return Err(Error::Seq); - } - if sr.incerr() { - // writing to a different address when programming 256 bit word was not finished - error!("incerr"); - return Err(Error::Seq); - } - if sr.crcrderr() { - error!("crcrderr"); - return Err(Error::Seq); - } - if sr.operr() { - return Err(Error::Prog); - } - if sr.sneccerr1() { - // single ECC error - return Err(Error::Prog); - } - if sr.dbeccerr() { - // double ECC error - return Err(Error::Prog); - } - if sr.rdperr() { - return Err(Error::Protected); - } - if sr.rdserr() { - return Err(Error::Protected); - } - - return Ok(()); + return get_result(sr); } } } + +fn get_result(sr: Sr) -> Result<(), Error> { + if sr.wrperr() { + Err(Error::Protected) + } else if sr.pgserr() { + error!("pgserr"); + Err(Error::Seq) + } else if sr.incerr() { + // writing to a different address when programming 256 bit word was not finished + error!("incerr"); + Err(Error::Seq) + } else if sr.crcrderr() { + error!("crcrderr"); + Err(Error::Seq) + } else if sr.operr() { + Err(Error::Prog) + } else if sr.sneccerr1() { + // single ECC error + Err(Error::Prog) + } else if sr.dbeccerr() { + // double ECC error + Err(Error::Prog) + } else if sr.rdperr() { + Err(Error::Protected) + } else if sr.rdserr() { + Err(Error::Protected) + } else { + Ok(()) + } +} diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index 39cd9b3a9..6211a37b7 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -1,14 +1,14 @@ //! Flash memory (FLASH) use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; -#[cfg(flash_f4)] +#[cfg(any(flash_f4, flash_h7, flash_h7ab))] mod asynch; #[cfg(flash)] mod common; #[cfg(eeprom)] mod eeprom; -#[cfg(flash_f4)] +#[cfg(any(flash_f4, flash_h7, flash_h7ab))] pub use asynch::InterruptHandler; #[cfg(flash)] pub use common::*; -- cgit From 8f6f6ae8e90610ddd76df001e618075524299d2b Mon Sep 17 00:00:00 2001 From: Filip Brozovic Date: Tue, 18 Nov 2025 14:05:34 +0100 Subject: stm32: add async flash example for h7 --- examples/stm32h7/src/bin/flash_async.rs | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 examples/stm32h7/src/bin/flash_async.rs diff --git a/examples/stm32h7/src/bin/flash_async.rs b/examples/stm32h7/src/bin/flash_async.rs new file mode 100644 index 000000000..96d1936f3 --- /dev/null +++ b/examples/stm32h7/src/bin/flash_async.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::{Flash, InterruptHandler}; +use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; +use embassy_stm32::{Peri, bind_interrupts}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FLASH => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + let mut f = Flash::new(p.FLASH, Irqs); + + // Led should blink uninterrupted during ~2sec erase operation + spawner.spawn(blinky(p.PB14.into()).unwrap()); + + // Test on bank 2 in order not to stall CPU. + test_flash(&mut f, 1024 * 1024, 128 * 1024).await; +} + +#[embassy_executor::task] +async fn blinky(p: Peri<'static, AnyPin>) { + let mut led = Output::new(p, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +async fn test_flash<'a>(f: &mut Flash<'a>, offset: u32, size: u32) { + info!("Testing offset: {=u32:#X}, size: {=u32:#X}", offset, size); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.erase(offset, offset + size).await); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!( + f.write( + offset, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32 + ] + ) + .await + ); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} -- cgit From 4e808345883ae64099135a8384f774c44feccc5c Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 18 Nov 2025 16:27:10 -0600 Subject: fix: fix incorrect logic for buffered usart transmission complete. --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/usart/buffered.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index b6caf8f65..5fd43744e 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- fix: fix incorrect logic for buffered usart transmission complete. - change: stm32/eth: ethernet no longer has a hard dependency on station management, and station management can be used independently ([#4871](https://github.com/embassy-rs/embassy/pull/4871)) - feat: allow embassy_executor::main for low power - feat: Add waveform methods to ComplementaryPwm diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 69c3a740f..26d2b8991 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -87,7 +87,7 @@ unsafe fn on_interrupt(r: Regs, state: &'static State) { // With `usart_v4` hardware FIFO is enabled and Transmission complete (TC) // indicates that all bytes are pushed out from the FIFO. // For other usart variants it shows that last byte from the buffer was just sent. - if sr_val.tc() { + if sr_val.tc() && r.cr1().read().tcie() { // For others it is cleared above with `clear_interrupt_flags`. #[cfg(any(usart_v1, usart_v2))] sr(r).modify(|w| w.set_tc(false)); -- cgit From f40b85d784824a24d42eded11bf9c83d7649ae2b Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 18 Nov 2025 19:34:10 -0600 Subject: stm32/exti: add poll_for methods --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/exti.rs | 46 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index b6caf8f65..7534f5b8e 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- feat: add poll_for methods to exti - change: stm32/eth: ethernet no longer has a hard dependency on station management, and station management can be used independently ([#4871](https://github.com/embassy-rs/embassy/pull/4871)) - feat: allow embassy_executor::main for low power - feat: Add waveform methods to ComplementaryPwm diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index cb46d362c..899d5e677 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -7,6 +7,7 @@ use core::task::{Context, Poll}; use embassy_hal_internal::{PeripheralType, impl_peripheral}; use embassy_sync::waitqueue::AtomicWaker; +use futures_util::FutureExt; use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, PinNumber, Pull}; use crate::pac::EXTI; @@ -133,7 +134,7 @@ impl<'d> ExtiInput<'d> { /// /// This returns immediately if the pin is already high. pub async fn wait_for_high(&mut self) { - let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false); + let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false, true); if self.is_high() { return; } @@ -144,7 +145,7 @@ impl<'d> ExtiInput<'d> { /// /// This returns immediately if the pin is already low. pub async fn wait_for_low(&mut self) { - let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true); + let fut = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true, true); if self.is_low() { return; } @@ -155,19 +156,40 @@ impl<'d> ExtiInput<'d> { /// /// If the pin is already high, it will wait for it to go low then back high. pub async fn wait_for_rising_edge(&mut self) { - ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false).await + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false, true).await + } + + /// Asynchronously wait until the pin sees a rising edge. + /// + /// If the pin is already high, it will wait for it to go low then back high. + pub fn poll_for_rising_edge<'a>(&mut self, cx: &mut Context<'a>) { + let _ = + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, false, false).poll_unpin(cx); } /// Asynchronously wait until the pin sees a falling edge. /// /// If the pin is already low, it will wait for it to go high then back low. pub async fn wait_for_falling_edge(&mut self) { - ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true).await + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true, true).await + } + + /// Asynchronously wait until the pin sees a falling edge. + /// + /// If the pin is already low, it will wait for it to go high then back low. + pub fn poll_for_falling_edge<'a>(&mut self, cx: &mut Context<'a>) { + let _ = + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), false, true, false).poll_unpin(cx); } /// Asynchronously wait until the pin sees any edge (either rising or falling). pub async fn wait_for_any_edge(&mut self) { - ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true).await + ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true, true).await + } + + /// Asynchronously wait until the pin sees any edge (either rising or falling). + pub fn poll_for_any_edge<'a>(&mut self, cx: &mut Context<'a>) { + let _ = ExtiInputFuture::new(self.pin.pin.pin.pin(), self.pin.pin.pin.port(), true, true, false).poll_unpin(cx); } } @@ -227,11 +249,12 @@ impl<'d> embedded_hal_async::digital::Wait for ExtiInput<'d> { #[must_use = "futures do nothing unless you `.await` or poll them"] struct ExtiInputFuture<'a> { pin: PinNumber, + drop: bool, phantom: PhantomData<&'a mut AnyPin>, } impl<'a> ExtiInputFuture<'a> { - fn new(pin: PinNumber, port: PinNumber, rising: bool, falling: bool) -> Self { + fn new(pin: PinNumber, port: PinNumber, rising: bool, falling: bool, drop: bool) -> Self { critical_section::with(|_| { let pin = pin as usize; exticr_regs().exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port)); @@ -252,6 +275,7 @@ impl<'a> ExtiInputFuture<'a> { Self { pin, + drop, phantom: PhantomData, } } @@ -259,10 +283,12 @@ impl<'a> ExtiInputFuture<'a> { impl<'a> Drop for ExtiInputFuture<'a> { fn drop(&mut self) { - critical_section::with(|_| { - let pin = self.pin as _; - cpu_regs().imr(0).modify(|w| w.set_line(pin, false)); - }); + if self.drop { + critical_section::with(|_| { + let pin = self.pin as _; + cpu_regs().imr(0).modify(|w| w.set_line(pin, false)); + }); + } } } -- cgit From 4104d114955be79cf419cb4cfb5c0e72b0abd261 Mon Sep 17 00:00:00 2001 From: xoviat Date: Wed, 19 Nov 2025 15:24:05 -0600 Subject: stm32: impl. stop for stm32wb --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/hsem/mod.rs | 60 +++++++++++++++++++++++++++---------- embassy-stm32/src/lib.rs | 3 ++ embassy-stm32/src/low_power.rs | 68 ++++++++++++++++++++++++++++++++++++++---- examples/stm32wb/Cargo.toml | 2 +- 5 files changed, 112 insertions(+), 22 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 597cbb192..7d8e7c617 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate +- feat: implement stop for stm32wb. - change: rework hsem and add HIL test for some chips. - change: stm32/eth: ethernet no longer has a hard dependency on station management, and station management can be used independently ([#4871](https://github.com/embassy-rs/embassy/pull/4871)) - feat: allow embassy_executor::main for low power diff --git a/embassy-stm32/src/hsem/mod.rs b/embassy-stm32/src/hsem/mod.rs index a6b910a53..5f1ed9b09 100644 --- a/embassy-stm32/src/hsem/mod.rs +++ b/embassy-stm32/src/hsem/mod.rs @@ -5,6 +5,8 @@ use core::marker::PhantomData; use core::sync::atomic::{Ordering, compiler_fence}; use core::task::Poll; +#[cfg(all(stm32wb, feature = "low-power"))] +use critical_section::CriticalSection; use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; @@ -78,6 +80,12 @@ impl CoreId { } } +#[cfg(not(all(stm32wb, feature = "low-power")))] +const PUB_CHANNELS: usize = 6; + +#[cfg(all(stm32wb, feature = "low-power"))] +const PUB_CHANNELS: usize = 4; + /// TX interrupt handler. pub struct HardwareSemaphoreInterruptHandler { _phantom: PhantomData, @@ -127,7 +135,7 @@ pub struct HardwareSemaphoreChannel<'a, T: Instance> { } impl<'a, T: Instance> HardwareSemaphoreChannel<'a, T> { - pub(self) const fn new(number: u8) -> Self { + pub(crate) const fn new(number: u8) -> Self { core::assert!(number > 0 && number <= 6); Self { @@ -151,19 +159,29 @@ impl<'a, T: Instance> HardwareSemaphoreChannel<'a, T> { .ier(core_id.to_index()) .modify(|w| w.set_ise(self.index as usize, true)); - if self.two_step_lock(process_id).is_ok() { - Poll::Ready(HardwareSemaphoreMutex { - index: self.index, - process_id: process_id, - _lifetime: PhantomData, - }) - } else { - Poll::Pending + match self.try_lock(process_id) { + Some(mutex) => Poll::Ready(mutex), + None => Poll::Pending, } }) .await } + /// Try to lock the semaphor + /// The 2-step lock procedure consists in a write to lock the semaphore, followed by a read to + /// check if the lock has been successful, carried out from the HSEM_Rx register. + pub fn try_lock(&mut self, process_id: u8) -> Option> { + if self.two_step_lock(process_id).is_ok() { + Some(HardwareSemaphoreMutex { + index: self.index, + process_id: process_id, + _lifetime: PhantomData, + }) + } else { + None + } + } + /// Locks the semaphore. /// The 2-step lock procedure consists in a write to lock the semaphore, followed by a read to /// check if the lock has been successful, carried out from the HSEM_Rx register. @@ -206,9 +224,9 @@ impl<'a, T: Instance> HardwareSemaphoreChannel<'a, T> { }); } - /// Checks if the semaphore is locked. - pub fn is_semaphore_locked(&self) -> bool { - T::regs().r(self.index as usize).read().lock() + /// Return the channel number + pub const fn channel(&self) -> u8 { + self.index + 1 } } @@ -230,15 +248,22 @@ impl HardwareSemaphore { /// Get a single channel, and keep the global struct pub const fn channel_for<'a>(&'a mut self, number: u8) -> HardwareSemaphoreChannel<'a, T> { + #[cfg(all(stm32wb, feature = "low-power"))] + core::assert!(number != 3 && number != 4); + HardwareSemaphoreChannel::new(number) } /// Split the global struct into channels - pub const fn split<'a>(self) -> [HardwareSemaphoreChannel<'a, T>; 6] { + /// + /// If using low-power mode, channels 3 and 4 will not be returned + pub const fn split<'a>(self) -> [HardwareSemaphoreChannel<'a, T>; PUB_CHANNELS] { [ HardwareSemaphoreChannel::new(1), HardwareSemaphoreChannel::new(2), + #[cfg(not(all(stm32wb, feature = "low-power")))] HardwareSemaphoreChannel::new(3), + #[cfg(not(all(stm32wb, feature = "low-power")))] HardwareSemaphoreChannel::new(4), HardwareSemaphoreChannel::new(5), HardwareSemaphoreChannel::new(6), @@ -267,6 +292,11 @@ impl HardwareSemaphore { } } +#[cfg(all(stm32wb, feature = "low-power"))] +pub(crate) fn init_hsem(_cs: CriticalSection) { + rcc::enable_and_reset::(); +} + struct State { wakers: [AtomicWaker; 6], } @@ -278,8 +308,8 @@ impl State { } } - const fn waker_for(&self, number: u8) -> &AtomicWaker { - &self.wakers[number as usize] + const fn waker_for(&self, index: u8) -> &AtomicWaker { + &self.wakers[index as usize] } } diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 7c3770643..ef6f1d6dc 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -650,6 +650,9 @@ fn init_hw(config: Config) -> Peripherals { #[cfg(feature = "low-power")] rtc::init_rtc(cs, config.rtc, config.min_stop_pause); + + #[cfg(all(stm32wb, feature = "low-power"))] + hsem::init_hsem(cs); } p diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index cf8f2b393..15478eed4 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -148,7 +148,7 @@ pub enum StopMode { } #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] -use stm32_metapac::pwr::vals::Lpms; +use crate::pac::pwr::vals::Lpms; #[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] impl Into for StopMode { @@ -242,8 +242,63 @@ impl Executor { } } + #[cfg(all(stm32wb, feature = "low-power"))] + fn configure_stop_stm32wb(&self, _stop_mode: StopMode) -> Result<(), ()> { + use core::task::Poll; + + use embassy_futures::poll_once; + + use crate::hsem::HardwareSemaphoreChannel; + use crate::pac::rcc::vals::{Smps, Sw}; + use crate::pac::{PWR, RCC}; + + let sem3_mutex = match poll_once(HardwareSemaphoreChannel::::new(3).lock(0)) { + Poll::Pending => None, + Poll::Ready(mutex) => Some(mutex), + } + .ok_or(())?; + + let sem4_mutex = HardwareSemaphoreChannel::::new(4).try_lock(0); + if let Some(sem4_mutex) = sem4_mutex { + if PWR.extscr().read().c2ds() { + drop(sem4_mutex); + } else { + return Ok(()); + } + } + + // Sem4 not granted + // Set HSION + RCC.cr().modify(|w| { + w.set_hsion(true); + }); + + // Wait for HSIRDY + while !RCC.cr().read().hsirdy() {} + + // Set SW to HSI + RCC.cfgr().modify(|w| { + w.set_sw(Sw::HSI); + }); + + // Wait for SWS to report HSI + while !RCC.cfgr().read().sws().eq(&Sw::HSI) {} + + // Set SMPSSEL to HSI + RCC.smpscr().modify(|w| { + w.set_smpssel(Smps::HSI); + }); + + drop(sem3_mutex); + + Ok(()) + } + #[allow(unused_variables)] - fn configure_stop(&self, stop_mode: StopMode) { + fn configure_stop(&self, stop_mode: StopMode) -> Result<(), ()> { + #[cfg(all(stm32wb, feature = "low-power"))] + self.configure_stop_stm32wb(stop_mode)?; + #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba, stm32wlex))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); #[cfg(stm32h5)] @@ -252,6 +307,8 @@ impl Executor { v.set_lpms(vals::Lpms::STOP); v.set_svos(vals::Svos::SCALE3); }); + + Ok(()) } fn configure_pwr(&self) { @@ -267,12 +324,11 @@ impl Executor { critical_section::with(|cs| { let stop_mode = Self::stop_mode(cs)?; let _ = get_driver().pause_time(cs).ok()?; + self.configure_stop(stop_mode).ok()?; - Some(stop_mode) + Some(()) }) - .map(|stop_mode| { - self.configure_stop(stop_mode); - + .map(|_| { #[cfg(not(feature = "low-power-debug-with-sleep"))] Self::get_scb().set_sleepdeep(); }); diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 783690c11..83119e3a0 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] # Change stm32wb55rg to your chip name in both dependencies, if necessary. -embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } +embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti", "low-power"] } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } -- cgit From 19e61543198e2d15fd4c7aef9377c8f40ae86ae0 Mon Sep 17 00:00:00 2001 From: xoviat Date: Wed, 19 Nov 2025 16:14:23 -0600 Subject: stm32: impl. low power test for stm32wb55 --- embassy-stm32/src/hsem/mod.rs | 9 +++++++-- embassy-stm32/src/low_power.rs | 24 ++++++++++++++++-------- tests/stm32/Cargo.toml | 2 +- tests/stm32/src/bin/hsem.rs | 3 +++ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/embassy-stm32/src/hsem/mod.rs b/embassy-stm32/src/hsem/mod.rs index 5f1ed9b09..e62de0454 100644 --- a/embassy-stm32/src/hsem/mod.rs +++ b/embassy-stm32/src/hsem/mod.rs @@ -293,8 +293,13 @@ impl HardwareSemaphore { } #[cfg(all(stm32wb, feature = "low-power"))] -pub(crate) fn init_hsem(_cs: CriticalSection) { - rcc::enable_and_reset::(); +pub(crate) fn init_hsem(cs: CriticalSection) { + rcc::enable_and_reset_with_cs::(cs); + + unsafe { + crate::rcc::REFCOUNT_STOP1 = 0; + crate::rcc::REFCOUNT_STOP2 = 0; + } } struct State { diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 15478eed4..f35eb71e4 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -147,17 +147,17 @@ pub enum StopMode { Stop2, } -#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wb, stm32wlex, stm32u0))] use crate::pac::pwr::vals::Lpms; -#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wlex, stm32u0))] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32wba, stm32wb, stm32wlex, stm32u0))] impl Into for StopMode { fn into(self) -> Lpms { match self { StopMode::Stop1 => Lpms::STOP1, - #[cfg(not(stm32wba))] + #[cfg(not(any(stm32wb, stm32wba)))] StopMode::Stop2 => Lpms::STOP2, - #[cfg(stm32wba)] + #[cfg(any(stm32wb, stm32wba))] StopMode::Stop2 => Lpms::STOP1, // TODO: WBA has no STOP2? } } @@ -237,13 +237,15 @@ impl Executor { trace!("low power: stop 1"); Some(StopMode::Stop1) } else { - trace!("low power: not ready to stop"); + trace!("low power: not ready to stop (refcount_stop1: {})", unsafe { + REFCOUNT_STOP1 + }); None } } #[cfg(all(stm32wb, feature = "low-power"))] - fn configure_stop_stm32wb(&self, _stop_mode: StopMode) -> Result<(), ()> { + fn configure_stop_stm32wb(&self) -> Result<(), ()> { use core::task::Poll; use embassy_futures::poll_once; @@ -252,14 +254,20 @@ impl Executor { use crate::pac::rcc::vals::{Smps, Sw}; use crate::pac::{PWR, RCC}; + trace!("low power: trying to get sem3"); + let sem3_mutex = match poll_once(HardwareSemaphoreChannel::::new(3).lock(0)) { Poll::Pending => None, Poll::Ready(mutex) => Some(mutex), } .ok_or(())?; + trace!("low power: got sem3"); + let sem4_mutex = HardwareSemaphoreChannel::::new(4).try_lock(0); if let Some(sem4_mutex) = sem4_mutex { + trace!("low power: got sem4"); + if PWR.extscr().read().c2ds() { drop(sem4_mutex); } else { @@ -297,9 +305,9 @@ impl Executor { #[allow(unused_variables)] fn configure_stop(&self, stop_mode: StopMode) -> Result<(), ()> { #[cfg(all(stm32wb, feature = "low-power"))] - self.configure_stop_stm32wb(stop_mode)?; + self.configure_stop_stm32wb()?; - #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wba, stm32wlex))] + #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wb, stm32wba, stm32wlex))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); #[cfg(stm32h5)] crate::pac::PWR.pmcr().modify(|v| { diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index f5684d4df..8fcb6b2b4 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -31,7 +31,7 @@ stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng", "dual- stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash", "dual-bank"] stm32u585ai = ["embassy-stm32/stm32u585ai", "spi-v345", "chrono", "rng", "hash", "cordic"] stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "spi-v345", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash -stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng", "hsem"] +stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng", "hsem", "stop"] stm32wba52cg = ["embassy-stm32/stm32wba52cg", "spi-v345", "chrono", "rng", "hash"] stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono", "hsem"] stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"] diff --git a/tests/stm32/src/bin/hsem.rs b/tests/stm32/src/bin/hsem.rs index 19648997c..fa69f22b2 100644 --- a/tests/stm32/src/bin/hsem.rs +++ b/tests/stm32/src/bin/hsem.rs @@ -30,6 +30,9 @@ async fn main(_spawner: Spawner) { // // hsem.channel_for(SemaphoreNumber::Channel5).unlock(0); + #[cfg(feature = "stm32wb55rg")] + let [_channel1, _channel2, mut channel5, _channel6] = hsem.split(); + #[cfg(not(feature = "stm32wb55rg"))] let [_channel1, _channel2, _channel3, _channel4, mut channel5, _channel6] = hsem.split(); info!("Locking channel 5"); -- cgit From 331901135c9537e726e08390eacdcd6c965d2406 Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 20 Nov 2025 07:49:04 -0600 Subject: low power: don't enter stop without rcc config --- embassy-stm32/src/low_power.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index f35eb71e4..9de49c867 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -50,7 +50,7 @@ use critical_section::CriticalSection; use embassy_executor::*; use crate::interrupt; -use crate::rcc::{REFCOUNT_STOP1, REFCOUNT_STOP2}; +use crate::rcc::{RCC_CONFIG, REFCOUNT_STOP1, REFCOUNT_STOP2}; use crate::time_driver::get_driver; const THREAD_PENDER: usize = usize::MAX; @@ -201,7 +201,7 @@ impl Executor { { use crate::pac::rcc::vals::Sw; use crate::pac::{PWR, RCC}; - use crate::rcc::{RCC_CONFIG, init as init_rcc}; + use crate::rcc::init as init_rcc; let extscr = PWR.extscr().read(); if extscr.c1stop2f() || extscr.c1stopf() { @@ -245,7 +245,7 @@ impl Executor { } #[cfg(all(stm32wb, feature = "low-power"))] - fn configure_stop_stm32wb(&self) -> Result<(), ()> { + fn configure_stop_stm32wb(&self, _cs: CriticalSection) -> Result<(), ()> { use core::task::Poll; use embassy_futures::poll_once; @@ -303,9 +303,9 @@ impl Executor { } #[allow(unused_variables)] - fn configure_stop(&self, stop_mode: StopMode) -> Result<(), ()> { + fn configure_stop(&self, _cs: CriticalSection, stop_mode: StopMode) -> Result<(), ()> { #[cfg(all(stm32wb, feature = "low-power"))] - self.configure_stop_stm32wb()?; + self.configure_stop_stm32wb(_cs)?; #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0, stm32wb, stm32wba, stm32wlex))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); @@ -330,9 +330,10 @@ impl Executor { compiler_fence(Ordering::SeqCst); critical_section::with(|cs| { + let _ = unsafe { RCC_CONFIG }?; let stop_mode = Self::stop_mode(cs)?; - let _ = get_driver().pause_time(cs).ok()?; - self.configure_stop(stop_mode).ok()?; + get_driver().pause_time(cs).ok()?; + self.configure_stop(cs, stop_mode).ok()?; Some(()) }) -- cgit From 8d1b4fde897af2c943b5b1abe1503a49a5b8560a Mon Sep 17 00:00:00 2001 From: xoviat Date: Thu, 20 Nov 2025 07:49:14 -0600 Subject: adc: fix ringbuf stop --- embassy-stm32/src/adc/ringbuffered.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/embassy-stm32/src/adc/ringbuffered.rs b/embassy-stm32/src/adc/ringbuffered.rs index a56f8ca0b..5437866d3 100644 --- a/embassy-stm32/src/adc/ringbuffered.rs +++ b/embassy-stm32/src/adc/ringbuffered.rs @@ -49,8 +49,6 @@ impl<'d, T: Instance + AnyInstance> RingBufferedAdc<'d, T> { } pub fn stop(&mut self) { - T::stop(); - self.ring_buf.request_pause(); compiler_fence(Ordering::SeqCst); @@ -161,7 +159,7 @@ impl<'d, T: Instance + AnyInstance> RingBufferedAdc<'d, T> { return Ok(len); } Err(_) => { - self.stop(); + self.ring_buf.request_pause(); return Err(OverrunError); } -- cgit