diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 99808c2334..2f27aa8b45 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - chore: Updated stm32-metapac and stm32-data dependencies - feat: stm32/adc/v3: allow DMA reads to loop through enable channels - fix: Fix XSPI not disabling alternate bytes when they were previously enabled +- feat: stm32/adc/v3: added support for Continuous DMA configuration - 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)) diff --git a/embassy-stm32/src/adc/ringbuffered_v3.rs b/embassy-stm32/src/adc/ringbuffered_v3.rs new file mode 100644 index 0000000000..0aee309e3c --- /dev/null +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs @@ -0,0 +1,180 @@ +use core::marker::PhantomData; +use core::sync::atomic::{Ordering, compiler_fence}; + +use embassy_hal_internal::Peri; + +use crate::adc::{Instance, RxDma}; +use crate::dma::{ReadableRingBuffer, TransferOptions}; +use crate::rcc; + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OverrunError; + +pub struct RingBufferedAdc<'d, T: Instance> { + pub _phantom: PhantomData, + pub ring_buf: ReadableRingBuffer<'d, u16>, +} + +impl<'d, T: Instance> RingBufferedAdc<'d, T> { + pub(crate) fn new(dma: Peri<'d, impl RxDma>, dma_buf: &'d mut [u16]) -> Self { + //dma side setup + let opts = TransferOptions { + half_transfer_ir: true, + circular: true, + ..Default::default() + }; + + // Safety: we forget the struct before this function returns. + let request = dma.request(); + + let ring_buf = + unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; + + Self { + _phantom: PhantomData, + ring_buf, + } + } + + #[inline] + fn start_continuous_sampling(&mut self) { + // Start adc conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + self.ring_buf.start(); + } + + #[inline] + pub fn stop_continuous_sampling(&mut self) { + // Stop adc conversion + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(true); + }); + while T::regs().cr().read().adstart() {} + } + } + pub fn disable_adc(&mut self) { + self.stop_continuous_sampling(); + self.ring_buf.clear(); + self.ring_buf.request_pause(); + } + + pub fn teardown_adc(&mut self) { + self.disable_adc(); + + //disable dma control + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_dmaen(false); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_dmaen(false); + }); + + //TODO: do we need to cleanup the DMA request here? + + compiler_fence(Ordering::SeqCst); + } + + /// Reads measurements from the DMA ring buffer. + /// + /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. + /// The length of the `measurements` array should be exactly half of the DMA buffer length. Because interrupts are only generated if half or full DMA transfer completes. + /// + /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. + /// There will be many sequences worth of measurements in this array because it only returns if at least half of the DMA buffer is filled. + /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. + /// + /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks + /// Otherwise, you'll see constant Overrun errors occuring, this means that you're sampling too quickly for the task to handle, and you may need to increase the buffer size. + /// Example: + /// ```rust,ignore + /// const DMA_BUF_LEN: usize = 120; + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.degrade_adc(); + /// let mut adc_pin1 = p.PA1.degrade_adc(); + /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; + /// + /// let mut ring_buffered_adc: RingBufferedAdc = adc.into_ring_buffered( + /// p.DMA2_CH0, + /// adc_dma_buf, [ + /// (&mut *adc_pin0, SampleTime::CYCLES160_5), + /// (&mut *adc_pin1, SampleTime::CYCLES160_5), + /// ].into_iter()); + /// + /// + /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; + /// loop { + /// match ring_buffered_adc.read(&mut measurements).await { + /// Ok(_) => { + /// defmt::info!("adc1: {}", measurements); + /// } + /// Err(e) => { + /// defmt::warn!("Error: {:?}", e); + /// } + /// } + /// } + /// ``` + /// + /// + /// [`teardown_adc`]: #method.teardown_adc + /// [`start_continuous_sampling`]: #method.start_continuous_sampling + pub async fn read(&mut self, measurements: &mut [u16]) -> Result { + assert_eq!( + self.ring_buf.capacity() / 2, + measurements.len(), + "Buffer size must be half the size of the ring buffer" + ); + + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr().read().adstart() { + self.start_continuous_sampling(); + } + + self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until the some + /// bytes are available (at least one byte and at most half the buffer size) + /// + /// Background receive is started if `start_continuous_sampling()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start_continuous_sampling()` or by re-calling `blocking_read()`. + pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr().read().adstart() { + self.start_continuous_sampling(); + } + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + self.stop_continuous_sampling(); + return Err(OverrunError); + } + } + } + } +} + +impl Drop for RingBufferedAdc<'_, T> { + fn drop(&mut self) { + self.teardown_adc(); + rcc::disable::(); + } +} diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 47632263b0..d9a3ce21da 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -12,6 +12,13 @@ pub use pac::adc::vals::{Ovsr, Ovss, Presc}; use super::{ Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, blocking_delay_us, }; + +#[cfg(any(adc_v3, adc_g0, adc_u0))] +mod ringbuffered_v3; + +#[cfg(any(adc_v3, adc_g0, adc_u0))] +use ringbuffered_v3::RingBufferedAdc; + use crate::dma::Transfer; use crate::{Peri, pac, rcc}; @@ -559,6 +566,137 @@ impl<'d, T: Instance> Adc<'d, T> { }); } + /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. + /// + /// The `dma_buf` should be large enough to prevent DMA buffer overrun. + /// The length of the `dma_buf` should be a multiple of the ADC channel count. + /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. + /// + /// `read` method is used to read out measurements from the DMA ring buffer, and its buffer should be exactly half of the `dma_buf` length. + /// It is critical to call `read` frequently to prevent DMA buffer overrun. + /// + /// [`read`]: #method.read + #[cfg(any(adc_v3, adc_g0, adc_u0))] + pub fn into_ring_buffered<'a>( + &mut self, + dma: Peri<'a, impl RxDma>, + dma_buf: &'a mut [u16], + sequence: impl ExactSizeIterator, SampleTime)>, + ) -> RingBufferedAdc<'a, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + // reset conversions and enable the adc + Self::cancel_conversions(); + self.enable(); + + //adc side setup + + // Set sequence length + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + #[cfg(adc_g0)] + { + let mut sample_times = Vec::::new(); + + T::regs().chselr().write(|chselr| { + T::regs().smpr().write(|smpr| { + for (channel, sample_time) in sequence { + chselr.set_chsel(channel.channel.into(), true); + if let Some(i) = sample_times.iter().position(|&t| t == sample_time) { + smpr.set_smpsel(channel.channel.into(), (i as u8).into()); + } else { + smpr.set_sample_time(sample_times.len(), sample_time); + if let Err(_) = sample_times.push(sample_time) { + panic!( + "Implementation is limited to {} unique sample times among all channels.", + SAMPLE_TIMES_CAPACITY + ); + } + } + } + }) + }); + } + #[cfg(not(adc_g0))] + { + #[cfg(adc_u0)] + let mut channel_mask = 0; + + // Configure channels and ranks + for (_i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + + // Each channel is sampled according to sequence + #[cfg(not(any(adc_g0, adc_u0)))] + match _i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(_i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(_i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(_i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(_i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + + #[cfg(adc_u0)] + { + channel_mask |= 1 << channel.channel(); + } + } + + // On G0 and U0 enabled channels are sampled from 0 to last channel. + // It is possible to add up to 8 sequences if CHSELRMOD = 1. + // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. + #[cfg(adc_u0)] + T::regs().chselr().modify(|reg| { + reg.set_chsel(channel_mask); + }); + } + // Set continuous mode with Circular dma. + // Clear overrun flag before starting transfer. + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + + #[cfg(not(any(adc_g0, adc_u0)))] + T::regs().cfgr().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::CIRCULAR); + reg.set_dmaen(true); + }); + #[cfg(any(adc_g0, adc_u0))] + T::regs().cfgr1().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::CIRCULAR); + reg.set_dmaen(true); + }); + + RingBufferedAdc::new(dma, dma_buf) + } + #[cfg(not(adc_g0))] fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { // RM0492, RM0481, etc. diff --git a/examples/stm32l4/src/bin/adc_dma.rs b/examples/stm32l4/src/bin/adc_dma.rs new file mode 100644 index 0000000000..7a9200edd8 --- /dev/null +++ b/examples/stm32l4/src/bin/adc_dma.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_stm32::adc::{Adc, AdcChannel, SampleTime}; +use {defmt_rtt as _, panic_probe as _}; + +const DMA_BUF_LEN: usize = 512; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.mux.adcsel = mux::Adcsel::SYS; + } + let p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + let mut adc_pin0 = p.PA0.degrade_adc(); + let mut adc_pin1 = p.PA1.degrade_adc(); + let mut adc_dma_buf = [0u16; DMA_BUF_LEN]; + let mut measurements = [0u16; DMA_BUF_LEN / 2]; + let mut ring_buffered_adc = adc.into_ring_buffered( + p.DMA1_CH1, + &mut adc_dma_buf, + [ + (&mut adc_pin0, SampleTime::CYCLES640_5), + (&mut adc_pin1, SampleTime::CYCLES640_5), + ] + .into_iter(), + ); + + info!("starting measurement loop"); + loop { + match ring_buffered_adc.read(&mut measurements).await { + Ok(_) => { + //note: originally there was a print here showing all the samples, + //but even that takes too much time and would cause adc overruns + info!("adc1 first 10 samples: {}", measurements[0..10]); + } + Err(e) => { + warn!("Error: {:?}", e); + } + } + } +}