Skip to content

Commit fdd152b

Browse files
authored
Merge pull request #5675 from jduranf/adc_ring_buffered_timer
Add ADC ring buffer using DMA and an external trigger in STM32G0 and STM32C0
2 parents bfc6123 + 6a4424c commit fdd152b

File tree

6 files changed

+323
-28
lines changed

6 files changed

+323
-28
lines changed

embassy-stm32/src/adc/c0.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ impl AdcRegs for crate::pac::adc::Adc {
8181
}
8282

8383
fn configure_dma(&self, conversion_mode: super::ConversionMode) {
84+
// Enable overrun control, so no new DMA requests will be generated until
85+
// previous DR values is read.
86+
self.isr().modify(|reg| {
87+
reg.set_ovr(true);
88+
});
89+
8490
match conversion_mode {
8591
ConversionMode::Singular => {
86-
// Enable overrun control, so no new DMA requests will be generated until
87-
// previous DR values is read.
88-
self.isr().modify(|reg| {
89-
reg.set_ovr(true);
90-
});
91-
9292
// Set continuous mode with oneshot dma.
9393
self.cfgr1().modify(|reg| {
9494
reg.set_discen(false);
@@ -98,6 +98,35 @@ impl AdcRegs for crate::pac::adc::Adc {
9898
reg.set_ovrmod(Ovrmod::PRESERVE);
9999
});
100100
}
101+
ConversionMode::Repeated(trigger) => {
102+
match trigger.signal {
103+
u8::MAX => {
104+
// continuous conversions
105+
self.cfgr1().modify(|reg| {
106+
reg.set_discen(false);
107+
reg.set_cont(true);
108+
reg.set_dmacfg(Dmacfg::DMA_CIRCULAR);
109+
reg.set_dmaen(true);
110+
reg.set_ovrmod(Ovrmod::OVERWRITE);
111+
});
112+
}
113+
_ => {
114+
self.cfgr1().modify(|reg| {
115+
reg.set_discen(false);
116+
reg.set_cont(false); // Triggered, not continuous
117+
reg.set_dmacfg(Dmacfg::DMA_CIRCULAR);
118+
reg.set_dmaen(true);
119+
reg.set_ovrmod(Ovrmod::OVERWRITE);
120+
// Configure trigger edge (rising, falling, or both)
121+
reg.set_exten(trigger.edge);
122+
reg.set_extsel(trigger.signal);
123+
});
124+
125+
// Regular conversions uses DMA so no need to generate interrupt
126+
self.ier().modify(|r| r.set_eosie(false));
127+
}
128+
}
129+
}
101130
}
102131
}
103132

embassy-stm32/src/adc/g4.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl super::AdcRegs for crate::pac::adc::Adc {
146146
}
147147
_ => {
148148
self.cfgr().modify(|r| {
149-
r.set_cont(false); // New trigger is neede for each sample to be read
149+
r.set_cont(false); // New trigger is needed for each sample to be read
150150
});
151151

152152
self.cfgr().modify(|r| {

embassy-stm32/src/adc/mod.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#[cfg_attr(adc_c0, path = "c0.rs")]
1818
mod _version;
1919

20-
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba))]
20+
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba, adc_c0))]
2121
mod ringbuffered;
2222

2323
use core::marker::PhantomData;
@@ -29,7 +29,7 @@ pub use _version::*;
2929
use embassy_hal_internal::PeripheralType;
3030
#[cfg(any(adc_f1, adc_f3v1, adc_v1, adc_l0, adc_f3v2))]
3131
use embassy_sync::waitqueue::AtomicWaker;
32-
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba))]
32+
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba, adc_c0))]
3333
pub use ringbuffered::RingBufferedAdc;
3434

3535
#[cfg(adc_u5)]
@@ -44,7 +44,7 @@ pub mod adc4;
4444
#[allow(unused)]
4545
pub(self) use crate::block_for_us as blocking_delay_us;
4646
pub use crate::pac::adc::vals;
47-
#[cfg(any(adc_v2, adc_g4))]
47+
#[cfg(any(adc_v2, adc_g4, adc_g0, adc_c0))]
4848
pub use crate::pac::adc::vals::Exten;
4949
#[cfg(not(any(adc_f1, adc_f3v3)))]
5050
pub use crate::pac::adc::vals::Res as Resolution;
@@ -171,11 +171,11 @@ pub enum Averaging {
171171
Samples1024,
172172
}
173173

174-
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba))]
174+
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba, adc_c0))]
175175
pub(crate) struct Trigger {
176-
#[cfg(any(adc_v2, adc_g4))]
176+
#[cfg(any(adc_v2, adc_g4, adc_g0, adc_c0))]
177177
signal: u8,
178-
#[cfg(any(adc_v2, adc_g4))]
178+
#[cfg(any(adc_v2, adc_g4, adc_g0, adc_c0))]
179179
edge: Exten,
180180
}
181181

