diff options
| author | xoviat <[email protected]> | 2025-11-03 01:02:50 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-03 01:02:50 +0000 |
| commit | 7e917fd8fa51170a6946beb51d4791bb9406ac61 (patch) | |
| tree | 72ee5098bc42863bf83118d03822dd521f36281d | |
| parent | 3ff0b2c5971e72a93bd37d7f8fecbc8e64421360 (diff) | |
| parent | 82158642e202cffbc527d672cdc33930bef5c78d (diff) | |
Merge pull request #4583 from maor1993/main
stm32/adc/v3: added support for DMA based adc sampling
| -rw-r--r-- | embassy-stm32/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/adc/ringbuffered_v3.rs | 180 | ||||
| -rw-r--r-- | embassy-stm32/src/adc/v3.rs | 138 | ||||
| -rw-r--r-- | examples/stm32l4/src/bin/adc_dma.rs | 51 |
4 files changed, 370 insertions, 0 deletions
diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 4e55e1034..4d0559c0d 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md | |||
| @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 51 | - chore: Updated stm32-metapac and stm32-data dependencies | 51 | - chore: Updated stm32-metapac and stm32-data dependencies |
| 52 | - feat: stm32/adc/v3: allow DMA reads to loop through enable channels | 52 | - feat: stm32/adc/v3: allow DMA reads to loop through enable channels |
| 53 | - fix: Fix XSPI not disabling alternate bytes when they were previously enabled | 53 | - fix: Fix XSPI not disabling alternate bytes when they were previously enabled |
| 54 | - feat: stm32/adc/v3: added support for Continuous DMA configuration | ||
| 54 | - fix: Fix stm32h7rs init when using external flash via XSPI | 55 | - fix: Fix stm32h7rs init when using external flash via XSPI |
| 55 | - feat: Add Adc::new_with_clock() to configure analog clock | 56 | - feat: Add Adc::new_with_clock() to configure analog clock |
| 56 | - feat: Add GPDMA linked-list + ringbuffer support ([#3923](https://github.com/embassy-rs/embassy/pull/3923)) | 57 | - 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 000000000..0aee309e3 --- /dev/null +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs | |||
| @@ -0,0 +1,180 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use core::sync::atomic::{Ordering, compiler_fence}; | ||
| 3 | |||
| 4 | use embassy_hal_internal::Peri; | ||
| 5 | |||
| 6 | use crate::adc::{Instance, RxDma}; | ||
| 7 | use crate::dma::{ReadableRingBuffer, TransferOptions}; | ||
| 8 | use crate::rcc; | ||
| 9 | |||
| 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 11 | pub struct OverrunError; | ||
| 12 | |||
| 13 | pub struct RingBufferedAdc<'d, T: Instance> { | ||
| 14 | pub _phantom: PhantomData<T>, | ||
| 15 | pub ring_buf: ReadableRingBuffer<'d, u16>, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl<'d, T: Instance> RingBufferedAdc<'d, T> { | ||
| 19 | pub(crate) fn new(dma: Peri<'d, impl RxDma<T>>, dma_buf: &'d mut [u16]) -> Self { | ||
| 20 | //dma side setup | ||
| 21 | let opts = TransferOptions { | ||
| 22 | half_transfer_ir: true, | ||
| 23 | circular: true, | ||
| 24 | ..Default::default() | ||
| 25 | }; | ||
| 26 | |||
| 27 | // Safety: we forget the struct before this function returns. | ||
| 28 | let request = dma.request(); | ||
| 29 | |||
| 30 | let ring_buf = | ||
| 31 | unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; | ||
| 32 | |||
| 33 | Self { | ||
| 34 | _phantom: PhantomData, | ||
| 35 | ring_buf, | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | #[inline] | ||
| 40 | fn start_continuous_sampling(&mut self) { | ||
| 41 | // Start adc conversion | ||
| 42 | T::regs().cr().modify(|reg| { | ||
| 43 | reg.set_adstart(true); | ||
| 44 | }); | ||
| 45 | self.ring_buf.start(); | ||
| 46 | } | ||
| 47 | |||
| 48 | #[inline] | ||
| 49 | pub fn stop_continuous_sampling(&mut self) { | ||
| 50 | // Stop adc conversion | ||
| 51 | if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { | ||
| 52 | T::regs().cr().modify(|reg| { | ||
| 53 | reg.set_adstp(true); | ||
| 54 | }); | ||
| 55 | while T::regs().cr().read().adstart() {} | ||
| 56 | } | ||
| 57 | } | ||
| 58 | pub fn disable_adc(&mut self) { | ||
| 59 | self.stop_continuous_sampling(); | ||
| 60 | self.ring_buf.clear(); | ||
| 61 | self.ring_buf.request_pause(); | ||
| 62 | } | ||
| 63 | |||
| 64 | pub fn teardown_adc(&mut self) { | ||
| 65 | self.disable_adc(); | ||
| 66 | |||
| 67 | //disable dma control | ||
| 68 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 69 | T::regs().cfgr().modify(|reg| { | ||
| 70 | reg.set_dmaen(false); | ||
| 71 | }); | ||
| 72 | #[cfg(any(adc_g0, adc_u0))] | ||
| 73 | T::regs().cfgr1().modify(|reg| { | ||
| 74 | reg.set_dmaen(false); | ||
| 75 | }); | ||
| 76 | |||
| 77 | //TODO: do we need to cleanup the DMA request here? | ||
| 78 | |||
| 79 | compiler_fence(Ordering::SeqCst); | ||
| 80 | } | ||
| 81 | |||
| 82 | /// Reads measurements from the DMA ring buffer. | ||
| 83 | /// | ||
| 84 | /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. | ||
| 85 | /// 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. | ||
| 86 | /// | ||
| 87 | /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. | ||
| 88 | /// 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. | ||
| 89 | /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. | ||
| 90 | /// | ||
| 91 | /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks | ||
| 92 | /// 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. | ||
| 93 | /// Example: | ||
| 94 | /// ```rust,ignore | ||
| 95 | /// const DMA_BUF_LEN: usize = 120; | ||
| 96 | /// use embassy_stm32::adc::{Adc, AdcChannel} | ||
| 97 | /// | ||
| 98 | /// let mut adc = Adc::new(p.ADC1); | ||
| 99 | /// let mut adc_pin0 = p.PA0.degrade_adc(); | ||
| 100 | /// let mut adc_pin1 = p.PA1.degrade_adc(); | ||
| 101 | /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; | ||
| 102 | /// | ||
| 103 | /// let mut ring_buffered_adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered( | ||
| 104 | /// p.DMA2_CH0, | ||
| 105 | /// adc_dma_buf, [ | ||
| 106 | /// (&mut *adc_pin0, SampleTime::CYCLES160_5), | ||
| 107 | /// (&mut *adc_pin1, SampleTime::CYCLES160_5), | ||
| 108 | /// ].into_iter()); | ||
| 109 | /// | ||
| 110 | /// | ||
| 111 | /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; | ||
| 112 | /// loop { | ||
| 113 | /// match ring_buffered_adc.read(&mut measurements).await { | ||
| 114 | /// Ok(_) => { | ||
| 115 | /// defmt::info!("adc1: {}", measurements); | ||
| 116 | /// } | ||
| 117 | /// Err(e) => { | ||
| 118 | /// defmt::warn!("Error: {:?}", e); | ||
| 119 | /// } | ||
| 120 | /// } | ||
| 121 | /// } | ||
| 122 | /// ``` | ||
| 123 | /// | ||
| 124 | /// | ||
| 125 | /// [`teardown_adc`]: #method.teardown_adc | ||
| 126 | /// [`start_continuous_sampling`]: #method.start_continuous_sampling | ||
| 127 | pub async fn read(&mut self, measurements: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 128 | assert_eq!( | ||
| 129 | self.ring_buf.capacity() / 2, | ||
| 130 | measurements.len(), | ||
| 131 | "Buffer size must be half the size of the ring buffer" | ||
| 132 | ); | ||
| 133 | |||
| 134 | let r = T::regs(); | ||
| 135 | |||
| 136 | // Start background receive if it was not already started | ||
| 137 | if !r.cr().read().adstart() { | ||
| 138 | self.start_continuous_sampling(); | ||
| 139 | } | ||
| 140 | |||
| 141 | self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) | ||
| 142 | } | ||
| 143 | |||
| 144 | /// Read bytes that are readily available in the ring buffer. | ||
| 145 | /// If no bytes are currently available in the buffer the call waits until the some | ||
| 146 | /// bytes are available (at least one byte and at most half the buffer size) | ||
| 147 | /// | ||
| 148 | /// Background receive is started if `start_continuous_sampling()` has not been previously called. | ||
| 149 | /// | ||
| 150 | /// Receive in the background is terminated if an error is returned. | ||
| 151 | /// It must then manually be started again by calling `start_continuous_sampling()` or by re-calling `blocking_read()`. | ||
| 152 | pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 153 | let r = T::regs(); | ||
| 154 | |||
| 155 | // Start background receive if it was not already started | ||
| 156 | if !r.cr().read().adstart() { | ||
| 157 | self.start_continuous_sampling(); | ||
| 158 | } | ||
| 159 | |||
| 160 | loop { | ||
| 161 | match self.ring_buf.read(buf) { | ||
| 162 | Ok((0, _)) => {} | ||
| 163 | Ok((len, _)) => { | ||
| 164 | return Ok(len); | ||
| 165 | } | ||
| 166 | Err(_) => { | ||
| 167 | self.stop_continuous_sampling(); | ||
| 168 | return Err(OverrunError); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | impl<T: Instance> Drop for RingBufferedAdc<'_, T> { | ||
| 176 | fn drop(&mut self) { | ||
| 177 | self.teardown_adc(); | ||
| 178 | rcc::disable::<T>(); | ||
| 179 | } | ||
| 180 | } | ||
diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 47632263b..d9a3ce21d 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}; | |||
| 12 | use super::{ | 12 | use super::{ |
| 13 | Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, blocking_delay_us, | 13 | Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, blocking_delay_us, |
| 14 | }; | 14 | }; |
| 15 | |||
| 16 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 17 | mod ringbuffered_v3; | ||
| 18 | |||
| 19 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 20 | use ringbuffered_v3::RingBufferedAdc; | ||
| 21 | |||
| 15 | use crate::dma::Transfer; | 22 | use crate::dma::Transfer; |
| 16 | use crate::{Peri, pac, rcc}; | 23 | use crate::{Peri, pac, rcc}; |
| 17 | 24 | ||
| @@ -559,6 +566,137 @@ impl<'d, T: Instance> Adc<'d, T> { | |||
| 559 | }); | 566 | }); |
| 560 | } | 567 | } |
| 561 | 568 | ||
| 569 | /// Configures the ADC to use a DMA ring buffer for continuous data acquisition. | ||
| 570 | /// | ||
| 571 | /// The `dma_buf` should be large enough to prevent DMA buffer overrun. | ||
| 572 | /// The length of the `dma_buf` should be a multiple of the ADC channel count. | ||
| 573 | /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements. | ||
| 574 | /// | ||
| 575 | /// `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. | ||
| 576 | /// It is critical to call `read` frequently to prevent DMA buffer overrun. | ||
| 577 | /// | ||
| 578 | /// [`read`]: #method.read | ||
| 579 | #[cfg(any(adc_v3, adc_g0, adc_u0))] | ||
| 580 | pub fn into_ring_buffered<'a>( | ||
| 581 | &mut self, | ||
| 582 | dma: Peri<'a, impl RxDma<T>>, | ||
| 583 | dma_buf: &'a mut [u16], | ||
| 584 | sequence: impl ExactSizeIterator<Item = (&'a mut AnyAdcChannel<T>, SampleTime)>, | ||
| 585 | ) -> RingBufferedAdc<'a, T> { | ||
| 586 | assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); | ||
| 587 | assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); | ||
| 588 | assert!( | ||
| 589 | sequence.len() <= 16, | ||
| 590 | "Asynchronous read sequence cannot be more than 16 in length" | ||
| 591 | ); | ||
| 592 | // reset conversions and enable the adc | ||
| 593 | Self::cancel_conversions(); | ||
| 594 | self.enable(); | ||
| 595 | |||
| 596 | //adc side setup | ||
| 597 | |||
| 598 | // Set sequence length | ||
| 599 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 600 | T::regs().sqr1().modify(|w| { | ||
| 601 | w.set_l(sequence.len() as u8 - 1); | ||
| 602 | }); | ||
| 603 | |||
| 604 | #[cfg(adc_g0)] | ||
| 605 | { | ||
| 606 | let mut sample_times = Vec::<SampleTime, SAMPLE_TIMES_CAPACITY>::new(); | ||
| 607 | |||
| 608 | T::regs().chselr().write(|chselr| { | ||
| 609 | T::regs().smpr().write(|smpr| { | ||
| 610 | for (channel, sample_time) in sequence { | ||
| 611 | chselr.set_chsel(channel.channel.into(), true); | ||
| 612 | if let Some(i) = sample_times.iter().position(|&t| t == sample_time) { | ||
| 613 | smpr.set_smpsel(channel.channel.into(), (i as u8).into()); | ||
| 614 | } else { | ||
| 615 | smpr.set_sample_time(sample_times.len(), sample_time); | ||
| 616 | if let Err(_) = sample_times.push(sample_time) { | ||
| 617 | panic!( | ||
| 618 | "Implementation is limited to {} unique sample times among all channels.", | ||
| 619 | SAMPLE_TIMES_CAPACITY | ||
| 620 | ); | ||
| 621 | } | ||
| 622 | } | ||
| 623 | } | ||
| 624 | }) | ||
| 625 | }); | ||
| 626 | } | ||
| 627 | #[cfg(not(adc_g0))] | ||
| 628 | { | ||
| 629 | #[cfg(adc_u0)] | ||
| 630 | let mut channel_mask = 0; | ||
| 631 | |||
| 632 | // Configure channels and ranks | ||
| 633 | for (_i, (channel, sample_time)) in sequence.enumerate() { | ||
| 634 | Self::configure_channel(channel, sample_time); | ||
| 635 | |||
| 636 | // Each channel is sampled according to sequence | ||
| 637 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 638 | match _i { | ||
| 639 | 0..=3 => { | ||
| 640 | T::regs().sqr1().modify(|w| { | ||
| 641 | w.set_sq(_i, channel.channel()); | ||
| 642 | }); | ||
| 643 | } | ||
| 644 | 4..=8 => { | ||
| 645 | T::regs().sqr2().modify(|w| { | ||
| 646 | w.set_sq(_i - 4, channel.channel()); | ||
| 647 | }); | ||
| 648 | } | ||
| 649 | 9..=13 => { | ||
| 650 | T::regs().sqr3().modify(|w| { | ||
| 651 | w.set_sq(_i - 9, channel.channel()); | ||
| 652 | }); | ||
| 653 | } | ||
| 654 | 14..=15 => { | ||
| 655 | T::regs().sqr4().modify(|w| { | ||
| 656 | w.set_sq(_i - 14, channel.channel()); | ||
| 657 | }); | ||
| 658 | } | ||
| 659 | _ => unreachable!(), | ||
| 660 | } | ||
| 661 | |||
| 662 | #[cfg(adc_u0)] | ||
| 663 | { | ||
| 664 | channel_mask |= 1 << channel.channel(); | ||
| 665 | } | ||
| 666 | } | ||
| 667 | |||
| 668 | // On G0 and U0 enabled channels are sampled from 0 to last channel. | ||
| 669 | // It is possible to add up to 8 sequences if CHSELRMOD = 1. | ||
| 670 | // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. | ||
| 671 | #[cfg(adc_u0)] | ||
| 672 | T::regs().chselr().modify(|reg| { | ||
| 673 | reg.set_chsel(channel_mask); | ||
| 674 | }); | ||
| 675 | } | ||
| 676 | // Set continuous mode with Circular dma. | ||
| 677 | // Clear overrun flag before starting transfer. | ||
| 678 | T::regs().isr().modify(|reg| { | ||
| 679 | reg.set_ovr(true); | ||
| 680 | }); | ||
| 681 | |||
| 682 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 683 | T::regs().cfgr().modify(|reg| { | ||
| 684 | reg.set_discen(false); | ||
| 685 | reg.set_cont(true); | ||
| 686 | reg.set_dmacfg(Dmacfg::CIRCULAR); | ||
| 687 | reg.set_dmaen(true); | ||
| 688 | }); | ||
| 689 | #[cfg(any(adc_g0, adc_u0))] | ||
| 690 | T::regs().cfgr1().modify(|reg| { | ||
| 691 | reg.set_discen(false); | ||
| 692 | reg.set_cont(true); | ||
| 693 | reg.set_dmacfg(Dmacfg::CIRCULAR); | ||
| 694 | reg.set_dmaen(true); | ||
| 695 | }); | ||
| 696 | |||
| 697 | RingBufferedAdc::new(dma, dma_buf) | ||
| 698 | } | ||
| 699 | |||
| 562 | #[cfg(not(adc_g0))] | 700 | #[cfg(not(adc_g0))] |
| 563 | fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) { | 701 | fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) { |
| 564 | // RM0492, RM0481, etc. | 702 | // 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 000000000..7a9200edd --- /dev/null +++ b/examples/stm32l4/src/bin/adc_dma.rs | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::Config; | ||
| 7 | use embassy_stm32::adc::{Adc, AdcChannel, SampleTime}; | ||
| 8 | use {defmt_rtt as _, panic_probe as _}; | ||
| 9 | |||
| 10 | const DMA_BUF_LEN: usize = 512; | ||
| 11 | |||
| 12 | #[embassy_executor::main] | ||
| 13 | async fn main(_spawner: Spawner) { | ||
| 14 | info!("Hello World!"); | ||
| 15 | |||
| 16 | let mut config = Config::default(); | ||
| 17 | { | ||
| 18 | use embassy_stm32::rcc::*; | ||
| 19 | config.rcc.mux.adcsel = mux::Adcsel::SYS; | ||
| 20 | } | ||
| 21 | let p = embassy_stm32::init(config); | ||
| 22 | |||
| 23 | let mut adc = Adc::new(p.ADC1); | ||
| 24 | let mut adc_pin0 = p.PA0.degrade_adc(); | ||
| 25 | let mut adc_pin1 = p.PA1.degrade_adc(); | ||
| 26 | let mut adc_dma_buf = [0u16; DMA_BUF_LEN]; | ||
| 27 | let mut measurements = [0u16; DMA_BUF_LEN / 2]; | ||
| 28 | let mut ring_buffered_adc = adc.into_ring_buffered( | ||
| 29 | p.DMA1_CH1, | ||
| 30 | &mut adc_dma_buf, | ||
| 31 | [ | ||
| 32 | (&mut adc_pin0, SampleTime::CYCLES640_5), | ||
| 33 | (&mut adc_pin1, SampleTime::CYCLES640_5), | ||
| 34 | ] | ||
| 35 | .into_iter(), | ||
| 36 | ); | ||
| 37 | |||
| 38 | info!("starting measurement loop"); | ||
| 39 | loop { | ||
| 40 | match ring_buffered_adc.read(&mut measurements).await { | ||
| 41 | Ok(_) => { | ||
| 42 | //note: originally there was a print here showing all the samples, | ||
| 43 | //but even that takes too much time and would cause adc overruns | ||
| 44 | info!("adc1 first 10 samples: {}", measurements[0..10]); | ||
| 45 | } | ||
| 46 | Err(e) => { | ||
| 47 | warn!("Error: {:?}", e); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
