Skip to content

Commit 7e917fd

Browse files
authored
Merge pull request #4583 from maor1993/main
stm32/adc/v3: added support for DMA based adc sampling
2 parents 3ff0b2c + 8215864 commit 7e917fd

File tree

4 files changed

+370
-0
lines changed

4 files changed

+370
-0
lines changed

embassy-stm32/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5151
- chore: Updated stm32-metapac and stm32-data dependencies
5252
- feat: stm32/adc/v3: allow DMA reads to loop through enable channels
5353
- fix: Fix XSPI not disabling alternate bytes when they were previously enabled
54+
- feat: stm32/adc/v3: added support for Continuous DMA configuration
5455
- fix: Fix stm32h7rs init when using external flash via XSPI
5556
- feat: Add Adc::new_with_clock() to configure analog clock
5657
- feat: Add GPDMA linked-list + ringbuffer support ([#3923](https://github.com/embassy-rs/embassy/pull/3923))
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

embassy-stm32/src/adc/v3.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ pub use pac::adc::vals::{Ovsr, Ovss, Presc};
1212
use super::{
1313
Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, blocking_delay_us,
1414
};
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+
1522
use crate::dma::Transfer;
1623
use crate::{Peri, pac, rcc};
1724

@@ -559,6 +566,137 @@ impl<'d, T: Instance> Adc<'d, T> {
559566
});
560567
}
561568

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+
562700
#[cfg(not(adc_g0))]
563701
fn configure_channel(channel: &mut impl AdcChannel<T>, sample_time: SampleTime) {
564702
// RM0492, RM0481, etc.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)