diff options
| author | maor malka <[email protected]> | 2025-08-24 21:36:33 -0400 |
|---|---|---|
| committer | maor malka <[email protected]> | 2025-08-24 21:36:33 -0400 |
| commit | 14a047a9ad75709e0bde8b0fa71f3b4bddedc576 (patch) | |
| tree | a3f4d4c00d955a0ae99a88755053a8609fb98924 | |
| parent | ef673c6ca310cf0a7e9b1254afb7806bd6879e94 (diff) | |
stm32/adc/v3: added support for DMA based adc sampling
| -rw-r--r-- | embassy-stm32/src/adc/v3.rs | 274 | ||||
| -rw-r--r-- | examples/stm32l4/src/bin/adc_dma.rs | 49 |
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)] |
| 4 | use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; | 4 | use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; |
| 5 | 5 | ||
| 6 | use core::marker::PhantomData; | ||
| 7 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 8 | |||
| 6 | use super::{ | 9 | use 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 | }; |
| 9 | use crate::dma::Transfer; | 12 | use crate::dma::{ReadableRingBuffer, Transfer, TransferOptions}; |
| 10 | use crate::{pac, rcc, Peri}; | 13 | use 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))] | ||
| 115 | pub struct OverrunError; | ||
| 116 | |||
| 117 | pub struct RingBufferedAdc<'d, T: Instance> { | ||
| 118 | _phantom: PhantomData<T>, | ||
| 119 | ring_buf: ReadableRingBuffer<'d, u16>, | ||
| 120 | } | ||
| 121 | |||
| 110 | impl<'d, T: Instance> Adc<'d, T> { | 122 | impl<'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 | |||
| 685 | impl<'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 | |||
| 822 | impl<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 | |||
| 4 | use defmt::*; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::adc::{Adc, AdcChannel, SampleTime}; | ||
| 7 | use embassy_stm32::Config; | ||
| 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 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 | } | ||
