diff options
| author | maor malka <[email protected]> | 2025-08-26 22:15:34 -0400 |
|---|---|---|
| committer | maor malka <[email protected]> | 2025-08-26 22:15:34 -0400 |
| commit | 6b8d375813116fba0e04aa28e23ded8ab077729a (patch) | |
| tree | e852447c4507dc11938a5f94fcecf6e94e537dfa | |
| parent | 75484f4f51847a92e2df1e8319debec61cd7aca2 (diff) | |
stm32/adc/v3: moved ringbuffered to seperate file
| -rw-r--r-- | embassy-stm32/src/adc/ringbuffered_v3.rs | 181 | ||||
| -rw-r--r-- | embassy-stm32/src/adc/v3.rs | 182 |
2 files changed, 189 insertions, 174 deletions
diff --git a/embassy-stm32/src/adc/ringbuffered_v3.rs b/embassy-stm32/src/adc/ringbuffered_v3.rs new file mode 100644 index 000000000..655ae712f --- /dev/null +++ b/embassy-stm32/src/adc/ringbuffered_v3.rs | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 3 | use embassy_hal_internal::Peri; | ||
| 4 | |||
| 5 | use crate::dma::{ReadableRingBuffer, TransferOptions}; | ||
| 6 | |||
| 7 | use crate::adc::Instance; | ||
| 8 | use crate::adc::RxDma; | ||
| 9 | use crate::rcc; | ||
| 10 | |||
| 11 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 12 | pub struct OverrunError; | ||
| 13 | |||
| 14 | pub struct RingBufferedAdc<'d, T: Instance> { | ||
| 15 | pub _phantom: PhantomData<T>, | ||
| 16 | pub ring_buf: ReadableRingBuffer<'d, u16>, | ||
| 17 | } | ||
| 18 | |||
| 19 | impl<'d, T: Instance> RingBufferedAdc<'d, T> { | ||
| 20 | pub fn new(dma: Peri<'d, impl RxDma<T>>, dma_buf: &'d mut [u16]) -> Self { | ||
| 21 | //dma side setup | ||
| 22 | let opts = TransferOptions { | ||
| 23 | half_transfer_ir: true, | ||
| 24 | circular: true, | ||
| 25 | ..Default::default() | ||
| 26 | }; | ||
| 27 | |||
| 28 | // Safety: we forget the struct before this function returns. | ||
| 29 | let request = dma.request(); | ||
| 30 | |||
| 31 | let ring_buf = | ||
| 32 | unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; | ||
| 33 | |||
| 34 | Self { | ||
| 35 | _phantom: PhantomData, | ||
| 36 | ring_buf, | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | #[inline] | ||
| 41 | fn start_continous_sampling(&mut self) { | ||
| 42 | // Start adc conversion | ||
| 43 | T::regs().cr().modify(|reg| { | ||
| 44 | reg.set_adstart(true); | ||
| 45 | }); | ||
| 46 | self.ring_buf.start(); | ||
| 47 | } | ||
| 48 | |||
| 49 | #[inline] | ||
| 50 | pub fn stop_continous_sampling(&mut self) { | ||
| 51 | // Stop adc conversion | ||
| 52 | if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { | ||
| 53 | T::regs().cr().modify(|reg| { | ||
| 54 | reg.set_adstp(true); | ||
| 55 | }); | ||
| 56 | while T::regs().cr().read().adstart() {} | ||
| 57 | } | ||
| 58 | } | ||
| 59 | pub fn disable_adc(&mut self) { | ||
| 60 | self.stop_continous_sampling(); | ||
| 61 | self.ring_buf.clear(); | ||
| 62 | self.ring_buf.request_pause(); | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn teardown_adc(&mut self) { | ||
| 66 | self.disable_adc(); | ||
| 67 | |||
| 68 | //disable dma control | ||
| 69 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 70 | T::regs().cfgr().modify(|reg| { | ||
| 71 | reg.set_dmaen(false); | ||
| 72 | }); | ||
| 73 | #[cfg(any(adc_g0, adc_u0))] | ||
| 74 | T::regs().cfgr1().modify(|reg| { | ||
| 75 | reg.set_dmaen(false); | ||
| 76 | }); | ||
| 77 | |||
| 78 | //TODO: do we need to cleanup the DMA request here? | ||
| 79 | |||
| 80 | compiler_fence(Ordering::SeqCst); | ||
| 81 | } | ||
| 82 | |||
| 83 | /// Reads measurements from the DMA ring buffer. | ||
| 84 | /// | ||
| 85 | /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. | ||
| 86 | /// 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. | ||
| 87 | /// | ||
| 88 | /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. | ||
| 89 | /// 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. | ||
| 90 | /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. | ||
| 91 | /// | ||
| 92 | /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks | ||
| 93 | /// 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. | ||
| 94 | /// Example: | ||
| 95 | /// ```rust,ignore | ||
| 96 | /// const DMA_BUF_LEN: usize = 120; | ||
| 97 | /// use embassy_stm32::adc::{Adc, AdcChannel} | ||
| 98 | /// | ||
| 99 | /// let mut adc = Adc::new(p.ADC1); | ||
| 100 | /// let mut adc_pin0 = p.PA0.degrade_adc(); | ||
| 101 | /// let mut adc_pin1 = p.PA1.degrade_adc(); | ||
| 102 | /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; | ||
| 103 | /// | ||
| 104 | /// let mut ring_buffered_adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered( | ||
| 105 | /// p.DMA2_CH0, | ||
| 106 | /// adc_dma_buf, [ | ||
| 107 | /// (&mut *adc_pin0, SampleTime::CYCLES160_5), | ||
| 108 | /// (&mut *adc_pin1, SampleTime::CYCLES160_5), | ||
| 109 | /// ].into_iter()); | ||
| 110 | /// | ||
| 111 | /// | ||
| 112 | /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; | ||
| 113 | /// loop { | ||
| 114 | /// match ring_buffered_adc.read(&mut measurements).await { | ||
| 115 | /// Ok(_) => { | ||
| 116 | /// defmt::info!("adc1: {}", measurements); | ||
| 117 | /// } | ||
| 118 | /// Err(e) => { | ||
| 119 | /// defmt::warn!("Error: {:?}", e); | ||
| 120 | /// } | ||
| 121 | /// } | ||
| 122 | /// } | ||
| 123 | /// ``` | ||
| 124 | /// | ||
| 125 | /// | ||
| 126 | /// [`teardown_adc`]: #method.teardown_adc | ||
| 127 | /// [`start_continous_sampling`]: #method.start_continous_sampling | ||
| 128 | pub async fn read(&mut self, measurements: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 129 | assert_eq!( | ||
| 130 | self.ring_buf.capacity() / 2, | ||
| 131 | measurements.len(), | ||
| 132 | "Buffer size must be half the size of the ring buffer" | ||
| 133 | ); | ||
| 134 | |||
| 135 | let r = T::regs(); | ||
| 136 | |||
| 137 | // Start background receive if it was not already started | ||
| 138 | if !r.cr().read().adstart() { | ||
| 139 | self.start_continous_sampling(); | ||
| 140 | } | ||
| 141 | |||
| 142 | self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Read bytes that are readily available in the ring buffer. | ||
| 146 | /// If no bytes are currently available in the buffer the call waits until the some | ||
| 147 | /// bytes are available (at least one byte and at most half the buffer size) | ||
| 148 | /// | ||
| 149 | /// Background receive is started if `start_continous_sampling()` has not been previously called. | ||
| 150 | /// | ||
| 151 | /// Receive in the background is terminated if an error is returned. | ||
| 152 | /// It must then manually be started again by calling `start_continous_sampling()` or by re-calling `blocking_read()`. | ||
| 153 | pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 154 | let r = T::regs(); | ||
| 155 | |||
| 156 | // Start background receive if it was not already started | ||
| 157 | if !r.cr().read().adstart() { | ||
| 158 | self.start_continous_sampling(); | ||
| 159 | } | ||
| 160 | |||
| 161 | loop { | ||
| 162 | match self.ring_buf.read(buf) { | ||
| 163 | Ok((0, _)) => {} | ||
| 164 | Ok((len, _)) => { | ||
| 165 | return Ok(len); | ||
| 166 | } | ||
| 167 | Err(_) => { | ||
| 168 | self.stop_continous_sampling(); | ||
| 169 | return Err(OverrunError); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | impl<T: Instance> Drop for RingBufferedAdc<'_, T> { | ||
| 177 | fn drop(&mut self) { | ||
| 178 | self.teardown_adc(); | ||
| 179 | rcc::disable::<T>(); | ||
| 180 | } | ||
| 181 | } | ||
diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index a52141a34..30b04fc81 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs | |||
| @@ -1,6 +1,3 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 3 | |||
| 4 | use cfg_if::cfg_if; | 1 | use cfg_if::cfg_if; |
| 5 | use pac::adc::vals::Dmacfg; | 2 | use pac::adc::vals::Dmacfg; |
| 6 | #[cfg(adc_v3)] | 3 | #[cfg(adc_v3)] |
| @@ -9,8 +6,14 @@ use pac::adc::vals::{OversamplingRatio, OversamplingShift, Rovsm, Trovs}; | |||
| 9 | use super::{ | 6 | use super::{ |
| 10 | blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, | 7 | blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, |
| 11 | }; | 8 | }; |
| 9 | |||
| 12 | #[cfg(adc_v3)] | 10 | #[cfg(adc_v3)] |
| 13 | use crate::dma::{ReadableRingBuffer, Transfer, TransferOptions}; | 11 | mod ringbuffered_v3; |
| 12 | |||
| 13 | #[cfg(adc_v3)] | ||
| 14 | use ringbuffered_v3::RingBufferedAdc; | ||
| 15 | |||
| 16 | use crate::dma::Transfer; | ||
| 14 | use crate::{pac, rcc, Peri}; | 17 | use crate::{pac, rcc, Peri}; |
| 15 | 18 | ||
| 16 | /// Default VREF voltage used for sample conversion to millivolts. | 19 | /// Default VREF voltage used for sample conversion to millivolts. |
| @@ -112,14 +115,6 @@ pub enum Averaging { | |||
| 112 | Samples256, | 115 | Samples256, |
| 113 | } | 116 | } |
| 114 | 117 | ||
| 115 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 116 | pub struct OverrunError; | ||
| 117 | |||
| 118 | pub struct RingBufferedAdc<'d, T: Instance> { | ||
| 119 | _phantom: PhantomData<T>, | ||
| 120 | ring_buf: ReadableRingBuffer<'d, u16>, | ||
| 121 | } | ||
| 122 | |||
| 123 | impl<'d, T: Instance> Adc<'d, T> { | 118 | impl<'d, T: Instance> Adc<'d, T> { |
| 124 | pub fn new(adc: Peri<'d, T>) -> Self { | 119 | pub fn new(adc: Peri<'d, T>) -> Self { |
| 125 | rcc::enable_and_reset::<T>(); | 120 | rcc::enable_and_reset::<T>(); |
| @@ -531,19 +526,6 @@ impl<'d, T: Instance> Adc<'d, T> { | |||
| 531 | } | 526 | } |
| 532 | } | 527 | } |
| 533 | 528 | ||
| 534 | //dma side setup | ||
| 535 | let opts = TransferOptions { | ||
| 536 | half_transfer_ir: true, | ||
| 537 | circular: true, | ||
| 538 | ..Default::default() | ||
| 539 | }; | ||
| 540 | |||
| 541 | // Safety: we forget the struct before this function returns. | ||
| 542 | let request = dma.request(); | ||
| 543 | |||
| 544 | let ring_buf = | ||
| 545 | unsafe { ReadableRingBuffer::new(dma, request, T::regs().dr().as_ptr() as *mut u16, dma_buf, opts) }; | ||
| 546 | |||
| 547 | // On G0 and U0 enabled channels are sampled from 0 to last channel. | 529 | // On G0 and U0 enabled channels are sampled from 0 to last channel. |
| 548 | // It is possible to add up to 8 sequences if CHSELRMOD = 1. | 530 | // It is possible to add up to 8 sequences if CHSELRMOD = 1. |
| 549 | // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. | 531 | // However for supporting more than 8 channels alternative CHSELRMOD = 0 approach is used. |
| @@ -573,10 +555,7 @@ impl<'d, T: Instance> Adc<'d, T> { | |||
| 573 | reg.set_dmaen(true); | 555 | reg.set_dmaen(true); |
| 574 | }); | 556 | }); |
| 575 | 557 | ||
| 576 | RingBufferedAdc { | 558 | RingBufferedAdc::new(dma, dma_buf) |
| 577 | _phantom: PhantomData, | ||
| 578 | ring_buf, | ||
| 579 | } | ||
| 580 | } | 559 | } |
| 581 | 560 | ||
| 582 | fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) { | 561 | fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) { |
| @@ -683,148 +662,3 @@ impl<'d, T: Instance> Adc<'d, T> { | |||
| 683 | } | 662 | } |
| 684 | } | 663 | } |
| 685 | } | 664 | } |
| 686 | |||
| 687 | #[cfg(adc_v3)] | ||
| 688 | impl<'d, T: Instance> RingBufferedAdc<'d, T> { | ||
| 689 | #[inline] | ||
| 690 | fn start_continous_sampling(&mut self) { | ||
| 691 | // Start adc conversion | ||
| 692 | T::regs().cr().modify(|reg| { | ||
| 693 | reg.set_adstart(true); | ||
| 694 | }); | ||
| 695 | self.ring_buf.start(); | ||
| 696 | } | ||
| 697 | |||
| 698 | #[inline] | ||
| 699 | pub fn stop_continous_sampling(&mut self) { | ||
| 700 | // Stop adc conversion | ||
| 701 | if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { | ||
| 702 | T::regs().cr().modify(|reg| { | ||
| 703 | reg.set_adstp(true); | ||
| 704 | }); | ||
| 705 | while T::regs().cr().read().adstart() {} | ||
| 706 | } | ||
| 707 | } | ||
| 708 | pub fn disable_adc(&mut self) { | ||
| 709 | self.stop_continous_sampling(); | ||
| 710 | self.ring_buf.clear(); | ||
| 711 | self.ring_buf.request_pause(); | ||
| 712 | } | ||
| 713 | |||
| 714 | pub fn teardown_adc(&mut self) { | ||
| 715 | self.disable_adc(); | ||
| 716 | |||
| 717 | //disable dma control | ||
| 718 | #[cfg(not(any(adc_g0, adc_u0)))] | ||
| 719 | T::regs().cfgr().modify(|reg| { | ||
| 720 | reg.set_dmaen(false); | ||
| 721 | }); | ||
| 722 | #[cfg(any(adc_g0, adc_u0))] | ||
| 723 | T::regs().cfgr1().modify(|reg| { | ||
| 724 | reg.set_dmaen(false); | ||
| 725 | }); | ||
| 726 | |||
| 727 | //TODO: do we need to cleanup the DMA request here? | ||
| 728 | |||
| 729 | compiler_fence(Ordering::SeqCst); | ||
| 730 | } | ||
| 731 | |||
| 732 | /// Reads measurements from the DMA ring buffer. | ||
| 733 | /// | ||
| 734 | /// This method fills the provided `measurements` array with ADC readings from the DMA buffer. | ||
| 735 | /// 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. | ||
| 736 | /// | ||
| 737 | /// Each call to `read` will populate the `measurements` array in the same order as the channels defined with `sequence`. | ||
| 738 | /// 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. | ||
| 739 | /// For example if 2 channels are sampled `measurements` contain: `[sq0 sq1 sq0 sq1 sq0 sq1 ..]`. | ||
| 740 | /// | ||
| 741 | /// Note that the ADC Datarate can be very fast, it is suggested to use DMA mode inside tightly running tasks | ||
| 742 | /// 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. | ||
| 743 | /// Example: | ||
| 744 | /// ```rust,ignore | ||
| 745 | /// const DMA_BUF_LEN: usize = 120; | ||
| 746 | /// use embassy_stm32::adc::{Adc, AdcChannel} | ||
| 747 | /// | ||
| 748 | /// let mut adc = Adc::new(p.ADC1); | ||
| 749 | /// let mut adc_pin0 = p.PA0.degrade_adc(); | ||
| 750 | /// let mut adc_pin1 = p.PA1.degrade_adc(); | ||
| 751 | /// let adc_dma_buf = [0u16; DMA_BUF_LEN]; | ||
| 752 | /// | ||
| 753 | /// let mut ring_buffered_adc: RingBufferedAdc<embassy_stm32::peripherals::ADC1> = adc.into_ring_buffered( | ||
| 754 | /// p.DMA2_CH0, | ||
| 755 | /// adc_dma_buf, [ | ||
| 756 | /// (&mut *adc_pin0, SampleTime::CYCLES160_5), | ||
| 757 | /// (&mut *adc_pin1, SampleTime::CYCLES160_5), | ||
| 758 | /// ].into_iter()); | ||
| 759 | /// | ||
| 760 | /// | ||
| 761 | /// let mut measurements = [0u16; DMA_BUF_LEN / 2]; | ||
| 762 | /// loop { | ||
| 763 | /// match ring_buffered_adc.read(&mut measurements).await { | ||
| 764 | /// Ok(_) => { | ||
| 765 | /// defmt::info!("adc1: {}", measurements); | ||
| 766 | /// } | ||
| 767 | /// Err(e) => { | ||
| 768 | /// defmt::warn!("Error: {:?}", e); | ||
| 769 | /// } | ||
| 770 | /// } | ||
| 771 | /// } | ||
| 772 | /// ``` | ||
| 773 | /// | ||
| 774 | /// | ||
| 775 | /// [`teardown_adc`]: #method.teardown_adc | ||
| 776 | /// [`start_continous_sampling`]: #method.start_continous_sampling | ||
| 777 | pub async fn read(&mut self, measurements: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 778 | assert_eq!( | ||
| 779 | self.ring_buf.capacity() / 2, | ||
| 780 | measurements.len(), | ||
| 781 | "Buffer size must be half the size of the ring buffer" | ||
| 782 | ); | ||
| 783 | |||
| 784 | let r = T::regs(); | ||
| 785 | |||
| 786 | // Start background receive if it was not already started | ||
| 787 | if !r.cr().read().adstart() { | ||
| 788 | self.start_continous_sampling(); | ||
| 789 | } | ||
| 790 | |||
| 791 | self.ring_buf.read_exact(measurements).await.map_err(|_| OverrunError) | ||
| 792 | } | ||
| 793 | |||
| 794 | /// Read bytes that are readily available in the ring buffer. | ||
| 795 | /// If no bytes are currently available in the buffer the call waits until the some | ||
| 796 | /// bytes are available (at least one byte and at most half the buffer size) | ||
| 797 | /// | ||
| 798 | /// Background receive is started if `start_continous_sampling()` has not been previously called. | ||
| 799 | /// | ||
| 800 | /// Receive in the background is terminated if an error is returned. | ||
| 801 | /// It must then manually be started again by calling `start_continous_sampling()` or by re-calling `blocking_read()`. | ||
| 802 | pub fn blocking_read(&mut self, buf: &mut [u16]) -> Result<usize, OverrunError> { | ||
| 803 | let r = T::regs(); | ||
| 804 | |||
| 805 | // Start background receive if it was not already started | ||
| 806 | if !r.cr().read().adstart() { | ||
| 807 | self.start_continous_sampling(); | ||
| 808 | } | ||
| 809 | |||
| 810 | loop { | ||
| 811 | match self.ring_buf.read(buf) { | ||
| 812 | Ok((0, _)) => {} | ||
| 813 | Ok((len, _)) => { | ||
| 814 | return Ok(len); | ||
| 815 | } | ||
| 816 | Err(_) => { | ||
| 817 | self.stop_continous_sampling(); | ||
| 818 | return Err(OverrunError); | ||
| 819 | } | ||
| 820 | } | ||
| 821 | } | ||
| 822 | } | ||
| 823 | } | ||
| 824 | |||
| 825 | impl<T: Instance> Drop for RingBufferedAdc<'_, T> { | ||
| 826 | fn drop(&mut self) { | ||
| 827 | self.teardown_adc(); | ||
| 828 | rcc::disable::<T>(); | ||
| 829 | } | ||
| 830 | } | ||
