aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPer Rosengren <[email protected]>2025-08-25 23:05:30 +0200
committerPer Rosengren <[email protected]>2025-08-30 22:01:40 +0200
commita548d7efe3ad963b95183d92c2841fde744cc373 (patch)
treecafaa7d28996212f3e8b2a9a6e6b808e23a09038
parentf86cf87f2f20f723e2ba2fe7d83908a2b3bac2d1 (diff)
Add Adc::new_with_clock() to configure analog clock
Required on STM32WL with default HAL initialization. The function is only available for adc_g0, but all that have clock config should add implementations.
-rw-r--r--embassy-stm32/CHANGELOG.md1
-rw-r--r--embassy-stm32/src/adc/v3.rs87
-rw-r--r--examples/stm32g0/src/bin/adc.rs4
-rw-r--r--examples/stm32g0/src/bin/adc_dma.rs4
-rw-r--r--examples/stm32g0/src/bin/adc_oversampling.rs4
-rw-r--r--examples/stm32wl/src/bin/adc.rs39
6 files changed, 132 insertions, 7 deletions
diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md
index c8ae7a357..5142596e8 100644
--- a/embassy-stm32/CHANGELOG.md
+++ b/embassy-stm32/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20- feat: stm32/adc/v3: allow DMA reads to loop through enable channels 20- feat: stm32/adc/v3: allow DMA reads to loop through enable channels
21- fix: Fix XSPI not disabling alternate bytes when they were previously enabled 21- fix: Fix XSPI not disabling alternate bytes when they were previously enabled
22- fix: Fix stm32h7rs init when using external flash via XSPI 22- fix: Fix stm32h7rs init when using external flash via XSPI
23- feat: Add Adc::new_with_clock() to configure analog clock
23 24
24## 0.3.0 - 2025-08-12 25## 0.3.0 - 2025-08-12
25 26
diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs
index dc1faa4d1..77f24c87f 100644
--- a/embassy-stm32/src/adc/v3.rs
+++ b/embassy-stm32/src/adc/v3.rs
@@ -107,8 +107,50 @@ pub enum Averaging {
107 Samples128, 107 Samples128,
108 Samples256, 108 Samples256,
109} 109}
110
111cfg_if! { if #[cfg(adc_g0)] {
112
113/// Synchronous PCLK prescaler
114/// * ADC_CFGR2:CKMODE in STM32WL5x
115#[repr(u8)]
116pub enum CkModePclk {
117 DIV1 = 3,
118 DIV2 = 1,
119 DIV4 = 2,
120}
121
122/// Asynchronous ADCCLK prescaler
123/// * ADC_CCR:PRESC in STM32WL5x
124#[repr(u8)]
125pub enum Presc {
126 DIV1,
127 DIV2,
128 DIV4,
129 DIV6,
130 DIV8,
131 DIV10,
132 DIV12,
133 DIV16,
134 DIV32,
135 DIV64,
136 DIV128,
137 DIV256,
138}
139
140/// The analog clock is either the synchronous prescaled PCLK or
141/// the asynchronous prescaled ADCCLK configured by the RCC mux.
142/// The data sheet states the maximum analog clock frequency -
143/// for STM32WL55CC it is 36 MHz.
144pub enum Clock {
145 Sync { div: CkModePclk },
146 Async { div: Presc },
147}
148
149}}
150
110impl<'d, T: Instance> Adc<'d, T> { 151impl<'d, T: Instance> Adc<'d, T> {
111 pub fn new(adc: Peri<'d, T>) -> Self { 152 /// Enable the voltage regulator
153 fn init_regulator() {
112 rcc::enable_and_reset::<T>(); 154 rcc::enable_and_reset::<T>();
113 T::regs().cr().modify(|reg| { 155 T::regs().cr().modify(|reg| {
114 #[cfg(not(any(adc_g0, adc_u0)))] 156 #[cfg(not(any(adc_g0, adc_u0)))]
@@ -117,13 +159,17 @@ impl<'d, T: Instance> Adc<'d, T> {
117 }); 159 });
118 160
119 // If this is false then each ADC_CHSELR bit enables an input channel. 161 // If this is false then each ADC_CHSELR bit enables an input channel.
162 // This is the reset value, so has no effect.
120 #[cfg(any(adc_g0, adc_u0))] 163 #[cfg(any(adc_g0, adc_u0))]
121 T::regs().cfgr1().modify(|reg| { 164 T::regs().cfgr1().modify(|reg| {
122 reg.set_chselrmod(false); 165 reg.set_chselrmod(false);
123 }); 166 });
124 167
125 blocking_delay_us(20); 168 blocking_delay_us(20);
169 }
126 170
171 /// Calibrate to remove conversion offset
172 fn init_calibrate() {
127 T::regs().cr().modify(|reg| { 173 T::regs().cr().modify(|reg| {
128 reg.set_adcal(true); 174 reg.set_adcal(true);
129 }); 175 });
@@ -133,6 +179,45 @@ impl<'d, T: Instance> Adc<'d, T> {
133 } 179 }
134 180
135 blocking_delay_us(1); 181 blocking_delay_us(1);
182 }
183
184 /// Initialize the ADC leaving any analog clock at reset value.
185 /// For G0 and WL, this is the async clock without prescaler.
186 pub fn new(adc: Peri<'d, T>) -> Self {
187 Self::init_regulator();
188 Self::init_calibrate();
189 Self {
190 adc,
191 sample_time: SampleTime::from_bits(0),
192 }
193 }
194
195 #[cfg(adc_g0)]
196 /// Initialize ADC with explicit clock for the analog ADC
197 pub fn new_with_clock(adc: Peri<'d, T>, clock: Clock) -> Self {
198 Self::init_regulator();
199
200 #[cfg(any(stm32wl5x))]
201 {
202 // Reset value 0 is actually _No clock selected_ in the STM32WL5x reference manual
203 let async_clock_available = pac::RCC.ccipr().read().adcsel() != pac::rcc::vals::Adcsel::_RESERVED_0;
204 match clock {
205 Clock::Async { div: _ } => {
206 assert!(async_clock_available);
207 }
208 Clock::Sync { div: _ } => {
209 if async_clock_available {
210 warn!("Not using configured ADC clock");
211 }
212 }
213 }
214 }
215 match clock {
216 Clock::Async { div } => T::regs().ccr().modify(|reg| reg.set_presc(div as u8)),
217 Clock::Sync { div } => T::regs().cfgr2().modify(|reg| reg.set_ckmode(div as u8)),
218 }
219
220 Self::init_calibrate();
136 221
137 Self { 222 Self {
138 adc, 223 adc,
diff --git a/examples/stm32g0/src/bin/adc.rs b/examples/stm32g0/src/bin/adc.rs
index 6c7f3b48a..7d8653ef2 100644
--- a/examples/stm32g0/src/bin/adc.rs
+++ b/examples/stm32g0/src/bin/adc.rs
@@ -3,7 +3,7 @@
3 3
4use defmt::*; 4use defmt::*;
5use embassy_executor::Spawner; 5use embassy_executor::Spawner;
6use embassy_stm32::adc::{Adc, SampleTime}; 6use embassy_stm32::adc::{Adc, Clock, Presc, SampleTime};
7use embassy_time::Timer; 7use embassy_time::Timer;
8use {defmt_rtt as _, panic_probe as _}; 8use {defmt_rtt as _, panic_probe as _};
9 9
@@ -12,7 +12,7 @@ async fn main(_spawner: Spawner) {
12 let p = embassy_stm32::init(Default::default()); 12 let p = embassy_stm32::init(Default::default());
13 info!("Hello World!"); 13 info!("Hello World!");
14 14
15 let mut adc = Adc::new(p.ADC1); 15 let mut adc = Adc::new_with_clock(p.ADC1, Clock::Async { div: Presc::DIV1 });
16 adc.set_sample_time(SampleTime::CYCLES79_5); 16 adc.set_sample_time(SampleTime::CYCLES79_5);
17 let mut pin = p.PA1; 17 let mut pin = p.PA1;
18 18
diff --git a/examples/stm32g0/src/bin/adc_dma.rs b/examples/stm32g0/src/bin/adc_dma.rs
index d7515933c..8266a6d83 100644
--- a/examples/stm32g0/src/bin/adc_dma.rs
+++ b/examples/stm32g0/src/bin/adc_dma.rs
@@ -3,7 +3,7 @@
3 3
4use defmt::*; 4use defmt::*;
5use embassy_executor::Spawner; 5use embassy_executor::Spawner;
6use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; 6use embassy_stm32::adc::{Adc, AdcChannel as _, Clock, Presc, SampleTime};
7use embassy_time::Timer; 7use embassy_time::Timer;
8use {defmt_rtt as _, panic_probe as _}; 8use {defmt_rtt as _, panic_probe as _};
9 9
@@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) {
17 17
18 info!("Hello World!"); 18 info!("Hello World!");
19 19
20 let mut adc = Adc::new(p.ADC1); 20 let mut adc = Adc::new_with_clock(p.ADC1, Clock::Async { div: Presc::DIV1 });
21 21
22 let mut dma = p.DMA1_CH1; 22 let mut dma = p.DMA1_CH1;
23 let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); 23 let mut vrefint_channel = adc.enable_vrefint().degrade_adc();
diff --git a/examples/stm32g0/src/bin/adc_oversampling.rs b/examples/stm32g0/src/bin/adc_oversampling.rs
index 9c5dd872a..bc49fac83 100644
--- a/examples/stm32g0/src/bin/adc_oversampling.rs
+++ b/examples/stm32g0/src/bin/adc_oversampling.rs
@@ -7,7 +7,7 @@
7 7
8use defmt::*; 8use defmt::*;
9use embassy_executor::Spawner; 9use embassy_executor::Spawner;
10use embassy_stm32::adc::{Adc, SampleTime}; 10use embassy_stm32::adc::{Adc, Clock, Presc, SampleTime};
11use embassy_time::Timer; 11use embassy_time::Timer;
12use {defmt_rtt as _, panic_probe as _}; 12use {defmt_rtt as _, panic_probe as _};
13 13
@@ -16,7 +16,7 @@ async fn main(_spawner: Spawner) {
16 let p = embassy_stm32::init(Default::default()); 16 let p = embassy_stm32::init(Default::default());
17 info!("Adc oversample test"); 17 info!("Adc oversample test");
18 18
19 let mut adc = Adc::new(p.ADC1); 19 let mut adc = Adc::new_with_clock(p.ADC1, Clock::Async { div: Presc::DIV1 });
20 adc.set_sample_time(SampleTime::CYCLES1_5); 20 adc.set_sample_time(SampleTime::CYCLES1_5);
21 let mut pin = p.PA1; 21 let mut pin = p.PA1;
22 22
diff --git a/examples/stm32wl/src/bin/adc.rs b/examples/stm32wl/src/bin/adc.rs
new file mode 100644
index 000000000..118f02ae1
--- /dev/null
+++ b/examples/stm32wl/src/bin/adc.rs
@@ -0,0 +1,39 @@
1#![no_std]
2#![no_main]
3
4use core::mem::MaybeUninit;
5
6use defmt::*;
7use embassy_executor::Spawner;
8use embassy_stm32::adc::{Adc, CkModePclk, Clock, SampleTime};
9use embassy_stm32::SharedData;
10use embassy_time::Timer;
11use {defmt_rtt as _, panic_probe as _};
12
13static SHARED_DATA: MaybeUninit<SharedData> = MaybeUninit::uninit();
14
15#[embassy_executor::main]
16async fn main(_spawner: Spawner) {
17 let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA);
18 info!("Hello World!");
19
20 let mut adc = Adc::new_with_clock(p.ADC1, Clock::Sync { div: CkModePclk::DIV1 });
21 adc.set_sample_time(SampleTime::CYCLES79_5);
22 let mut pin = p.PB2;
23
24 let mut vrefint = adc.enable_vrefint();
25 let vrefint_sample = adc.blocking_read(&mut vrefint);
26 let convert_to_millivolts = |sample| {
27 // From https://www.st.com/resource/en/datasheet/stm32g031g8.pdf
28 // 6.3.3 Embedded internal reference voltage
29 const VREFINT_MV: u32 = 1212; // mV
30
31 (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16
32 };
33
34 loop {
35 let v = adc.blocking_read(&mut pin);
36 info!("--> {} - {} mV", v, convert_to_millivolts(v));
37 Timer::after_millis(100).await;
38 }
39}