@@ -189,7 +189,7 @@ pub(crate) enum ConversionMode {
189189
))]
190190
Singular,
191191
// Should match the cfg on "into_ring_buffered" below
192-
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba))]
192+
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba, adc_c0))]
193193
Repeated(Trigger),
194194
}
195195

@@ -307,7 +307,7 @@ impl<'d, T: Instance> Adc<'d, T> {
307307
T::regs().stop();
308308
}
309309

310-
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba))]
310+
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0, adc_wba, adc_c0))]
311311
/// Configures the ADC to use a DMA ring buffer for continuous data acquisition.
312312
///
313313
/// Use the [`Self::read`] method to retrieve measurements from the DMA ring buffer. The read buffer
@@ -341,7 +341,7 @@ impl<'d, T: Instance> Adc<'d, T> {
341341
irq: impl crate::interrupt::typelevel::Binding<D::Interrupt, crate::dma::InterruptHandler<D>> + 'a,
342342
sequence: impl ExactSizeIterator<Item = (AnyAdcChannel<'b, T>, <T::Regs as BasicAdcRegs>::SampleTime)>,
343343
_trigger: impl RegularTrigger<T>,
344-
#[cfg(any(adc_v2, adc_g4))] edge: Exten,
344+
#[cfg(any(adc_v2, adc_g4, adc_g0, adc_c0))] edge: Exten,
345345
) -> RingBufferedAdc<'a, T> {
346346
let sequence_len = sequence.len();
347347
assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF);
@@ -369,9 +369,9 @@ impl<'d, T: Instance> Adc<'d, T> {
369369
#[cfg(any(adc_g4, adc_h5))]
370370
T::regs().enable();
371371
T::regs().configure_dma(ConversionMode::Repeated(Trigger {
372-
#[cfg(any(adc_v2, adc_g4))]
372+
#[cfg(any(adc_v2, adc_g4, adc_g0, adc_c0))]
373373
signal: _trigger.signal(),
374-
#[cfg(any(adc_v2, adc_g4))]
374+
#[cfg(any(adc_v2, adc_g4, adc_g0, adc_c0))]
375375
edge,
376376
}));
377377

embassy-stm32/src/adc/v3.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,16 +256,56 @@ impl super::AdcRegs for crate::pac::adc::Adc {
256256
#[cfg(any(adc_g0, adc_u0))]
257257
let regs = self.cfgr1();
258258

259-
regs.modify(|reg| {
260-
reg.set_discen(false);
261-
reg.set_cont(true);
262-
reg.set_dmacfg(match conversion_mode {
263-
ConversionMode::Singular => Dmacfg::ONE_SHOT,
264-
#[cfg(any(adc_v2, adc_g4, adc_v3, adc_g0, adc_u0))]
265-
ConversionMode::Repeated(_) => Dmacfg::CIRCULAR,
266-
});
267-
reg.set_dmaen(true);
268-
});
259+
match conversion_mode {
260+
ConversionMode::Singular => {
261+
regs.modify(|reg| {
262+
reg.set_discen(false);
263+
reg.set_cont(true);
264+
reg.set_dmacfg(Dmacfg::ONE_SHOT);
265+
reg.set_dmaen(true);
266+
});
267+
}
268+
#[cfg(any(adc_v3, adc_g0, adc_u0))]
269+
ConversionMode::Repeated(trigger) => {
270+
#[cfg(not(adc_g0))]
271+
{
272+
let _ = trigger; // Suppress unused variable warning
273+
// For non-G0 variants, only continuous conversions are supported
274+
regs.modify(|reg| {
275+
reg.set_discen(false);
276+
reg.set_cont(true);
277+
reg.set_dmacfg(Dmacfg::CIRCULAR);
278+
reg.set_dmaen(true);
279+
});
280+
}
281+
#[cfg(adc_g0)]
282+
match trigger.signal {
283+
u8::MAX => {
284+
// continuous conversions
285+
regs.modify(|reg| {
286+
reg.set_discen(false);
287+
reg.set_cont(true);
288+
reg.set_dmacfg(Dmacfg::CIRCULAR);
289+
reg.set_dmaen(true);
290+
});
291+
}
292+
_ => {
293+
regs.modify(|reg| {
294+
reg.set_discen(false);
295+
reg.set_cont(false); // New trigger is needed for each sample to be read
296+
reg.set_dmacfg(Dmacfg::CIRCULAR);
297+
reg.set_dmaen(true);
298+
// Configure trigger edge (rising, falling, or both)
299+
reg.set_exten(trigger.edge);
300+
reg.set_extsel(trigger.signal.into());
301+
});
302+
303+
// Regular conversions uses DMA so no need to generate interrupt
304+
self.ier().modify(|r| r.set_eosie(false));
305+
}
306+
}
307+
}
308+
}
269309
}
270310

271311
fn configure_sequence(&self, sequence: impl ExactSizeIterator<Item = ((u8, bool), SampleTime)>) {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! ADC with DMA ring buffer triggered by timer
2+
//!
3+
//! This example demonstrates periodic ADC sampling using a timer trigger and DMA ring buffer.
4+
//! The timer generates regular ADC conversions at a controlled rate, and DMA automatically
5+
//! stores the samples in a circular buffer for efficient data acquisition.
6+
//!
7+
//! Hardware setup:
8+
//! - PA0: ADC input (connect to analog signal)
9+
//! - Internal VrefInt and Temperature sensors also monitored
10+
11+
#![no_std]
12+
#![no_main]
13+
14+
use defmt::*;
15+
use embassy_executor::Spawner;
16+
use embassy_stm32::adc::{Adc, AdcChannel as _, Resolution, SampleTime};
17+
use embassy_stm32::pac::adc::vals::Exten;
18+
use embassy_stm32::peripherals::DMA1_CH1;
19+
use embassy_stm32::time::Hertz;
20+
use embassy_stm32::timer::complementary_pwm::{ComplementaryPwm, Mms2};
21+
use embassy_stm32::timer::low_level::CountingMode;
22+
use embassy_stm32::triggers::TIM1_TRGO2;
23+
use embassy_stm32::{bind_interrupts, dma};
24+
use {defmt_rtt as _, panic_probe as _};
25+
26+
bind_interrupts!(struct Irqs {
27+
DMA1_CHANNEL1 => dma::InterruptHandler<DMA1_CH1>;
28+
});
29+
30+
#[embassy_executor::main]
31+
async fn main(_spawner: Spawner) {
32+
let config = Default::default();
33+
let p = embassy_stm32::init(config);
34+
35+
info!("ADC Ring Buffer with Timer Trigger example for STM32C0");
36+
37+
// Configure TIM1 to generate TRGO2 events at 10 Hz
38+
// This will trigger ADC conversions periodically
39+
let tim1 = p.TIM1;
40+
let mut pwm = ComplementaryPwm::new(
41+
tim1,
42+
None, // CH1
43+
None, // CH1N
44+
None, // CH2
45+
None, // CH2N
46+
None, // CH3
47+
None, // CH3N
48+
None, // CH4
49+
None, // CH4N
50+
Hertz::hz(10), // 10 Hz trigger rate
51+
CountingMode::EdgeAlignedUp,
52+
);
53+
54+
// Configure TRGO2 to trigger on update event
55+
pwm.set_mms2(Mms2::UPDATE);
56+
57+
// Configure ADC with DMA ring buffer
58+
let adc = Adc::new(p.ADC1, Resolution::BITS12);
59+
60+
// Setup channels to measure
61+
let vrefint_channel = adc.enable_vrefint().degrade_adc();
62+
let temp_channel = adc.enable_temperature().degrade_adc();
63+
let pa0 = p.PA0.degrade_adc();
64+
65+
let sequence = [
66+
(vrefint_channel, SampleTime::CYCLES12_5),
67+
(temp_channel, SampleTime::CYCLES12_5),
68+
(pa0, SampleTime::CYCLES12_5),
69+
]
70+
.into_iter();
71+
72+
// DMA buffer - using double size allows reading one half while DMA fills the other
73+
// Buffer holds continuous samples
74+
let mut dma_buf = [0u16; 12]; // 4 complete samples (3 channels each)
75+
76+
// Create ring-buffered ADC with timer trigger
77+
let mut ring_buffered_adc = adc.into_ring_buffered(
78+
p.DMA1_CH1,
79+
&mut dma_buf,
80+
Irqs,
81+
sequence,
82+
TIM1_TRGO2, // Timer 1 TRGO2 as trigger source
83+
Exten::RISING_EDGE, // Trigger on rising edge (can also use FALLING_EDGE or BOTH_EDGES)
84+
);
85+
86+
// Start ADC conversions and DMA transfer
87+
ring_buffered_adc.start();
88+
89+
info!("ADC configured with TIM1 trigger at 10 Hz");
90+
info!("Reading 3 channels: VrefInt, Temperature, PA0");
91+
92+
// Buffer to read samples - must be half the size of dma_buf
93+
let mut data = [0u16; 6]; // 2 complete samples
94+
95+
loop {
96+
match ring_buffered_adc.read(&mut data).await {
97+
Ok(remaining) => {
98+
// Data contains interleaved samples: [vref0, temp0, pa0_0, vref1, temp1, pa0_1]
99+
info!("Sample 1: VrefInt={}, Temp={}, PA0={}", data[0], data[1], data[2]);
100+
info!("Sample 2: VrefInt={}, Temp={}, PA0={}", data[3], data[4], data[5]);
101+
info!("Remaining samples in buffer: {}", remaining);
102+
103+
// Convert VrefInt to millivolts (example calculation)
104+
let vdda_mv = (3300 * 1210) / data[0] as u32; // Assuming VREFINT_CAL = 1210
105+
info!("Estimated VDDA: {} mV", vdda_mv);
106+
}
107+
Err(e) => {
108+
error!("DMA error: {:?}", e);
109+
ring_buffered_adc.clear();
110+
}
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)