From 5a1be543ac8838963a6597dda2ddf3918397e39b Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Fri, 13 Jun 2025 12:59:56 +0000 Subject: stm32/adc/v3: allow DMA reads to loop through enabled channels Tested on an STM32H533RE. Documentation of other chips has been reviewed, but not extensively. --- embassy-stm32/CHANGELOG.md | 1 + embassy-stm32/src/adc/v3.rs | 7 +-- examples/stm32h5/src/bin/adc_dma.rs | 94 +++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 examples/stm32h5/src/bin/adc_dma.rs diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 301c20055..da8bce0e2 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: Fix vrefbuf building with log feature - fix: Fix performing a hash after performing a hmac - chore: Updated stm32-metapac and stm32-data dependencies +- feat: stm32/adc/v3: allow DMA reads to loop through enable channels ## 0.3.0 - 2025-08-12 diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index a2e42fe52..dc1faa4d1 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -296,7 +296,8 @@ impl<'d, T: Instance> Adc<'d, T> { /// Read one or multiple ADC channels using DMA. /// - /// `sequence` iterator and `readings` must have the same length. + /// `readings` must have a length that is a multiple of the length of the + /// `sequence` iterator. /// /// Note: The order of values in `readings` is defined by the pin ADC /// channel number and not the pin order in `sequence`. @@ -330,8 +331,8 @@ impl<'d, T: Instance> Adc<'d, T> { ) { assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); assert!( - sequence.len() == readings.len(), - "Sequence length must be equal to readings length" + readings.len() % sequence.len() == 0, + "Readings length must be a multiple of sequence length" ); assert!( sequence.len() <= 16, diff --git a/examples/stm32h5/src/bin/adc_dma.rs b/examples/stm32h5/src/bin/adc_dma.rs new file mode 100644 index 000000000..20073e22f --- /dev/null +++ b/examples/stm32h5/src/bin/adc_dma.rs @@ -0,0 +1,94 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{self, Adc, AdcChannel, RxDma, SampleTime}; +use embassy_stm32::peripherals::{ADC1, ADC2, GPDMA1_CH0, GPDMA1_CH1, PA0, PA1, PA2, PA3}; +use embassy_stm32::{Config, Peri}; +use embassy_time::Instant; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV4), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: None, + divq: None, + divr: Some(PllDiv::DIV4), // 100mhz + }); + config.rcc.sys = Sysclk::PLL1_P; // 200 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV1; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcdacsel = mux::Adcdacsel::PLL2_R; + } + let p = embassy_stm32::init(config); + + spawner.must_spawn(adc1_task(p.ADC1, p.GPDMA1_CH0, p.PA0, p.PA2)); + spawner.must_spawn(adc2_task(p.ADC2, p.GPDMA1_CH1, p.PA1, p.PA3)); +} + +#[embassy_executor::task] +async fn adc1_task( + adc: Peri<'static, ADC1>, + dma: Peri<'static, GPDMA1_CH0>, + pin1: Peri<'static, PA0>, + pin2: Peri<'static, PA2>, +) { + adc_task(adc, dma, pin1, pin2).await; +} + +#[embassy_executor::task] +async fn adc2_task( + adc: Peri<'static, ADC2>, + dma: Peri<'static, GPDMA1_CH1>, + pin1: Peri<'static, PA1>, + pin2: Peri<'static, PA3>, +) { + adc_task(adc, dma, pin1, pin2).await; +} + +async fn adc_task<'a, T: adc::Instance>( + adc: Peri<'a, T>, + mut dma: Peri<'a, impl RxDma>, + pin1: impl AdcChannel, + pin2: impl AdcChannel, +) { + let mut adc = Adc::new(adc); + let mut pin1 = pin1.degrade_adc(); + let mut pin2 = pin2.degrade_adc(); + + let mut tic = Instant::now(); + let mut buffer = [0u16; 512]; + loop { + // This is not a true continuous read as there is downtime between each + // call to `Adc::read` where the ADC is sitting idle. + adc.read( + dma.reborrow(), + [(&mut pin1, SampleTime::CYCLES2_5), (&mut pin2, SampleTime::CYCLES2_5)].into_iter(), + &mut buffer, + ) + .await; + let toc = Instant::now(); + info!("\n adc1: {} dt = {}", buffer[0..16], (toc - tic).as_micros()); + tic = toc; + } +} -- cgit