From cb1bccfd5cf528497e3be8e0da9928854ed8e081 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 25 May 2025 21:39:23 +0800 Subject: [PATCH 01/23] feat(stm32): Add DAC::new_unbuffered method. Signed-off-by: Ivan Li --- embassy-stm32/src/dac/mod.rs | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/embassy-stm32/src/dac/mod.rs b/embassy-stm32/src/dac/mod.rs index 30046849b..d8f1f96f2 100644 --- a/embassy-stm32/src/dac/mod.rs +++ b/embassy-stm32/src/dac/mod.rs @@ -403,6 +403,46 @@ impl<'d, T: Instance> Dac<'d, T, Async> { Mode::NormalExternalBuffered, ) } + /// Create a new `Dac` instance with external output pins and unbuffered mode. + /// + /// This function consumes the underlying DAC peripheral and allows access to both channels. + /// The channels are configured for external output with the buffer disabled. + /// + /// The channels are enabled on creation and begin to drive their output pins. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will + /// disable the channel; you must re-enable them with `enable()`. + /// + /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` + /// method on the underlying channels. + /// + /// # Arguments + /// + /// * `peri` - The DAC peripheral instance. + /// * `dma_ch1` - The DMA channel for DAC channel 1. + /// * `dma_ch2` - The DMA channel for DAC channel 2. + /// * `pin_ch1` - The GPIO pin for DAC channel 1 output. + /// * `pin_ch2` - The GPIO pin for DAC channel 2 output. + /// + /// # Returns + /// + /// A new `Dac` instance in unbuffered mode. + pub fn new_unbuffered( + peri: Peri<'d, T>, + dma_ch1: Peri<'d, impl Dma>, + dma_ch2: Peri<'d, impl Dma>, + pin_ch1: Peri<'d, impl DacPin + crate::gpio::Pin>, + pin_ch2: Peri<'d, impl DacPin + crate::gpio::Pin>, + ) -> Self { + pin_ch1.set_as_analog(); + pin_ch2.set_as_analog(); + Self::new_inner( + peri, + new_dma!(dma_ch1), + new_dma!(dma_ch2), + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + Mode::NormalExternalUnbuffered, + ) + } /// Create a new `Dac` instance where the external output pins are not used, /// so the DAC can only be used to generate internal signals but the GPIO From 035040e53cba24d7a15a055b605d50c568eb9e57 Mon Sep 17 00:00:00 2001 From: Nathan Vander Wilt Date: Tue, 27 May 2025 11:38:11 -0700 Subject: [PATCH 02/23] Update layer_by_layer.adoc to clarify PAC limitations proposed fix for #4226, re. https://github.com/embassy-rs/embassy/issues/4226#issuecomment-2908772500 --- docs/pages/layer_by_layer.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/layer_by_layer.adoc b/docs/pages/layer_by_layer.adoc index 7dba11b5e..0692ee4fa 100644 --- a/docs/pages/layer_by_layer.adoc +++ b/docs/pages/layer_by_layer.adoc @@ -8,7 +8,7 @@ The application we'll write is a simple 'push button, blink led' application, wh == PAC version -The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code. +The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does little to prevent you from configuring or coordinating those registers incorrectly. Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use. From 358a0cd464c0798c7d6a1178b5a633f5d90ad515 Mon Sep 17 00:00:00 2001 From: Adam Newell Date: Mon, 9 Jun 2025 17:15:24 -0400 Subject: [PATCH 03/23] Fix MPU region enablement in stack guard installation Updated the MPU region enablement logic in the `install_stack_guard` function to correctly set the region limit by using the stack bottom address plus 256 minus one, ensuring proper memory protection configuration. See Table 235. MPU_RLAR Register in RP2350 documentation See Section 4.5 MPU_RLAR in armv8m MPU documentation --- embassy-rp/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index f3c5a35bb..94b20bce3 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -567,7 +567,7 @@ unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { unsafe { core.MPU.ctrl.write(5); // enable mpu with background default map core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address - core.MPU.rlar.write(1); // enable region + core.MPU.rlar.write(((stack_bottom as usize + 255) as u32) | 1); } Ok(()) } From 56d76aeb7bccfebf07f0ad5ad9da8d9d36b0865d Mon Sep 17 00:00:00 2001 From: Jakob Date: Wed, 11 Jun 2025 09:08:55 +0200 Subject: [PATCH 04/23] Remove incorrect addition of 1 to get_max_duty --- embassy-stm32/src/timer/complementary_pwm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 8eec6c0c7..d51b5a8fc 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -121,7 +121,7 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { /// /// This value depends on the configured frequency and the timer's clock rate from RCC. pub fn get_max_duty(&self) -> u16 { - self.inner.get_max_compare_value() as u16 + 1 + self.inner.get_max_compare_value() as u16 } /// Set the duty for a given channel. From 0ee77f50aacdc39aabae1881304a7c744adc24c5 Mon Sep 17 00:00:00 2001 From: Jakob Date: Wed, 11 Jun 2025 09:24:00 +0200 Subject: [PATCH 05/23] Add separate case for center aligned mode --- embassy-stm32/src/timer/complementary_pwm.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index d51b5a8fc..1a840650a 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -121,7 +121,11 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { /// /// This value depends on the configured frequency and the timer's clock rate from RCC. pub fn get_max_duty(&self) -> u16 { - self.inner.get_max_compare_value() as u16 + if self.inner.get_counting_mode().is_center_aligned() { + self.inner.get_max_compare_value() as u16 + } else { + self.inner.get_max_compare_value() as u16 + 1 + } } /// Set the duty for a given channel. From 66296f673b05f3e34a85f85f30a23a98a863b3e3 Mon Sep 17 00:00:00 2001 From: Jakob Date: Wed, 11 Jun 2025 11:34:37 +0200 Subject: [PATCH 06/23] Enable autoreload_preload for complementary PWM --- embassy-stm32/src/timer/complementary_pwm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 8eec6c0c7..7807bb1bf 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -88,6 +88,7 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); this.inner.set_output_compare_preload(channel, true); }); + this.inner.set_autoreload_preload(true); this } From 09967b71f509bdebaf23ab11e3c362e447722240 Mon Sep 17 00:00:00 2001 From: Jakob Date: Wed, 11 Jun 2025 11:48:39 +0200 Subject: [PATCH 07/23] Also update the AdvancedInstace4Channel version --- embassy-stm32/src/timer/complementary_pwm.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 1a840650a..fa6164bdd 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -175,7 +175,11 @@ impl<'d, T: AdvancedInstance4Channel> embedded_hal_02::Pwm for ComplementaryPwm< } fn get_max_duty(&self) -> Self::Duty { - self.inner.get_max_compare_value() as u16 + 1 + if self.inner.get_counting_mode().is_center_aligned() { + self.inner.get_max_compare_value() as u16 + } else { + self.inner.get_max_compare_value() as u16 + 1 + } } fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { From f2266242043c0933e6d39e922400b21d726c9be3 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Wed, 11 Jun 2025 19:37:37 -0700 Subject: [PATCH 08/23] Add extra methods for the low-power interrupt timer. --- embassy-stm32/src/lptim/timer/mod.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/embassy-stm32/src/lptim/timer/mod.rs b/embassy-stm32/src/lptim/timer/mod.rs index a629be62b..f6abd4a74 100644 --- a/embassy-stm32/src/lptim/timer/mod.rs +++ b/embassy-stm32/src/lptim/timer/mod.rs @@ -82,6 +82,31 @@ impl<'d, T: Instance> Timer<'d, T> { pub fn get_max_compare_value(&self) -> u16 { T::regs().arr().read().arr() } + + /// Enable the timer interrupt. + pub fn enable_interrupt(&self) { + T::regs().dier().modify(|w| w.set_arrmie(true)); + } + + /// Disable the timer interrupt. + pub fn disable_interrupt(&self) { + T::regs().dier().modify(|w| w.set_arrmie(false)); + } + + /// Check if the timer interrupt is enabled. + pub fn is_interrupt_enabled(&self) -> bool { + T::regs().dier().read().arrmie() + } + + /// Check if the timer interrupt is pending. + pub fn is_interrupt_pending(&self) -> bool { + T::regs().isr().read().arrm() + } + + /// Clear the timer interrupt. + pub fn clear_interrupt(&self) { + T::regs().icr().write(|w| w.set_arrmcf(true)); + } } #[cfg(any(lptim_v2a, lptim_v2b))] From 4301016f155fd26a3934fb9e7bd2920107a32910 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Thu, 12 Jun 2025 04:11:49 +0000 Subject: [PATCH 09/23] Move the methods to the cfg gated impls to handle register renaming. --- embassy-stm32/src/lptim/timer/mod.rs | 75 ++++++++++++++++++---------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/embassy-stm32/src/lptim/timer/mod.rs b/embassy-stm32/src/lptim/timer/mod.rs index f6abd4a74..648da5940 100644 --- a/embassy-stm32/src/lptim/timer/mod.rs +++ b/embassy-stm32/src/lptim/timer/mod.rs @@ -82,31 +82,6 @@ impl<'d, T: Instance> Timer<'d, T> { pub fn get_max_compare_value(&self) -> u16 { T::regs().arr().read().arr() } - - /// Enable the timer interrupt. - pub fn enable_interrupt(&self) { - T::regs().dier().modify(|w| w.set_arrmie(true)); - } - - /// Disable the timer interrupt. - pub fn disable_interrupt(&self) { - T::regs().dier().modify(|w| w.set_arrmie(false)); - } - - /// Check if the timer interrupt is enabled. - pub fn is_interrupt_enabled(&self) -> bool { - T::regs().dier().read().arrmie() - } - - /// Check if the timer interrupt is pending. - pub fn is_interrupt_pending(&self) -> bool { - T::regs().isr().read().arrm() - } - - /// Clear the timer interrupt. - pub fn clear_interrupt(&self) { - T::regs().icr().write(|w| w.set_arrmcf(true)); - } } #[cfg(any(lptim_v2a, lptim_v2b))] @@ -140,6 +115,31 @@ impl<'d, T: Instance> Timer<'d, T> { .ccmr(0) .modify(|w| w.set_ccsel(channel.index(), direction.into())); } + + /// Enable the timer interrupt. + pub fn enable_interrupt(&self) { + T::regs().dier().modify(|w| w.set_arrmie(true)); + } + + /// Disable the timer interrupt. + pub fn disable_interrupt(&self) { + T::regs().dier().modify(|w| w.set_arrmie(false)); + } + + /// Check if the timer interrupt is enabled. + pub fn is_interrupt_enabled(&self) -> bool { + T::regs().dier().read().arrmie() + } + + /// Check if the timer interrupt is pending. + pub fn is_interrupt_pending(&self) -> bool { + T::regs().isr().read().arrm() + } + + /// Clear the timer interrupt. + pub fn clear_interrupt(&self) { + T::regs().icr().write(|w| w.set_arrmcf(true)); + } } #[cfg(not(any(lptim_v2a, lptim_v2b)))] @@ -153,4 +153,29 @@ impl<'d, T: Instance> Timer<'d, T> { pub fn get_compare_value(&self) -> u16 { T::regs().cmp().read().cmp() } + + /// Enable the timer interrupt. + pub fn enable_interrupt(&self) { + T::regs().ier().modify(|w| w.set_arrmie(true)); + } + + /// Disable the timer interrupt. + pub fn disable_interrupt(&self) { + T::regs().ier().modify(|w| w.set_arrmie(false)); + } + + /// Check if the timer interrupt is enabled. + pub fn is_interrupt_enabled(&self) -> bool { + T::regs().ier().read().arrmie() + } + + /// Check if the timer interrupt is pending. + pub fn is_interrupt_pending(&self) -> bool { + T::regs().isr().read().arrm() + } + + /// Clear the timer interrupt. + pub fn clear_interrupt(&self) { + T::regs().icr().write(|w| w.set_arrmcf(true)); + } } From 7fe4547ecb8a2838b26e6e0a902fbef3a4d22e52 Mon Sep 17 00:00:00 2001 From: Frank Stevenson Date: Tue, 17 Jun 2025 14:27:37 +0200 Subject: [PATCH 10/23] U5: Apply auto-calibration on MSIK and calculate frequencies for detuned LSE input --- embassy-stm32/src/rcc/u5.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index 97eb2eb6d..7e5001499 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -5,7 +5,7 @@ pub use crate::pac::rcc::vals::{ Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, }; -use crate::pac::rcc::vals::{Hseext, Msirgsel, Pllmboost, Pllrge}; +use crate::pac::rcc::vals::{Hseext, Msipllsel, Msirgsel, Pllmboost, Pllrge}; #[cfg(all(peri_usb_otg_hs))] pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG}; use crate::pac::{FLASH, PWR, RCC}; @@ -131,7 +131,18 @@ pub(crate) unsafe fn init(config: Config) { PWR.vosr().modify(|w| w.set_vos(config.voltage_range)); while !PWR.vosr().read().vosrdy() {} - let msis = config.msis.map(|range| { + let lse_calibration_freq = match config.ls.lse { + Some(lse_config) => { + if lse_config.peripherals_clocked && (31_000..=34_000).contains(&lse_config.frequency.0) { + Some(lse_config.frequency) + } else { + None + } + } + _ => None, + }; + + let mut msis = config.msis.map(|range| { // Check MSI output per RM0456 § 11.4.10 match config.voltage_range { VoltageScale::RANGE4 => { @@ -184,8 +195,25 @@ pub(crate) unsafe fn init(config: Config) { RCC.cr().modify(|w| { w.set_msikon(true); }); + if lse_calibration_freq.is_some() { + // Enable the MSIK auto-calibration feature + RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIK)); + RCC.cr().modify(|w| w.set_msipllen(true)); + } while !RCC.cr().read().msikrdy() {} - msirange_to_hertz(range) + if let Some(freq) = lse_calibration_freq { + // Estimate frequency based on it being a fixed fractional multiple of LSE + let base = msirange_to_hertz(range).0; + let multiplier = (base + 8096) / 16384; + let msik_freq = Hertz(freq.0 * multiplier / 2); + if config.msis == config.msik { + // If MSIS and MSIK are the same range both will be auto calibrated to the same frequency + msis = Some(msik_freq) + } + msik_freq + } else { + msirange_to_hertz(range) + } }); let hsi = config.hsi.then(|| { From d750a8b6b99d7012a1aab21ab67ba530e7a7206b Mon Sep 17 00:00:00 2001 From: Frank Stevenson Date: Tue, 17 Jun 2025 15:46:32 +0200 Subject: [PATCH 11/23] Make more accurate table based MSI frequency calculation based on datasheet. --- embassy-stm32/src/rcc/u5.rs | 53 ++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index 7e5001499..e7fe50f33 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -202,10 +202,7 @@ pub(crate) unsafe fn init(config: Config) { } while !RCC.cr().read().msikrdy() {} if let Some(freq) = lse_calibration_freq { - // Estimate frequency based on it being a fixed fractional multiple of LSE - let base = msirange_to_hertz(range).0; - let multiplier = (base + 8096) / 16384; - let msik_freq = Hertz(freq.0 * multiplier / 2); + let msik_freq = calculate_calibrated_msi_frequency(range, freq); if config.msis == config.msik { // If MSIS and MSIK are the same range both will be auto calibrated to the same frequency msis = Some(msik_freq) @@ -542,3 +539,51 @@ fn init_pll(instance: PllInstance, config: Option, input: &PllInput, voltag PllOutput { p, q, r } } + +/// Fraction structure for MSI auto-calibration +/// Represents the multiplier as numerator/denominator that LSE frequency is multiplied by +#[derive(Debug, Clone, Copy)] +struct MsiFraction { + numerator: u32, + denominator: u32, +} + +impl MsiFraction { + const fn new(numerator: u32, denominator: u32) -> Self { + Self { numerator, denominator } + } + + /// Calculate the calibrated frequency given an LSE frequency + fn calculate_frequency(&self, lse_freq: Hertz) -> Hertz { + Hertz(lse_freq.0 * self.numerator / self.denominator) + } +} + +/// Get the calibration fraction for a given MSI range +/// Based on STM32U5 datasheet table for LSE = 32.768 kHz +fn get_msi_calibration_fraction(range: Msirange) -> MsiFraction { + match range { + Msirange::RANGE_48MHZ => MsiFraction::new(1465, 1), // Range 0: 48.005 MHz + Msirange::RANGE_24MHZ => MsiFraction::new(1465, 2), // Range 1: 24.003 MHz + Msirange::RANGE_16MHZ => MsiFraction::new(1465, 3), // Range 2: 16.002 MHz + Msirange::RANGE_12MHZ => MsiFraction::new(1465, 4), // Range 3: 12.001 MHz + Msirange::RANGE_4MHZ => MsiFraction::new(122, 1), // Range 4: 3.998 MHz + Msirange::RANGE_2MHZ => MsiFraction::new(61, 1), // Range 5: 1.999 MHz + Msirange::RANGE_1_33MHZ => MsiFraction::new(122, 3), // Range 6: 1.333 MHz + Msirange::RANGE_1MHZ => MsiFraction::new(61, 2), // Range 7: 0.999 MHz + Msirange::RANGE_3_072MHZ => MsiFraction::new(94, 1), // Range 8: 3.08 MHz + Msirange::RANGE_1_536MHZ => MsiFraction::new(47, 1), // Range 9: 1.54 MHz + Msirange::RANGE_1_024MHZ => MsiFraction::new(94, 3), // Range 10: 1.027 MHz + Msirange::RANGE_768KHZ => MsiFraction::new(47, 2), // Range 11: 0.77 MHz + Msirange::RANGE_400KHZ => MsiFraction::new(12, 1), // Range 12: 393 kHz + Msirange::RANGE_200KHZ => MsiFraction::new(6, 1), // Range 13: 196.6 kHz + Msirange::RANGE_133KHZ => MsiFraction::new(4, 1), // Range 14: 131 kHz + Msirange::RANGE_100KHZ => MsiFraction::new(3, 1), // Range 15: 98.3 kHz + } +} + +/// Calculate the calibrated MSI frequency for a given range and LSE frequency +fn calculate_calibrated_msi_frequency(range: Msirange, lse_freq: Hertz) -> Hertz { + let fraction = get_msi_calibration_fraction(range); + fraction.calculate_frequency(lse_freq) +} From cf9856255e1c2abf3ad2164ce669645f5da098c6 Mon Sep 17 00:00:00 2001 From: Frank Stevenson Date: Wed, 23 Jul 2025 10:08:41 +0200 Subject: [PATCH 12/23] Make MSI calibration configurabke. Refine detection and handling of shared clock sources between MSIS and MSIK --- embassy-stm32/src/rcc/u5.rs | 101 ++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index e7fe50f33..0c7dc8ecc 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -64,6 +64,28 @@ pub struct Pll { pub divr: Option, } +#[derive(Clone, Copy, PartialEq)] +pub enum MsiAutoCalibration { + /// MSI auto-calibration is disabled + Disabled, + /// MSIS is given priority for auto-calibration + MSIS, + /// MSIK is given priority for auto-calibration + MSIK, +} + +impl MsiAutoCalibration { + const fn default() -> Self { + MsiAutoCalibration::Disabled + } +} + +impl Default for MsiAutoCalibration { + fn default() -> Self { + Self::default() + } +} + #[derive(Clone, Copy)] pub struct Config { // base clock sources @@ -95,6 +117,7 @@ pub struct Config { /// Per-peripheral kernel clock selection muxes pub mux: super::mux::ClockMux, + pub auto_calibration: MsiAutoCalibration, } impl Config { @@ -116,6 +139,7 @@ impl Config { voltage_range: VoltageScale::RANGE1, ls: crate::rcc::LsConfig::new(), mux: super::mux::ClockMux::default(), + auto_calibration: MsiAutoCalibration::default(), } } } @@ -133,7 +157,8 @@ pub(crate) unsafe fn init(config: Config) { let lse_calibration_freq = match config.ls.lse { Some(lse_config) => { - if lse_config.peripherals_clocked && (31_000..=34_000).contains(&lse_config.frequency.0) { + // Allow +/- 5% tolerance for LSE frequency + if lse_config.peripherals_clocked && (31_100..=34_400).contains(&lse_config.frequency.0) { Some(lse_config.frequency) } else { None @@ -167,11 +192,19 @@ pub(crate) unsafe fn init(config: Config) { w.set_msipllen(false); w.set_msison(true); }); + let msis = if let (Some(freq), MsiAutoCalibration::MSIS) = (lse_calibration_freq, config.auto_calibration) { + // Enable the MSIS auto-calibration feature + RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIS)); + RCC.cr().modify(|w| w.set_msipllen(true)); + calculate_calibrated_msi_frequency(range, freq) + } else { + msirange_to_hertz(range) + }; while !RCC.cr().read().msisrdy() {} - msirange_to_hertz(range) + msis }); - let msik = config.msik.map(|range| { + let mut msik = config.msik.map(|range| { // Check MSI output per RM0456 § 11.4.10 match config.voltage_range { VoltageScale::RANGE4 => { @@ -195,24 +228,38 @@ pub(crate) unsafe fn init(config: Config) { RCC.cr().modify(|w| { w.set_msikon(true); }); - if lse_calibration_freq.is_some() { + let msik = if let (Some(freq), MsiAutoCalibration::MSIK) = (lse_calibration_freq, config.auto_calibration) { // Enable the MSIK auto-calibration feature RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIK)); RCC.cr().modify(|w| w.set_msipllen(true)); - } - while !RCC.cr().read().msikrdy() {} - if let Some(freq) = lse_calibration_freq { - let msik_freq = calculate_calibrated_msi_frequency(range, freq); - if config.msis == config.msik { - // If MSIS and MSIK are the same range both will be auto calibrated to the same frequency - msis = Some(msik_freq) - } - msik_freq + calculate_calibrated_msi_frequency(range, freq) } else { msirange_to_hertz(range) - } + }; + while !RCC.cr().read().msikrdy() {} + msik }); + // If both MSIS and MSIK are enabled, we need to check if they are using the same internal source. + if let Some(lse_freq) = lse_calibration_freq { + if let (Some(msis_range), Some(msik_range)) = (config.msis, config.msik) { + if (msis_range as u8 >> 2) == (msik_range as u8 >> 2) { + // Clock source is shared, both will be auto calibrated. + match config.auto_calibration { + MsiAutoCalibration::MSIS => { + // MSIS and MSIK are using the same clock source, recalibrate + msik = Some(calculate_calibrated_msi_frequency(msik_range, lse_freq)); + } + MsiAutoCalibration::MSIK => { + // MSIS and MSIK are using the same clock source, recalibrate + msis = Some(calculate_calibrated_msi_frequency(msis_range, lse_freq)); + } + _ => {} + } + } + } + } + let hsi = config.hsi.then(|| { RCC.cr().modify(|w| w.set_hsion(true)); while !RCC.cr().read().hsirdy() {} @@ -559,27 +606,13 @@ impl MsiFraction { } } -/// Get the calibration fraction for a given MSI range -/// Based on STM32U5 datasheet table for LSE = 32.768 kHz fn get_msi_calibration_fraction(range: Msirange) -> MsiFraction { - match range { - Msirange::RANGE_48MHZ => MsiFraction::new(1465, 1), // Range 0: 48.005 MHz - Msirange::RANGE_24MHZ => MsiFraction::new(1465, 2), // Range 1: 24.003 MHz - Msirange::RANGE_16MHZ => MsiFraction::new(1465, 3), // Range 2: 16.002 MHz - Msirange::RANGE_12MHZ => MsiFraction::new(1465, 4), // Range 3: 12.001 MHz - Msirange::RANGE_4MHZ => MsiFraction::new(122, 1), // Range 4: 3.998 MHz - Msirange::RANGE_2MHZ => MsiFraction::new(61, 1), // Range 5: 1.999 MHz - Msirange::RANGE_1_33MHZ => MsiFraction::new(122, 3), // Range 6: 1.333 MHz - Msirange::RANGE_1MHZ => MsiFraction::new(61, 2), // Range 7: 0.999 MHz - Msirange::RANGE_3_072MHZ => MsiFraction::new(94, 1), // Range 8: 3.08 MHz - Msirange::RANGE_1_536MHZ => MsiFraction::new(47, 1), // Range 9: 1.54 MHz - Msirange::RANGE_1_024MHZ => MsiFraction::new(94, 3), // Range 10: 1.027 MHz - Msirange::RANGE_768KHZ => MsiFraction::new(47, 2), // Range 11: 0.77 MHz - Msirange::RANGE_400KHZ => MsiFraction::new(12, 1), // Range 12: 393 kHz - Msirange::RANGE_200KHZ => MsiFraction::new(6, 1), // Range 13: 196.6 kHz - Msirange::RANGE_133KHZ => MsiFraction::new(4, 1), // Range 14: 131 kHz - Msirange::RANGE_100KHZ => MsiFraction::new(3, 1), // Range 15: 98.3 kHz - } + // Exploiting the MSIx internals to make calculations compact + let denominator = (range as u32 & 0x03) + 1; + // Base multipliers are deduced from Table 82: MSI oscillator characteristics in data sheet + let numerator = [1465, 122, 94, 12][(range as u32 >> 2) as usize]; + + MsiFraction::new(numerator, denominator) } /// Calculate the calibrated MSI frequency for a given range and LSE frequency From 4abacac2522b93a7f1d44c353b81f5f5054ed7cc Mon Sep 17 00:00:00 2001 From: clubby789 Date: Wed, 23 Jul 2025 15:20:25 +0100 Subject: [PATCH 13/23] stm32/wb: Add memory manager to GATT example --- examples/stm32wb/src/bin/gatt_server.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/stm32wb/src/bin/gatt_server.rs b/examples/stm32wb/src/bin/gatt_server.rs index 041dc0cf5..9864fa026 100644 --- a/examples/stm32wb/src/bin/gatt_server.rs +++ b/examples/stm32wb/src/bin/gatt_server.rs @@ -27,6 +27,7 @@ use embassy_stm32_wpan::hci::vendor::event::{self, AttributeHandle, VendorEvent} use embassy_stm32_wpan::hci::{BdAddr, Event}; use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; use embassy_stm32_wpan::sub::ble::Ble; +use embassy_stm32_wpan::sub::mm; use embassy_stm32_wpan::TlMbox; use {defmt_rtt as _, panic_probe as _}; @@ -38,7 +39,7 @@ bind_interrupts!(struct Irqs{ const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; #[embassy_executor::main] -async fn main(_spawner: Spawner) { +async fn main(spawner: Spawner) { /* How to make this work: @@ -70,6 +71,7 @@ async fn main(_spawner: Spawner) { let config = Config::default(); let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); let sys_event = mbox.sys_subsystem.read().await; info!("sys event: {}", sys_event.payload()); @@ -221,6 +223,11 @@ async fn main(_spawner: Spawner) { } } +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + fn get_bd_addr() -> BdAddr { let mut bytes = [0u8; 6]; From f819b0d63c4b670f2aef3e8e9b35b0745090052d Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:51:33 +0200 Subject: [PATCH 14/23] feat(embedded-hal)!: rely on v1.0 traits for `SpiBus` on `BlockingAsync` --- .../src/adapter/blocking_async.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy-embedded-hal/src/adapter/blocking_async.rs index bafc31583..bc965fbdd 100644 --- a/embassy-embedded-hal/src/adapter/blocking_async.rs +++ b/embassy-embedded-hal/src/adapter/blocking_async.rs @@ -63,16 +63,16 @@ where impl embedded_hal_async::spi::ErrorType for BlockingAsync where - E: embedded_hal_1::spi::Error, - T: blocking::spi::Transfer + blocking::spi::Write, + E: embedded_hal_async::spi::Error, + T: embedded_hal_1::spi::SpiBus, { type Error = E; } impl embedded_hal_async::spi::SpiBus for BlockingAsync where - E: embedded_hal_1::spi::Error + 'static, - T: blocking::spi::Transfer + blocking::spi::Write, + E: embedded_hal_async::spi::Error, + T: embedded_hal_1::spi::SpiBus, { async fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) @@ -84,21 +84,17 @@ where } async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { - self.wrapped.transfer(data)?; + self.wrapped.read(data)?; Ok(()) } async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - // Ensure we write the expected bytes - for i in 0..core::cmp::min(read.len(), write.len()) { - read[i] = write[i].clone(); - } - self.wrapped.transfer(read)?; + self.wrapped.transfer(read, write)?; Ok(()) } async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { - self.wrapped.transfer(data)?; + self.wrapped.transfer_in_place(data)?; Ok(()) } } From 00824a900c6babf9727708357527b2a2807aa673 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:08:54 +0200 Subject: [PATCH 15/23] feat(embedded-hal)!: rely on v1.0 traits for `I2c` on `BlockingAsync` --- embassy-embedded-hal/src/adapter/blocking_async.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy-embedded-hal/src/adapter/blocking_async.rs index bc965fbdd..3b6e0ec00 100644 --- a/embassy-embedded-hal/src/adapter/blocking_async.rs +++ b/embassy-embedded-hal/src/adapter/blocking_async.rs @@ -1,5 +1,3 @@ -use embedded_hal_02::blocking; - /// Wrapper that implements async traits using blocking implementations. /// /// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations. @@ -24,7 +22,7 @@ impl BlockingAsync { impl embedded_hal_1::i2c::ErrorType for BlockingAsync where E: embedded_hal_1::i2c::Error + 'static, - T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, + T: embedded_hal_1::i2c::I2c, { type Error = E; } @@ -32,7 +30,7 @@ where impl embedded_hal_async::i2c::I2c for BlockingAsync where E: embedded_hal_1::i2c::Error + 'static, - T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, + T: embedded_hal_1::i2c::I2c, { async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { self.wrapped.read(address, read) @@ -51,9 +49,7 @@ where address: u8, operations: &mut [embedded_hal_1::i2c::Operation<'_>], ) -> Result<(), Self::Error> { - let _ = address; - let _ = operations; - todo!() + self.wrapped.transaction(address, operations) } } From fd3cdfcf251225fb333870f0341ae9ce416f54ad Mon Sep 17 00:00:00 2001 From: Frank Stevenson Date: Thu, 24 Jul 2025 13:26:10 +0200 Subject: [PATCH 16/23] Introduce configration options for Pll fast modes. Ensure that the auto calibration is applied to an active clock. --- embassy-stm32/src/rcc/u5.rs | 54 +++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index 0c7dc8ecc..ae0ef73f4 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -5,7 +5,7 @@ pub use crate::pac::rcc::vals::{ Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, }; -use crate::pac::rcc::vals::{Hseext, Msipllsel, Msirgsel, Pllmboost, Pllrge}; +use crate::pac::rcc::vals::{Hseext, Msipllfast, Msipllsel, Msirgsel, Pllmboost, Pllrge}; #[cfg(all(peri_usb_otg_hs))] pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG}; use crate::pac::{FLASH, PWR, RCC}; @@ -72,12 +72,30 @@ pub enum MsiAutoCalibration { MSIS, /// MSIK is given priority for auto-calibration MSIK, + /// MSIS with fast mode (always on) + MsisFast, + /// MSIK with fast mode (always on) + MsikFast, } impl MsiAutoCalibration { const fn default() -> Self { MsiAutoCalibration::Disabled } + + fn base_mode(&self) -> Self { + match self { + MsiAutoCalibration::Disabled => MsiAutoCalibration::Disabled, + MsiAutoCalibration::MSIS => MsiAutoCalibration::MSIS, + MsiAutoCalibration::MSIK => MsiAutoCalibration::MSIK, + MsiAutoCalibration::MsisFast => MsiAutoCalibration::MSIS, + MsiAutoCalibration::MsikFast => MsiAutoCalibration::MSIK, + } + } + + fn is_fast(&self) -> bool { + matches!(self, MsiAutoCalibration::MsisFast | MsiAutoCalibration::MsikFast) + } } impl Default for MsiAutoCalibration { @@ -159,7 +177,23 @@ pub(crate) unsafe fn init(config: Config) { Some(lse_config) => { // Allow +/- 5% tolerance for LSE frequency if lse_config.peripherals_clocked && (31_100..=34_400).contains(&lse_config.frequency.0) { - Some(lse_config.frequency) + // Check that the calibration is applied to an active clock + match ( + config.auto_calibration.base_mode(), + config.msis.is_some(), + config.msik.is_some(), + ) { + (MsiAutoCalibration::MSIS, true, _) => { + // MSIS is active and using LSE for auto-calibration + Some(lse_config.frequency) + } + (MsiAutoCalibration::MSIK, _, true) => { + // MSIK is active and using LSE for auto-calibration + Some(lse_config.frequency) + } + // improper configuration, no LSE calibration + _ => None, + } } else { None } @@ -192,7 +226,9 @@ pub(crate) unsafe fn init(config: Config) { w.set_msipllen(false); w.set_msison(true); }); - let msis = if let (Some(freq), MsiAutoCalibration::MSIS) = (lse_calibration_freq, config.auto_calibration) { + let msis = if let (Some(freq), MsiAutoCalibration::MSIS) = + (lse_calibration_freq, config.auto_calibration.base_mode()) + { // Enable the MSIS auto-calibration feature RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIS)); RCC.cr().modify(|w| w.set_msipllen(true)); @@ -228,7 +264,9 @@ pub(crate) unsafe fn init(config: Config) { RCC.cr().modify(|w| { w.set_msikon(true); }); - let msik = if let (Some(freq), MsiAutoCalibration::MSIK) = (lse_calibration_freq, config.auto_calibration) { + let msik = if let (Some(freq), MsiAutoCalibration::MSIK) = + (lse_calibration_freq, config.auto_calibration.base_mode()) + { // Enable the MSIK auto-calibration feature RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIK)); RCC.cr().modify(|w| w.set_msipllen(true)); @@ -242,10 +280,16 @@ pub(crate) unsafe fn init(config: Config) { // If both MSIS and MSIK are enabled, we need to check if they are using the same internal source. if let Some(lse_freq) = lse_calibration_freq { + // Check if Fast mode should be used + if config.auto_calibration.is_fast() { + RCC.cr().modify(|w| { + w.set_msipllfast(Msipllfast::FAST); + }); + } if let (Some(msis_range), Some(msik_range)) = (config.msis, config.msik) { if (msis_range as u8 >> 2) == (msik_range as u8 >> 2) { // Clock source is shared, both will be auto calibrated. - match config.auto_calibration { + match config.auto_calibration.base_mode() { MsiAutoCalibration::MSIS => { // MSIS and MSIK are using the same clock source, recalibrate msik = Some(calculate_calibrated_msi_frequency(msik_range, lse_freq)); From 3394f3ab9d2c0640bbd0804d5fd28cc68153786d Mon Sep 17 00:00:00 2001 From: Frank Stevenson Date: Thu, 24 Jul 2025 13:51:35 +0200 Subject: [PATCH 17/23] Panic on improper auto-calibration configurations --- embassy-stm32/src/rcc/u5.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index ae0ef73f4..d12416a72 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -191,9 +191,8 @@ pub(crate) unsafe fn init(config: Config) { // MSIK is active and using LSE for auto-calibration Some(lse_config.frequency) } - // improper configuration, no LSE calibration - _ => None, - } + // improper configuration + _ => panic!("MSIx auto-calibration is enabled for a source that has not been configured.") } } else { None } From aa243e4d3e51719d32314ba47ea68674ecc6c95e Mon Sep 17 00:00:00 2001 From: Frank Stevenson Date: Thu, 24 Jul 2025 18:08:29 +0200 Subject: [PATCH 18/23] Improved error checks, and some cleanup --- embassy-stm32/src/rcc/u5.rs | 72 ++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index d12416a72..7a7ffc939 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -173,31 +173,39 @@ pub(crate) unsafe fn init(config: Config) { PWR.vosr().modify(|w| w.set_vos(config.voltage_range)); while !PWR.vosr().read().vosrdy() {} - let lse_calibration_freq = match config.ls.lse { - Some(lse_config) => { - // Allow +/- 5% tolerance for LSE frequency - if lse_config.peripherals_clocked && (31_100..=34_400).contains(&lse_config.frequency.0) { - // Check that the calibration is applied to an active clock - match ( - config.auto_calibration.base_mode(), - config.msis.is_some(), - config.msik.is_some(), - ) { - (MsiAutoCalibration::MSIS, true, _) => { - // MSIS is active and using LSE for auto-calibration - Some(lse_config.frequency) - } - (MsiAutoCalibration::MSIK, _, true) => { - // MSIK is active and using LSE for auto-calibration - Some(lse_config.frequency) - } - // improper configuration - _ => panic!("MSIx auto-calibration is enabled for a source that has not been configured.") } - } else { - None + let lse_calibration_freq = if config.auto_calibration != MsiAutoCalibration::Disabled { + // LSE must be configured and peripherals clocked for MSI auto-calibration + let lse_config = config + .ls + .lse + .clone() + .expect("LSE must be configured for MSI auto-calibration"); + assert!(lse_config.peripherals_clocked); + + // Expect less than +/- 5% deviation for LSE frequency + if (31_100..=34_400).contains(&lse_config.frequency.0) { + // Check that the calibration is applied to an active clock + match ( + config.auto_calibration.base_mode(), + config.msis.is_some(), + config.msik.is_some(), + ) { + (MsiAutoCalibration::MSIS, true, _) => { + // MSIS is active and using LSE for auto-calibration + Some(lse_config.frequency) + } + (MsiAutoCalibration::MSIK, _, true) => { + // MSIK is active and using LSE for auto-calibration + Some(lse_config.frequency) + } + // improper configuration + _ => panic!("MSIx auto-calibration is enabled for a source that has not been configured."), } + } else { + panic!("LSE frequency more than 5% off from 32.768 kHz, cannot use for MSI auto-calibration"); } - _ => None, + } else { + None }; let mut msis = config.msis.map(|range| { @@ -277,30 +285,28 @@ pub(crate) unsafe fn init(config: Config) { msik }); - // If both MSIS and MSIK are enabled, we need to check if they are using the same internal source. if let Some(lse_freq) = lse_calibration_freq { - // Check if Fast mode should be used - if config.auto_calibration.is_fast() { - RCC.cr().modify(|w| { - w.set_msipllfast(Msipllfast::FAST); - }); - } + // If both MSIS and MSIK are enabled, we need to check if they are using the same internal source. if let (Some(msis_range), Some(msik_range)) = (config.msis, config.msik) { if (msis_range as u8 >> 2) == (msik_range as u8 >> 2) { - // Clock source is shared, both will be auto calibrated. + // Clock source is shared, both will be auto calibrated, recalculate other frequency match config.auto_calibration.base_mode() { MsiAutoCalibration::MSIS => { - // MSIS and MSIK are using the same clock source, recalibrate msik = Some(calculate_calibrated_msi_frequency(msik_range, lse_freq)); } MsiAutoCalibration::MSIK => { - // MSIS and MSIK are using the same clock source, recalibrate msis = Some(calculate_calibrated_msi_frequency(msis_range, lse_freq)); } _ => {} } } } + // Check if Fast mode should be used + if config.auto_calibration.is_fast() { + RCC.cr().modify(|w| { + w.set_msipllfast(Msipllfast::FAST); + }); + } } let hsi = config.hsi.then(|| { From 0d1e34d0fcc1fb995f0da46eede063cfbd9c962e Mon Sep 17 00:00:00 2001 From: Frank Stevenson Date: Thu, 24 Jul 2025 21:17:30 +0200 Subject: [PATCH 19/23] Minor cleanup --- embassy-stm32/src/rcc/u5.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index 7a7ffc939..06895a99a 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -659,7 +659,7 @@ fn get_msi_calibration_fraction(range: Msirange) -> MsiFraction { // Exploiting the MSIx internals to make calculations compact let denominator = (range as u32 & 0x03) + 1; // Base multipliers are deduced from Table 82: MSI oscillator characteristics in data sheet - let numerator = [1465, 122, 94, 12][(range as u32 >> 2) as usize]; + let numerator = [1465, 122, 94, 12][range as usize >> 2]; MsiFraction::new(numerator, denominator) } From b49d809346bb420c7994c75fa0121f6d28870c05 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 24 Jul 2025 23:29:54 +0200 Subject: [PATCH 20/23] Add dedup to doc job. --- .github/ci/doc.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 90662af82..9162b37ae 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh @@ -1,5 +1,7 @@ #!/bin/bash ## on push branch=main +## priority -10 +## dedup dequeue set -euxo pipefail From 915513753aea689f73d1300acc069ac985be3a0b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 24 Jul 2025 23:30:36 +0200 Subject: [PATCH 21/23] Add dedup to book job. --- .github/ci/book.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ci/book.sh b/.github/ci/book.sh index 285cdc8fa..2466f53f5 100755 --- a/.github/ci/book.sh +++ b/.github/ci/book.sh @@ -1,5 +1,7 @@ #!/bin/bash ## on push branch=main +## priority -9 +## dedup dequeue set -euxo pipefail From 9863406346fdf5defcb8fe8de4bb5d122fa0b05f Mon Sep 17 00:00:00 2001 From: Knaifhogg Date: Wed, 18 Jun 2025 08:26:12 +0200 Subject: [PATCH 22/23] fix: stm32 i2c slave blocking r/w This fixes an issue where the slave interface would time out when the master goes from a short write to a read (e.g. when accessing memory registers) with a START signal between. The previous implementation would expect the full buffer length to be written before starting to listen to new commands. This also adds debug trace printing which helped during implemention and testing. Places error checking into a function inspired from a C implementation of HAL. --- embassy-stm32/Cargo.toml | 1 + embassy-stm32/src/i2c/v2.rs | 278 ++++++++++++++++++++++++++++-------- 2 files changed, 221 insertions(+), 58 deletions(-) diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 38254ee40..02e75733e 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -129,6 +129,7 @@ defmt = [ "embassy-net-driver/defmt", "embassy-time?/defmt", "embassy-usb-synopsys-otg/defmt", + "stm32-metapac/defmt" ] exti = [] diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 35dc91c86..e24cce5c6 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -36,11 +36,46 @@ impl Address { } } +enum ReceiveResult { + DataAvailable, + StopReceived, + NewStart, +} + +fn debug_print_interrupts(isr: stm32_metapac::i2c::regs::Isr) { + if isr.tcr() { + trace!("interrupt: tcr"); + } + if isr.tc() { + trace!("interrupt: tc"); + } + if isr.addr() { + trace!("interrupt: addr"); + } + if isr.stopf() { + trace!("interrupt: stopf"); + } + if isr.nackf() { + trace!("interrupt: nackf"); + } + if isr.berr() { + trace!("interrupt: berr"); + } + if isr.arlo() { + trace!("interrupt: arlo"); + } + if isr.ovr() { + trace!("interrupt: ovr"); + } +} + pub(crate) unsafe fn on_interrupt() { let regs = T::info().regs; let isr = regs.isr().read(); if isr.tcr() || isr.tc() || isr.addr() || isr.stopf() || isr.nackf() || isr.berr() || isr.arlo() || isr.ovr() { + debug_print_interrupts(isr); + T::state().waker.wake(); } @@ -193,49 +228,132 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { fn flush_txdr(&self) { if self.info.regs.isr().read().txis() { - self.info.regs.txdr().write(|w| w.set_txdata(0)); + trace!("Flush TXDATA with zeroes"); + self.info.regs.txdr().modify(|w| w.set_txdata(0)); } if !self.info.regs.isr().read().txe() { + trace!("Flush TXDR"); self.info.regs.isr().modify(|w| w.set_txe(true)) } } - fn wait_txe(&self, timeout: Timeout) -> Result<(), Error> { + fn error_occurred(&self, isr: &i2c::regs::Isr, timeout: Timeout) -> Result<(), Error> { + if isr.nackf() { + trace!("NACK triggered."); + self.info.regs.icr().modify(|reg| reg.set_nackcf(true)); + // NACK should be followed by STOP + if let Ok(()) = self.wait_stop(timeout) { + trace!("Got STOP after NACK, clearing flag."); + self.info.regs.icr().modify(|reg| reg.set_stopcf(true)); + } + self.flush_txdr(); + return Err(Error::Nack); + } else if isr.berr() { + trace!("BERR triggered."); + self.info.regs.icr().modify(|reg| reg.set_berrcf(true)); + self.flush_txdr(); + return Err(Error::Bus); + } else if isr.arlo() { + trace!("ARLO triggered."); + self.info.regs.icr().modify(|reg| reg.set_arlocf(true)); + self.flush_txdr(); + return Err(Error::Arbitration); + } else if isr.ovr() { + trace!("OVR triggered."); + self.info.regs.icr().modify(|reg| reg.set_ovrcf(true)); + return Err(Error::Overrun); + } + return Ok(()); + } + + fn wait_txis(&self, timeout: Timeout) -> Result<(), Error> { + let mut first_loop = true; + loop { let isr = self.info.regs.isr().read(); - if isr.txe() { + self.error_occurred(&isr, timeout)?; + if isr.txis() { + trace!("TXIS"); return Ok(()); - } else if isr.berr() { - self.info.regs.icr().write(|reg| reg.set_berrcf(true)); - return Err(Error::Bus); - } else if isr.arlo() { - self.info.regs.icr().write(|reg| reg.set_arlocf(true)); - return Err(Error::Arbitration); - } else if isr.nackf() { - self.info.regs.icr().write(|reg| reg.set_nackcf(true)); - self.flush_txdr(); - return Err(Error::Nack); } + { + if first_loop { + trace!("Waiting for TXIS..."); + first_loop = false; + } + } timeout.check()?; } } - fn wait_rxne(&self, timeout: Timeout) -> Result<(), Error> { + fn wait_stop_or_err(&self, timeout: Timeout) -> Result<(), Error> { loop { let isr = self.info.regs.isr().read(); - if isr.rxne() { + self.error_occurred(&isr, timeout)?; + if isr.stopf() { + trace!("STOP triggered."); + self.info.regs.icr().modify(|reg| reg.set_stopcf(true)); return Ok(()); - } else if isr.berr() { - self.info.regs.icr().write(|reg| reg.set_berrcf(true)); - return Err(Error::Bus); - } else if isr.arlo() { - self.info.regs.icr().write(|reg| reg.set_arlocf(true)); - return Err(Error::Arbitration); - } else if isr.nackf() { - self.info.regs.icr().write(|reg| reg.set_nackcf(true)); - self.flush_txdr(); - return Err(Error::Nack); + } + timeout.check()?; + } + } + fn wait_stop(&self, timeout: Timeout) -> Result<(), Error> { + loop { + let isr = self.info.regs.isr().read(); + if isr.stopf() { + trace!("STOP triggered."); + self.info.regs.icr().modify(|reg| reg.set_stopcf(true)); + return Ok(()); + } + timeout.check()?; + } + } + + fn wait_af(&self, timeout: Timeout) -> Result<(), Error> { + loop { + let isr = self.info.regs.isr().read(); + if isr.nackf() { + trace!("AF triggered."); + self.info.regs.icr().modify(|reg| reg.set_nackcf(true)); + return Ok(()); + } + timeout.check()?; + } + } + + fn wait_rxne(&self, timeout: Timeout) -> Result { + let mut first_loop = true; + + loop { + let isr = self.info.regs.isr().read(); + self.error_occurred(&isr, timeout)?; + if isr.stopf() { + trace!("STOP when waiting for RXNE."); + if self.info.regs.isr().read().rxne() { + trace!("Data received with STOP."); + return Ok(ReceiveResult::DataAvailable); + } + trace!("STOP triggered without data."); + return Ok(ReceiveResult::StopReceived); + } else if isr.rxne() { + trace!("RXNE."); + return Ok(ReceiveResult::DataAvailable); + } else if isr.addr() { + // Another addr event received, which means START was sent again + // which happens when accessing memory registers (common i2c interface design) + // e.g. master sends: START, write 1 byte (register index), START, read N bytes (until NACK) + // Possible to receive this flag at the same time as rxne, so check rxne first + trace!("START when waiting for RXNE. Ending receive loop."); + // Return without clearing ADDR so `listen` can catch it + return Ok(ReceiveResult::NewStart); + } + { + if first_loop { + trace!("Waiting for RXNE..."); + first_loop = false; + } } timeout.check()?; @@ -245,20 +363,10 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { fn wait_tc(&self, timeout: Timeout) -> Result<(), Error> { loop { let isr = self.info.regs.isr().read(); + self.error_occurred(&isr, timeout)?; if isr.tc() { return Ok(()); - } else if isr.berr() { - self.info.regs.icr().write(|reg| reg.set_berrcf(true)); - return Err(Error::Bus); - } else if isr.arlo() { - self.info.regs.icr().write(|reg| reg.set_arlocf(true)); - return Err(Error::Arbitration); - } else if isr.nackf() { - self.info.regs.icr().write(|reg| reg.set_nackcf(true)); - self.flush_txdr(); - return Err(Error::Nack); } - timeout.check()?; } } @@ -344,7 +452,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { // Wait until we are allowed to send data // (START has been ACKed or last byte when // through) - if let Err(err) = self.wait_txe(timeout) { + if let Err(err) = self.wait_txis(timeout) { if send_stop { self.master_stop(); } @@ -459,7 +567,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { // Wait until we are allowed to send data // (START has been ACKed or last byte when // through) - if let Err(err) = self.wait_txe(timeout) { + if let Err(err) = self.wait_txis(timeout) { self.master_stop(); return Err(err); } @@ -884,10 +992,11 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // clear the address flag, will stop the clock stretching. // this should only be done after the dma transfer has been set up. info.regs.icr().modify(|reg| reg.set_addrcf(true)); + trace!("ADDRCF cleared (ADDR interrupt enabled, clock stretching ended)"); } // A blocking read operation - fn slave_read_internal(&self, read: &mut [u8], timeout: Timeout) -> Result<(), Error> { + fn slave_read_internal(&self, read: &mut [u8], timeout: Timeout) -> Result { let completed_chunks = read.len() / 255; let total_chunks = if completed_chunks * 255 == read.len() { completed_chunks @@ -895,20 +1004,46 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { completed_chunks + 1 }; let last_chunk_idx = total_chunks.saturating_sub(1); + let total_len = read.len(); + let mut remaining_len = total_len; + for (number, chunk) in read.chunks_mut(255).enumerate() { - if number != 0 { + trace!( + "--- Slave RX transmission start - chunk: {}, expected (max) size: {}", + number, + chunk.len() + ); + if number == 0 { + Self::slave_start(self.info, chunk.len(), number != last_chunk_idx); + } else { Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?; } + let mut index = 0; + for byte in chunk { // Wait until we have received something - self.wait_rxne(timeout)?; - - *byte = self.info.regs.rxdr().read().rxdata(); + match self.wait_rxne(timeout) { + Ok(ReceiveResult::StopReceived) | Ok(ReceiveResult::NewStart) => { + trace!("--- Slave RX transmission end (early)"); + return Ok(total_len - remaining_len); // Return N bytes read + } + Ok(ReceiveResult::DataAvailable) => { + *byte = self.info.regs.rxdr().read().rxdata(); + remaining_len = remaining_len.saturating_sub(1); + { + trace!("Slave RX data {}: {:#04x}", index, byte); + index = index + 1; + } + } + Err(e) => return Err(e), + }; } } + self.wait_stop_or_err(timeout)?; - Ok(()) + trace!("--- Slave RX transmission end"); + Ok(total_len - remaining_len) // Return N bytes read } // A blocking write operation @@ -922,19 +1057,36 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { let last_chunk_idx = total_chunks.saturating_sub(1); for (number, chunk) in write.chunks(255).enumerate() { - if number != 0 { + trace!( + "--- Slave TX transmission start - chunk: {}, size: {}", + number, + chunk.len() + ); + if number == 0 { + Self::slave_start(self.info, chunk.len(), number != last_chunk_idx); + } else { Self::reload(self.info, chunk.len(), number != last_chunk_idx, timeout)?; } + let mut index = 0; + for byte in chunk { // Wait until we are allowed to send data - // (START has been ACKed or last byte when - // through) - self.wait_txe(timeout)?; + // (START has been ACKed or last byte when through) + self.wait_txis(timeout)?; + { + trace!("Slave TX data {}: {:#04x}", index, byte); + index = index + 1; + } self.info.regs.txdr().write(|w| w.set_txdata(*byte)); } } + self.wait_af(timeout)?; + self.flush_txdr(); + self.wait_stop_or_err(timeout)?; + + trace!("--- Slave TX transmission end"); Ok(()) } @@ -945,6 +1097,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { let state = self.state; self.info.regs.cr1().modify(|reg| { reg.set_addrie(true); + trace!("Enable ADDRIE"); }); poll_fn(|cx| { @@ -953,17 +1106,24 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { if !isr.addr() { Poll::Pending } else { + trace!("ADDR triggered (address match)"); // we do not clear the address flag here as it will be cleared by the dma read/write // if we clear it here the clock stretching will stop and the master will read in data before the slave is ready to send it match isr.dir() { - i2c::vals::Dir::WRITE => Poll::Ready(Ok(SlaveCommand { - kind: SlaveCommandKind::Write, - address: self.determine_matched_address()?, - })), - i2c::vals::Dir::READ => Poll::Ready(Ok(SlaveCommand { - kind: SlaveCommandKind::Read, - address: self.determine_matched_address()?, - })), + i2c::vals::Dir::WRITE => { + trace!("DIR: write"); + Poll::Ready(Ok(SlaveCommand { + kind: SlaveCommandKind::Write, + address: self.determine_matched_address()?, + })) + } + i2c::vals::Dir::READ => { + trace!("DIR: read"); + Poll::Ready(Ok(SlaveCommand { + kind: SlaveCommandKind::Read, + address: self.determine_matched_address()?, + })) + } } } }) @@ -971,7 +1131,9 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { } /// Respond to a write command. - pub fn blocking_respond_to_write(&self, read: &mut [u8]) -> Result<(), Error> { + /// + /// Returns total number of bytes received. + pub fn blocking_respond_to_write(&self, read: &mut [u8]) -> Result { let timeout = self.timeout(); self.slave_read_internal(read, timeout) } @@ -1025,7 +1187,7 @@ impl<'d> I2c<'d, Async, MultiMaster> { w.set_rxdmaen(false); w.set_stopie(false); w.set_tcie(false); - }) + }); }); let total_received = poll_fn(|cx| { From 3329089412e3ab367893eb975d611be49c8f5c5d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 25 Jul 2025 00:11:42 +0200 Subject: [PATCH 23/23] embassy-embedded-hal: make time feature non-default default features considered harmful. --- ci.sh | 2 ++ embassy-embedded-hal/Cargo.toml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ci.sh b/ci.sh index 94f70aae8..f4db1da03 100755 --- a/ci.sh +++ b/ci.sh @@ -40,6 +40,8 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabihf --features arch-cortex-ar,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32 \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \ + --- build --release --manifest-path embassy-embedded-hal/Cargo.toml --target thumbv7em-none-eabi \ + --- build --release --manifest-path embassy-embedded-hal/Cargo.toml --target thumbv7em-none-eabi --features time \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,mock-driver \ --- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target thumbv6m-none-eabi \ diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index aab6e0f1e..8277aa291 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -19,7 +19,6 @@ target = "x86_64-unknown-linux-gnu" [features] time = ["dep:embassy-time"] -default = ["time"] [dependencies] embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal" }