diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 8ed4dbd65..fe2fb64f2 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - feat: stm32/adc/v3: allow DMA reads to loop through enable channels - fix: Fix XSPI not disabling alternate bytes when they were previously enabled - fix: Fix stm32h7rs init when using external flash via XSPI +- feat: Add Adc::new_with_clock() to configure analog clock - feat: Add GPDMA linked-list + ringbuffer support ([#3923](https://github.com/embassy-rs/embassy/pull/3923)) ## 0.3.0 - 2025-08-12 diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index dc1faa4d1..77f24c87f 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -107,8 +107,50 @@ pub enum Averaging { Samples128, Samples256, } + +cfg_if! { if #[cfg(adc_g0)] { + +/// Synchronous PCLK prescaler +/// * ADC_CFGR2:CKMODE in STM32WL5x +#[repr(u8)] +pub enum CkModePclk { + DIV1 = 3, + DIV2 = 1, + DIV4 = 2, +} + +/// Asynchronous ADCCLK prescaler +/// * ADC_CCR:PRESC in STM32WL5x +#[repr(u8)] +pub enum Presc { + DIV1, + DIV2, + DIV4, + DIV6, + DIV8, + DIV10, + DIV12, + DIV16, + DIV32, + DIV64, + DIV128, + DIV256, +} + +/// The analog clock is either the synchronous prescaled PCLK or +/// the asynchronous prescaled ADCCLK configured by the RCC mux. +/// The data sheet states the maximum analog clock frequency - +/// for STM32WL55CC it is 36 MHz. +pub enum Clock { + Sync { div: CkModePclk }, + Async { div: Presc }, +} + +}} + impl<'d, T: Instance> Adc<'d, T> { - pub fn new(adc: Peri<'d, T>) -> Self { + /// Enable the voltage regulator + fn init_regulator() { rcc::enable_and_reset::(); T::regs().cr().modify(|reg| { #[cfg(not(any(adc_g0, adc_u0)))] @@ -117,13 +159,17 @@ impl<'d, T: Instance> Adc<'d, T> { }); // If this is false then each ADC_CHSELR bit enables an input channel. + // This is the reset value, so has no effect. #[cfg(any(adc_g0, adc_u0))] T::regs().cfgr1().modify(|reg| { reg.set_chselrmod(false); }); blocking_delay_us(20); + } + /// Calibrate to remove conversion offset + fn init_calibrate() { T::regs().cr().modify(|reg| { reg.set_adcal(true); }); @@ -133,6 +179,45 @@ impl<'d, T: Instance> Adc<'d, T> { } blocking_delay_us(1); + } + + /// Initialize the ADC leaving any analog clock at reset value. + /// For G0 and WL, this is the async clock without prescaler. + pub fn new(adc: Peri<'d, T>) -> Self { + Self::init_regulator(); + Self::init_calibrate(); + Self { + adc, + sample_time: SampleTime::from_bits(0), + } + } + + #[cfg(adc_g0)] + /// Initialize ADC with explicit clock for the analog ADC + pub fn new_with_clock(adc: Peri<'d, T>, clock: Clock) -> Self { + Self::init_regulator(); + + #[cfg(any(stm32wl5x))] + { + // Reset value 0 is actually _No clock selected_ in the STM32WL5x reference manual + let async_clock_available = pac::RCC.ccipr().read().adcsel() != pac::rcc::vals::Adcsel::_RESERVED_0; + match clock { + Clock::Async { div: _ } => { + assert!(async_clock_available); + } + Clock::Sync { div: _ } => { + if async_clock_available { + warn!("Not using configured ADC clock"); + } + } + } + } + match clock { + Clock::Async { div } => T::regs().ccr().modify(|reg| reg.set_presc(div as u8)), + Clock::Sync { div } => T::regs().cfgr2().modify(|reg| reg.set_ckmode(div as u8)), + } + + Self::init_calibrate(); Self { adc, diff --git a/examples/stm32g0/src/bin/adc.rs b/examples/stm32g0/src/bin/adc.rs index 6c7f3b48a..7d8653ef2 100644 --- a/examples/stm32g0/src/bin/adc.rs +++ b/examples/stm32g0/src/bin/adc.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::adc::{Adc, Clock, Presc, SampleTime}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -12,7 +12,7 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let mut adc = Adc::new(p.ADC1); + let mut adc = Adc::new_with_clock(p.ADC1, Clock::Async { div: Presc::DIV1 }); adc.set_sample_time(SampleTime::CYCLES79_5); let mut pin = p.PA1; diff --git a/examples/stm32g0/src/bin/adc_dma.rs b/examples/stm32g0/src/bin/adc_dma.rs index d7515933c..8266a6d83 100644 --- a/examples/stm32g0/src/bin/adc_dma.rs +++ b/examples/stm32g0/src/bin/adc_dma.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; +use embassy_stm32::adc::{Adc, AdcChannel as _, Clock, Presc, SampleTime}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let mut adc = Adc::new(p.ADC1); + let mut adc = Adc::new_with_clock(p.ADC1, Clock::Async { div: Presc::DIV1 }); let mut dma = p.DMA1_CH1; let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); diff --git a/examples/stm32g0/src/bin/adc_oversampling.rs b/examples/stm32g0/src/bin/adc_oversampling.rs index 9c5dd872a..bc49fac83 100644 --- a/examples/stm32g0/src/bin/adc_oversampling.rs +++ b/examples/stm32g0/src/bin/adc_oversampling.rs @@ -7,7 +7,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::adc::{Adc, Clock, Presc, SampleTime}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -16,7 +16,7 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Adc oversample test"); - let mut adc = Adc::new(p.ADC1); + let mut adc = Adc::new_with_clock(p.ADC1, Clock::Async { div: Presc::DIV1 }); adc.set_sample_time(SampleTime::CYCLES1_5); let mut pin = p.PA1; diff --git a/examples/stm32wl/src/bin/adc.rs b/examples/stm32wl/src/bin/adc.rs new file mode 100644 index 000000000..118f02ae1 --- /dev/null +++ b/examples/stm32wl/src/bin/adc.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, CkModePclk, Clock, SampleTime}; +use embassy_stm32::SharedData; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); + info!("Hello World!"); + + let mut adc = Adc::new_with_clock(p.ADC1, Clock::Sync { div: CkModePclk::DIV1 }); + adc.set_sample_time(SampleTime::CYCLES79_5); + let mut pin = p.PB2; + + let mut vrefint = adc.enable_vrefint(); + let vrefint_sample = adc.blocking_read(&mut vrefint); + let convert_to_millivolts = |sample| { + // From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf + // 6.3.3 Embedded internal reference voltage + const VREFINT_MV: u32 = 1212; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.blocking_read(&mut pin); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +}