From f64dd8228b7b8a570546ffa9b522ae85145cfdef Mon Sep 17 00:00:00 2001 From: seth Date: Mon, 24 Jun 2024 17:09:43 -0700 Subject: [PATCH] new PR, taking Dirbao's advice to make the DMA impl in a separate struct that consumes Adc to make RingBufferedAdc. Handling overrun similar to RingBufferedUart --- embassy-stm32/build.rs | 6 +- embassy-stm32/src/adc/mod.rs | 2 + embassy-stm32/src/adc/ringbuffered_v2.rs | 376 +++++++++++++++++++++++ embassy-stm32/src/adc/v2.rs | 3 + examples/stm32f4/src/bin/adc_dma.rs | 59 ++++ 5 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 embassy-stm32/src/adc/ringbuffered_v2.rs create mode 100644 examples/stm32f4/src/bin/adc_dma.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 6aedcc228..24e2226a2 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -764,7 +764,7 @@ fn main() { #[rustfmt::skip] let signals: HashMap<_, _> = [ - // (kind, signal) => trait + // (kind, signal) => trait (("ucpd", "CC1"), quote!(crate::ucpd::Cc1Pin)), (("ucpd", "CC2"), quote!(crate::ucpd::Cc2Pin)), (("usart", "TX"), quote!(crate::usart::TxPin)), @@ -1178,6 +1178,10 @@ fn main() { let signals: HashMap<_, _> = [ // (kind, signal) => trait + (("adc", "ADC"), quote!(crate::adc::RxDma)), + (("adc", "ADC1"), quote!(crate::adc::RxDma)), + (("adc", "ADC2"), quote!(crate::adc::RxDma)), + (("adc", "ADC3"), quote!(crate::adc::RxDma)), (("ucpd", "RX"), quote!(crate::ucpd::RxDma)), (("ucpd", "TX"), quote!(crate::ucpd::TxDma)), (("usart", "RX"), quote!(crate::usart::RxDma)), diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 0c22a7dae..7a7d7cd8e 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -28,6 +28,8 @@ pub use crate::pac::adc::vals::Res as Resolution; pub use crate::pac::adc::vals::SampleTime; use crate::peripherals; +dma_trait!(RxDma, Instance); + /// Analog to Digital driver. pub struct Adc<'d, T: Instance> { #[allow(unused)] diff --git a/embassy-stm32/src/adc/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs new file mode 100644 index 000000000..e11311b9a --- /dev/null +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -0,0 +1,376 @@ +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; + +use embassy_hal_internal::{into_ref, Peripheral}; +use stm32_metapac::adc::vals::SampleTime; + +use crate::adc::{Adc, AdcChannel, Instance, RxDma}; +use crate::dma::ringbuffer::OverrunError; +use crate::dma::{Priority, ReadableRingBuffer, TransferOptions}; +use crate::pac::adc::vals; +use crate::rcc; + +fn clear_interrupt_flags(r: crate::pac::adc::Adc) { + r.sr().modify(|regs| { + regs.set_eoc(false); + regs.set_ovr(false); + }); +} + +#[derive(PartialOrd, PartialEq, Debug, Clone, Copy)] +pub enum Sequence { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + Ten, + Eleven, + Twelve, + Thirteen, + Fourteen, + Fifteen, + Sixteen, +} + +impl From for u8 { + fn from(s: Sequence) -> u8 { + match s { + Sequence::One => 0, + Sequence::Two => 1, + Sequence::Three => 2, + Sequence::Four => 3, + Sequence::Five => 4, + Sequence::Six => 5, + Sequence::Seven => 6, + Sequence::Eight => 7, + Sequence::Nine => 8, + Sequence::Ten => 9, + Sequence::Eleven => 10, + Sequence::Twelve => 11, + Sequence::Thirteen => 12, + Sequence::Fourteen => 13, + Sequence::Fifteen => 14, + Sequence::Sixteen => 15, + } + } +} + +impl From for Sequence { + fn from(val: u8) -> Self { + match val { + 0 => Sequence::One, + 1 => Sequence::Two, + 2 => Sequence::Three, + 3 => Sequence::Four, + 4 => Sequence::Five, + 5 => Sequence::Six, + 6 => Sequence::Seven, + 7 => Sequence::Eight, + 8 => Sequence::Nine, + 9 => Sequence::Ten, + 10 => Sequence::Eleven, + 11 => Sequence::Twelve, + 12 => Sequence::Thirteen, + 13 => Sequence::Fourteen, + 14 => Sequence::Fifteen, + 15 => Sequence::Sixteen, + _ => panic!("Invalid sequence number"), + } + } +} + +pub struct RingBufferedAdc<'d, T: Instance> { + _phantom: PhantomData, + ring_buf: ReadableRingBuffer<'d, u16>, +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn into_ring_buffered( + self, + dma: impl Peripheral

> + 'd, + dma_buf: &'d mut [u16], + ) -> RingBufferedAdc<'d, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + into_ref!(dma); + + let opts: crate::dma::TransferOptions = TransferOptions { + half_transfer_ir: true, + priority: Priority::VeryHigh, + ..Default::default() + }; + + // Safety: we forget the struct before this function returns. + let rx_src = T::regs().dr().as_ptr() as *mut u16; + let request = dma.request(); + + let ring_buf = unsafe { ReadableRingBuffer::new(dma, request, rx_src, dma_buf, opts) }; + + // Don't disable the clock + mem::forget(self); + + RingBufferedAdc { + _phantom: PhantomData, + ring_buf, + } + } +} + +impl<'d, T: Instance> RingBufferedAdc<'d, T> { + fn is_on() -> bool { + T::regs().cr2().read().adon() + } + + fn stop_adc() { + T::regs().cr2().modify(|reg| { + reg.set_adon(false); + }); + } + + fn start_adc() { + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + }); + } + + /// Sets the channel sample time + /// + /// ## SAFETY: + /// - ADON == 0 i.e ADC must not be enabled when this is called. + unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + if ch <= 9 { + T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time)); + } else { + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); + } + } + + fn set_channels_sample_time(&mut self, ch: &[u8], sample_time: SampleTime) { + let ch_iter = ch.iter(); + for idx in ch_iter { + unsafe { + Self::set_channel_sample_time(*idx, sample_time); + } + } + } + + pub fn set_sample_sequence( + &mut self, + sequence: Sequence, + channel: &mut impl AdcChannel, + sample_time: SampleTime, + ) { + let was_on = Self::is_on(); + if !was_on { + Self::start_adc(); + } + + //Check the sequence is long enough + T::regs().sqr1().modify(|r| { + let prev: Sequence = r.l().into(); + if prev < sequence { + let new_l: Sequence = sequence; + trace!("Setting sequence length from {:?} to {:?}", prev as u8, new_l as u8); + r.set_l(sequence.into()) + } else { + r.set_l(prev.into()) + } + }); + + //Set this GPIO as an analog input. + channel.setup(); + + //Set the channel in the right sequence field. + match sequence { + Sequence::One => T::regs().sqr3().modify(|w| w.set_sq(0, channel.channel())), + Sequence::Two => T::regs().sqr3().modify(|w| w.set_sq(1, channel.channel())), + Sequence::Three => T::regs().sqr3().modify(|w| w.set_sq(2, channel.channel())), + Sequence::Four => T::regs().sqr3().modify(|w| w.set_sq(3, channel.channel())), + Sequence::Five => T::regs().sqr3().modify(|w| w.set_sq(4, channel.channel())), + Sequence::Six => T::regs().sqr3().modify(|w| w.set_sq(5, channel.channel())), + Sequence::Seven => T::regs().sqr2().modify(|w| w.set_sq(6, channel.channel())), + Sequence::Eight => T::regs().sqr2().modify(|w| w.set_sq(7, channel.channel())), + Sequence::Nine => T::regs().sqr2().modify(|w| w.set_sq(8, channel.channel())), + Sequence::Ten => T::regs().sqr2().modify(|w| w.set_sq(9, channel.channel())), + Sequence::Eleven => T::regs().sqr2().modify(|w| w.set_sq(10, channel.channel())), + Sequence::Twelve => T::regs().sqr2().modify(|w| w.set_sq(11, channel.channel())), + Sequence::Thirteen => T::regs().sqr1().modify(|w| w.set_sq(12, channel.channel())), + Sequence::Fourteen => T::regs().sqr1().modify(|w| w.set_sq(13, channel.channel())), + Sequence::Fifteen => T::regs().sqr1().modify(|w| w.set_sq(14, channel.channel())), + Sequence::Sixteen => T::regs().sqr1().modify(|w| w.set_sq(15, channel.channel())), + }; + + if !was_on { + Self::stop_adc(); + } + + self.set_channels_sample_time(&[channel.channel()], sample_time); + + Self::start_adc(); + } + + pub fn start(&mut self) -> Result<(), OverrunError> { + self.ring_buf.clear(); + + self.setup_adc(); + + Ok(()) + } + + fn stop(&mut self, err: OverrunError) -> Result { + self.teardown_adc(); + Err(err) + } + + pub fn teardown_adc(&mut self) { + // Stop the DMA transfer + self.ring_buf.request_stop(); + + let r = T::regs(); + + // Stop ADC + r.cr2().modify(|reg| { + // Stop ADC + reg.set_swstart(false); + // Stop DMA + reg.set_dma(false); + }); + + r.cr1().modify(|w| { + // Disable interrupt for end of conversion + w.set_eocie(false); + // Disable interrupt for overrun + w.set_ovrie(false); + }); + + clear_interrupt_flags(r); + + compiler_fence(Ordering::SeqCst); + } + + fn setup_adc(&mut self) { + compiler_fence(Ordering::SeqCst); + + self.ring_buf.start(); + + let r = T::regs(); + + //Enable ADC + let was_on = Self::is_on(); + if !was_on { + r.cr2().modify(|reg| { + reg.set_adon(false); + reg.set_swstart(false); + }); + } + + // Clear all interrupts + r.sr().modify(|regs| { + regs.set_eoc(false); + regs.set_ovr(false); + regs.set_strt(false); + }); + + r.cr1().modify(|w| { + // Enable interrupt for end of conversion + w.set_eocie(true); + // Enable interrupt for overrun + w.set_ovrie(true); + // Scanning converisons of multiple channels + w.set_scan(true); + // Continuous conversion mode + w.set_discen(false); + }); + + r.cr2().modify(|w| { + // Enable DMA mode + w.set_dma(true); + // Enable continuous conversions + w.set_cont(vals::Cont::CONTINUOUS); + // DMA requests are issues as long as DMA=1 and data are converted. + w.set_dds(vals::Dds::CONTINUOUS); + // EOC flag is set at the end of each conversion. + w.set_eocs(vals::Eocs::EACHCONVERSION); + }); + + //Being ADC conversions + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + reg.set_swstart(true); + }); + + super::blocking_delay_us(3); + } + + /// 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()` 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()` or by re-calling `read()`. + pub async fn read(&mut self, buf: &mut [u16; N]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr2().read().dma() { + self.start()?; + } + + // Clear overrun flag if set. + if r.sr().read().ovr() { + r.sr().modify(|regs| { + regs.set_ovr(false); + // regs.set_eoc(false); + }); + // return self.stop(OverrunError); + } + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + return self.stop(OverrunError); + } + } + } + } + + pub async fn read_exact(&mut self, buf: &mut [u16; N]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + if !r.cr2().read().dma() { + self.start()?; + } + + // Clear overrun flag if set. + if r.sr().read().ovr() { + r.sr().modify(|regs| { + regs.set_ovr(false); + // regs.set_eoc(false); + }); + // return self.stop(OverrunError); + } + match self.ring_buf.read_exact(buf).await { + Ok(len) => Ok(len), + Err(_) => self.stop(OverrunError), + } + } +} + +impl Drop for RingBufferedAdc<'_, T> { + fn drop(&mut self) { + self.teardown_adc(); + rcc::disable::(); + } +} diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index e3175dff5..ddeb7ea79 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -6,6 +6,9 @@ use crate::peripherals::ADC1; use crate::time::Hertz; use crate::{rcc, Peripheral}; +mod ringbuffered_v2; +pub use ringbuffered_v2::{RingBufferedAdc, Sequence}; + /// Default VREF voltage used for sample conversion to millivolts. pub const VREF_DEFAULT_MV: u32 = 3300; /// VREF voltage used for factory calibration of VREFINTCAL register. diff --git a/examples/stm32f4/src/bin/adc_dma.rs b/examples/stm32f4/src/bin/adc_dma.rs new file mode 100644 index 000000000..a2611bb6f --- /dev/null +++ b/examples/stm32f4/src/bin/adc_dma.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] +use cortex_m::singleton; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::RingBufferedAdc; +use embassy_stm32::adc::{Adc, SampleTime, Sequence}; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + const ADC_BUF_SIZE: usize = 1024; + let mut p = embassy_stm32::init(Default::default()); + + let adc_data: &mut [u16; ADC_BUF_SIZE] = singleton!(ADCDAT : [u16; ADC_BUF_SIZE] = [0u16; ADC_BUF_SIZE]).unwrap(); + + let adc = Adc::new(p.ADC1); + + let mut adc: RingBufferedAdc = adc.into_ring_buffered(p.DMA2_CH0, adc_data); + + adc.set_sample_sequence(Sequence::One, &mut p.PA0, SampleTime::CYCLES112); + adc.set_sample_sequence(Sequence::Two, &mut p.PA2, SampleTime::CYCLES112); + adc.set_sample_sequence(Sequence::Three, &mut p.PA1, SampleTime::CYCLES112); + adc.set_sample_sequence(Sequence::Four, &mut p.PA3, SampleTime::CYCLES112); + + // Note that overrun is a big consideration in this implementation. Whatever task is running the adc.read() calls absolutely must circle back around + // to the adc.read() call before the DMA buffer is wrapped around > 1 time. At this point, the overrun is so significant that the context of + // what channel is at what index is lost. The buffer must be cleared and reset. This *is* handled here, but allowing this to happen will cause + // a reduction of performance as each time the buffer is reset, the adc & dma buffer must be restarted. + + // An interrupt executor with a higher priority than other tasks may be a good approach here, allowing this task to wake and read the buffer most + // frequently. + let mut tic = Instant::now(); + let mut buffer1: [u16; 256] = [0u16; 256]; + let _ = adc.start(); + loop { + match adc.read(&mut buffer1).await { + Ok(_data) => { + let toc = Instant::now(); + info!( + "\n adc1: {} dt = {}, n = {}", + buffer1[0..16], + (toc - tic).as_micros(), + _data + ); + tic = toc; + } + Err(e) => { + warn!("Error: {:?}", e); + buffer1 = [0u16; 256]; + let _ = adc.start(); + continue; + } + } + + Timer::after_micros(300).await; + } +}