aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormaor malka <[email protected]>2025-08-24 21:36:33 -0400
committermaor malka <[email protected]>2025-08-24 21:36:33 -0400
commit14a047a9ad75709e0bde8b0fa71f3b4bddedc576 (patch)
treea3f4d4c00d955a0ae99a88755053a8609fb98924
parentef673c6ca310cf0a7e9b1254afb7806bd6879e94 (diff)
stm32/adc/v3: added support for DMA based adc sampling
-rw-r--r--embassy-stm32/src/adc/v3.rs274
-rw-r--r--examples/stm32l4/src/bin/adc_dma.rs49
2 files changed, 322 insertions, 1 deletions
diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs
index a2e42fe52..6d874dbba 100644
--- a/embassy-stm32/src/adc/v3.rs
+++ b/embassy-stm32/src/adc/v3.rs
@@ -3,10 +3,13 @@ use pac::adc::vals::Dmacfg;
3#[cfg(adc_v3)] 3#[cfg(adc_v3)]
4use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; 4use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs};
5 5
6use core::marker::PhantomData;
7use core::sync::atomic::{compiler_fence, Ordering};
8
6use super::{ 9use super::{
7 blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, 10 blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel,
8}; 11};
9use crate::dma::Transfer; 12use crate::dma::{ReadableRingBuffer, Transfer, TransferOptions};
10use crate::{pac, rcc, Peri}; 13use crate::{pac, rcc, Peri};
11 14
12/// Default VREF voltage used for sample conversion to millivolts. 15/// Default VREF voltage used for sample conversion to millivolts.
@@ -107,6 +110,15 @@ pub enum Averaging {
107 Samples128, 110 Samples128,
108 Samples256, 111 Samples256,
109} 112}
113
114#[cfg_attr(feature = "defmt", derive(defmt::Format))]
115pub struct OverrunError;
116
117pub struct RingBufferedAdc<'d, T: Instance> {
118 _phantom: PhantomData<T>,
119 ring_buf: ReadableRingBuffer<'d, u16>,
120}
121
110impl<'d, T: Instance> Adc<'d, T> { 122impl<'d, T: Instance> Adc<'d, T> {
111 pub fn new(adc: Peri<'d, T>) -> Self { 123 pub fn new(adc: Peri<'d, T>) -> Self {
112 rcc::enable_and_reset::<T>(); 124 rcc::enable_and_reset::<T>();
@@ -449,6 +461,122 @@ impl<'d, T: Instance> Adc<'d, T> {
449 }); 461 });
450 } 462 }
451 463
464 /// Configures the ADC to use a DMA ring buffer for continuous data acquisition.
465 ///
466 /// The `dma_buf` should be large enough to prevent DMA buffer overrun.
467 /// The length of the `dma_buf` should be a multiple of the ADC channel count.
468 /// For example, if 3 channels are measured, its length can be 3 * 40 = 120 measurements.
469 ///
470 /// `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.
471 /// It is critical to call `read` frequently to prevent DMA buffer overrun.
472 ///
473 /// [`read`]: #method.read
474 pub fn into_ring_buffered<'a>(
475 &mut self,
476 dma: Peri<'a, impl RxDma<T>>,
477 dma_buf: &'a mut [u16],
478 sequence: impl ExactSizeIterator<Item = (&'a mut AnyAdcChannel<T>, SampleTime)>,
479 ) -> RingBufferedAdc<'a, T> {
480 assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF);
481 assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty");
482 assert!(
483 sequence.len() <= 16,
484 "Asynchronous read sequence cannot be more than 16 in length"
485 );
486 // reset conversions and enable the adc
487 Self::cancel_conversions();
488 self.enable();
489
490 //adc side setup
491
492 // Set sequence length
493 #[cfg(not(any(adc_g0, adc_u0)))]
494 T::regs().sqr1().modify(|w| {
495 w.set_l(sequence.len() as u8 - 1);
496 });
497
498 #[cfg(any(adc_g0, adc_u0))]
499 let mut channel_mask = 0;
500
501 // Configure channels and ranks
502 for (_i, (channel, sample_time)) in sequence.enumerate() {
503 Self::configure_channel(channel, sample_time);
504
505 // Each channel is sampled according to sequence
506 #[cfg(not(any(adc_g0, adc_u0)))]
507 match _i {
508 0..=3 => {
509 T::regs().sqr1().modify(|w| {
510 w.set_sq(_i, channel.channel());
511 });
512 }
513 4..=8 => {
514 T::regs().sqr2().modify(|w| {
515 w.set_sq(_i - 4, channel.channel());
516 });
517 }
518 9..=13 => {
519 T::regs().sqr3().modify(|w| {
520 w.set_sq(_i - 9, channel.channel());
521 });
522 }
523 14..=15 => {
524 T::regs().sqr4().modify(|w| {
525 w.set_sq(_i - 14, channel.channel());
526 });
527 }
528 _ => unreachable!(),
529 }
530 }
531
532 //dma side setup
533 let opts = TransferOptions {
534 half_transfer_ir: true,
535 circular: true,
536 ..Default::default()
537 };
538
539 // Safety: we forget the struct before this function returns.
540 let request = dma.request();
541
542 let ring_buf =
543 unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) };
544
545 // On G0 and U0 enabled channels are sampled from 0 to last channel.
546 // It is possible to add up to 8 sequences if CHSELRMOD = 1.
547 // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used.
548 #[cfg(any(adc_g0, adc_u0))]
549 T::regs().chselr().modify(|reg| {
550 reg.set_chsel(channel_mask);
551 });
552
553 // Set continuous mode with Circular dma.
554 // Clear overrun flag before starting transfer.
555 T::regs().isr().modify(|reg| {
556 reg.set_ovr(true);
557 });
558
559 #[cfg(not(any(adc_g0, adc_u0)))]
560 T::regs().cfgr().modify(|reg| {
561 reg.set_discen(false);
562 reg.set_cont(true);
563 reg.set_dmacfg(Dmacfg::CIRCULAR);
564 reg.set_dmaen(true);
565 });
566 #[cfg(any(adc_g0, adc_u0))]
567 T::regs().cfgr1().modify(|reg| {
568 reg.set_discen(false);
569 reg.set_cont(true);
570 reg.set_dmacfg(Dmacfg::CIRCULAR);
571 reg.set_dmaen(true);
572 });
573
574 RingBufferedAdc {
575 _phantom: PhantomData,
576 ring_buf,
577 }
578 }
579
452 fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) { 580 fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) {
453 // RM0492, RM0481, etc. 581 // RM0492, RM0481, etc.
454 // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected." 582 // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected."
@@ -553,3 +681,147 @@ impl<'d, T: Instance> Adc<'d, T> {
553 } 681 }
554 } 682 }
555} 683}
684
685impl<'d, T: Instance> RingBufferedAdc<'d, T> {
686 #[inline]
687 fn start_continous_sampling(&mut self) {
688 // Start adc conversion
689 T::regs().cr().modify(|reg| {
690 reg.set_adstart(true);
691 });
692 self.ring_buf.start();
693 }
694
695 #[inline]
696 pub fn stop_continous_sampling(&mut self) {
697 // Stop adc conversion
698 if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() {
699 T::regs().cr().modify(|reg| {
700 reg.set_adstp(true);
701 });
702 while T::regs().cr().read().adstart() {}
703 }
704 }
705 pub fn disable_adc(&mut self) {
706 self.stop_continous_sampling();
707 self.ring_buf.clear();
708 self.ring_buf.request_pause();
709 }
710
711 pub fn teardown_adc(&mut self) {
712 self.disable_adc();
713
714 //disable dma control
715 #[cfg(not(any(adc_g0, adc_u0)))]
716 T::regs().cfgr().modify(|reg| {
717 reg.set_dmaen(false);
718 });
719 #[cfg(any(adc_g0, adc_u0))]
720 T::regs().cfgr1().modify(|reg| {
721 reg.set_dmaen(false);
722 });
723
724 //TODO: do we need to cleanup the DMA request here?
725
726 compiler_fence(Ordering::SeqCst);
727 }
728
729 /// Reads measurements from the DMA ring buffer.
730 ///
731 /// This method fills the provided `measurements` array with ADC readings from the DMA buffer.
732 /// 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.
733 ///
734 /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`.
735 /// 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.
736 /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`.
737 ///
738 /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks
739 /// 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.
740 /// Example:
741 /// ```rust,ignore
742 /// const DMA_BUF_LEN: usize = 120;
743 /// use embassy_stm32::adc::{Adc, AdcChannel}
744 ///
745 /// let mut adc = Adc::new(p.ADC1);
746 /// let mut adc_pin0 = p.PA0.degrade_adc();
747 /// let mut adc_pin1 = p.PA1.degrade_adc();
748 /// let adc_dma_buf = [0u16; DMA_BUF_LEN];
749 ///
750 /// let mut ring_buffered_adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered(
751 /// p.DMA2_CH0,
752 /// adc_dma_buf, [
753 /// (&mut *adc_pin0, SampleTime::CYCLES160_5),
754 /// (&mut *adc_pin1, SampleTime::CYCLES160_5),
755 /// ].into_iter());
756 ///
757 ///
758 /// let mut measurements = [0u16; DMA_BUF_LEN / 2];
759 /// loop {
760 /// match ring_buffered_adc.read(&mut measurements).await {
761 /// Ok(_) => {
762 /// defmt::info!("adc1: {}", measurements);
763 /// }
764 /// Err(e) => {
765 /// defmt::warn!("Error: {:?}", e);
766 /// }
767 /// }
768 /// }
769 /// ```
770 ///
771 ///
772 /// [`teardown_adc`]: #method.teardown_adc
773 /// [`start_continous_sampling`]: #method.start_continous_sampling
774 pub async fn read(&mut self, measurements: &mut [u16]) -> Result<usize, OverrunError> {
775 assert_eq!(
776 self.ring_buf.capacity() / 2,
777 measurements.len(),
778 "Buffer size must be half the size of the ring buffer"
779 );
780
781 let r = T::regs();
782
783 // Start background receive if it was not already started
784 if !r.cr().read().adstart() {
785 self.start_continous_sampling();
786 }
787
788 self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError)
789 }
790
791 /// Read bytes that are readily available in the ring buffer.
792 /// If no bytes are currently available in the buffer the call waits until the some
793 /// bytes are available (at least one byte and at most half the buffer size)
794 ///
795 /// Background receive is started if `start_continous_sampling()` has not been previously called.
796 ///
797 /// Receive in the background is terminated if an error is returned.
798 /// It must then manually be started again by calling `start_continous_sampling()` or by re-calling `blocking_read()`.
799 pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result<usize, OverrunError> {
800 let r = T::regs();
801
802 // Start background receive if it was not already started
803 if !r.cr().read().adstart() {
804 self.start_continous_sampling();
805 }
806
807 loop {
808 match self.ring_buf.read(buf) {
809 Ok((0, _)) => {}
810 Ok((len, _)) => {
811 return Ok(len);
812 }
813 Err(_) => {
814 self.stop_continous_sampling();
815 return Err(OverrunError);
816 }
817 }
818 }
819 }
820}
821
822impl<T: Instance> Drop for RingBufferedAdc<'_, T> {
823 fn drop(&mut self) {
824 self.teardown_adc();
825 rcc::disable::<T>();
826 }
827}
diff --git a/examples/stm32l4/src/bin/adc_dma.rs b/examples/stm32l4/src/bin/adc_dma.rs
new file mode 100644
index 000000000..1769c735a
--- /dev/null
+++ b/examples/stm32l4/src/bin/adc_dma.rs
@@ -0,0 +1,49 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_stm32::adc::{Adc, AdcChannel, SampleTime};
7use embassy_stm32::Config;
8use {defmt_rtt as _, panic_probe as _};
9
10const DMA_BUF_LEN: usize = 512;
11
12#[embassy_executor::main]
13async fn main(_spawner: Spawner) {
14 info!("Hello World!");
15
16 let config = Config::default();
17
18 let p = embassy_stm32::init(config);
19
20 let mut adc = Adc::new(p.ADC1);
21 let mut adc_pin0 = p.PA0.degrade_adc();
22 let mut adc_pin1 = p.PA1.degrade_adc();
23 let mut adc_dma_buf = [0u16; DMA_BUF_LEN];
24 let mut measurements = [0u16; DMA_BUF_LEN / 2];
25 let mut ring_buffered_adc = adc.into_ring_buffered(
26 p.DMA1_CH1,
27 &mut adc_dma_buf,
28 [
29 (&mut adc_pin0, SampleTime::CYCLES640_5),
30 (&mut adc_pin1, SampleTime::CYCLES640_5),
31 ]
32 .into_iter(),
33 );
34
35 info!("starting measurement loop");
36 loop {
37 match ring_buffered_adc.read(&mut measurements).await {
38 Ok(_) => {
39 //note: originally there was a print here showing all the samples,
40 //but even that takes too much time and would cause adc overruns
41 info!("adc1 first 10 samples: {}",measurements[0..10]);
42 }
43 Err(e) => {
44 warn!("Error: {:?}", e);
45 }
46 }
47 }
48
49}