aboutsummaryrefslogtreecommitdiff
path: root/embassy-stm32
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2025-07-03 14:10:31 +0000
committerGitHub <[email protected]>2025-07-03 14:10:31 +0000
commit4727c07a0830db45fb4424e2c861c6c44efa4fb1 (patch)
treee7a962e4c87840b46ee9d81e8b383841aafb7158 /embassy-stm32
parent9eab8a98b874dbfccd5a4b2afe5c3770018c169f (diff)
parent440b94aecf5964aeda192eb8bb5d1d2b8648e7e4 (diff)
Merge pull request #4330 from innermatrix/stm32-adc-v1-watchdog
Added STM32 ADCv1 analog watchdog implementation
Diffstat (limited to 'embassy-stm32')
-rw-r--r--embassy-stm32/src/adc/v1.rs23
-rw-r--r--embassy-stm32/src/adc/watchdog_v1.rs188
2 files changed, 207 insertions, 4 deletions
diff --git a/embassy-stm32/src/adc/v1.rs b/embassy-stm32/src/adc/v1.rs
index fb6f5b7d0..7fe502da0 100644
--- a/embassy-stm32/src/adc/v1.rs
+++ b/embassy-stm32/src/adc/v1.rs
@@ -11,6 +11,9 @@ use crate::interrupt::typelevel::Interrupt;
11use crate::peripherals::ADC1; 11use crate::peripherals::ADC1;
12use crate::{interrupt, rcc, Peri}; 12use crate::{interrupt, rcc, Peri};
13 13
14mod watchdog_v1;
15pub use watchdog_v1::WatchdogChannels;
16
14pub const VDDA_CALIB_MV: u32 = 3300; 17pub const VDDA_CALIB_MV: u32 = 3300;
15pub const VREF_INT: u32 = 1230; 18pub const VREF_INT: u32 = 1230;
16 19
@@ -21,8 +24,15 @@ pub struct InterruptHandler<T: Instance> {
21 24
22impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { 25impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
23 unsafe fn on_interrupt() { 26 unsafe fn on_interrupt() {
24 if T::regs().isr().read().eoc() { 27 let isr = T::regs().isr().read();
28 let ier = T::regs().ier().read();
29 if ier.eocie() && isr.eoc() {
30 // eocie is set during adc.read()
25 T::regs().ier().modify(|w| w.set_eocie(false)); 31 T::regs().ier().modify(|w| w.set_eocie(false));
32 } else if ier.awdie() && isr.awd() {
33 // awdie is set during adc.monitor_watchdog()
34 T::regs().cr().read().set_adstp(true);
35 T::regs().ier().modify(|w| w.set_awdie(false));
26 } else { 36 } else {
27 return; 37 return;
28 } 38 }
@@ -186,16 +196,21 @@ impl<'d, T: Instance> Adc<'d, T> {
186 196
187 T::regs().dr().read().data() 197 T::regs().dr().read().data()
188 } 198 }
189}
190 199
191impl<'d, T: Instance> Drop for Adc<'d, T> { 200 fn teardown_adc() {
192 fn drop(&mut self) {
193 // A.7.3 ADC disable code example 201 // A.7.3 ADC disable code example
194 T::regs().cr().modify(|reg| reg.set_adstp(true)); 202 T::regs().cr().modify(|reg| reg.set_adstp(true));
195 while T::regs().cr().read().adstp() {} 203 while T::regs().cr().read().adstp() {}
196 204
197 T::regs().cr().modify(|reg| reg.set_addis(true)); 205 T::regs().cr().modify(|reg| reg.set_addis(true));
198 while T::regs().cr().read().aden() {} 206 while T::regs().cr().read().aden() {}
207 }
208}
209
210impl<'d, T: Instance> Drop for Adc<'d, T> {
211 fn drop(&mut self) {
212 Self::teardown_adc();
213 Self::teardown_awd();
199 214
200 rcc::disable::<T>(); 215 rcc::disable::<T>();
201 } 216 }
diff --git a/embassy-stm32/src/adc/watchdog_v1.rs b/embassy-stm32/src/adc/watchdog_v1.rs
new file mode 100644
index 000000000..bbe8e1971
--- /dev/null
+++ b/embassy-stm32/src/adc/watchdog_v1.rs
@@ -0,0 +1,188 @@
1use core::future::poll_fn;
2use core::task::Poll;
3
4use stm32_metapac::adc::vals::{Align, Awdsgl, Res};
5
6use crate::adc::{Adc, AdcChannel, Instance};
7
8/// This enum is passed into `Adc::init_watchdog` to specify the channels for the watchdog to monitor
9pub enum WatchdogChannels {
10 // Single channel identified by index
11 Single(u8),
12 // Multiple channels identified by mask
13 Multiple(u16),
14}
15
16impl WatchdogChannels {
17 pub fn from_channel<T>(channel: &impl AdcChannel<T>) -> Self {
18 Self::Single(channel.channel())
19 }
20
21 pub fn add_channel<T>(self, channel: &impl AdcChannel<T>) -> Self {
22 WatchdogChannels::Multiple(
23 (match self {
24 WatchdogChannels::Single(ch) => 1 << ch,
25 WatchdogChannels::Multiple(ch) => ch,
26 }) | 1 << channel.channel(),
27 )
28 }
29}
30
31impl<'d, T: Instance> Adc<'d, T> {
32 /// Configure the analog window watchdog to monitor one or more ADC channels
33 ///
34 /// `high_threshold` and `low_threshold` are expressed in the same way as ADC results. The format
35 /// depends on the values of CFGR1.ALIGN and CFGR1.RES.
36 pub fn init_watchdog(&mut self, channels: WatchdogChannels, low_threshold: u16, high_threshold: u16) {
37 Self::stop_awd();
38
39 match channels {
40 WatchdogChannels::Single(ch) => {
41 T::regs().chselr().modify(|w| {
42 w.set_chsel_x(ch.into(), true);
43 });
44 T::regs().cfgr1().modify(|w| {
45 w.set_awdch(ch);
46 w.set_awdsgl(Awdsgl::SINGLE_CHANNEL)
47 });
48 }
49 WatchdogChannels::Multiple(ch) => {
50 T::regs().chselr().modify(|w| w.0 = ch.into());
51 T::regs().cfgr1().modify(|w| {
52 w.set_awdch(0);
53 w.set_awdsgl(Awdsgl::ALL_CHANNELS)
54 });
55 }
56 }
57
58 Self::set_watchdog_thresholds(low_threshold, high_threshold);
59 Self::setup_awd();
60 }
61
62 /// Monitor the voltage on the selected channels; return when it crosses the thresholds.
63 ///
64 /// ```rust,ignore
65 /// // Wait for pin to go high
66 /// adc.init_watchdog(WatchdogChannels::from_channel(&pin), 0, 0x07F);
67 /// let v_high = adc.monitor_watchdog().await;
68 /// info!("ADC sample is high {}", v_high);
69 /// ```
70 pub async fn monitor_watchdog(&mut self) -> u16 {
71 assert!(
72 match T::regs().cfgr1().read().awdsgl() {
73 Awdsgl::SINGLE_CHANNEL => T::regs().cfgr1().read().awdch() != 0,
74 Awdsgl::ALL_CHANNELS => T::regs().cfgr1().read().awdch() == 0,
75 },
76 "`set_channel` should be called before `monitor`",
77 );
78 assert!(T::regs().chselr().read().0 != 0);
79 T::regs().smpr().modify(|reg| reg.set_smp(self.sample_time.into()));
80 Self::start_awd();
81
82 let sample = poll_fn(|cx| {
83 T::state().waker.register(cx.waker());
84
85 if T::regs().isr().read().awd() {
86 Poll::Ready(T::regs().dr().read().data())
87 } else {
88 Poll::Pending
89 }
90 })
91 .await;
92
93 self.stop_watchdog();
94 sample
95 }
96
97 /// Stop monitoring the selected channels
98 pub fn stop_watchdog(&mut self) {
99 Self::stop_awd();
100 }
101
102 fn set_watchdog_thresholds(low_threshold: u16, high_threshold: u16) {
103 // This function takes `high_threshold` and `low_threshold` in the same alignment and resolution
104 // as ADC results, and programs them into ADC_DR. Because ADC_DR is always right-aligned on 12 bits,
105 // some bit-shifting may be necessary. See more in table 47 §13.7.1 Analog Watchdog Comparison
106
107 // Verify that the thresholds are in the correct bit positions according to alignment and resolution
108 let threshold_mask = match (T::regs().cfgr1().read().align(), T::regs().cfgr1().read().res()) {
109 (Align::LEFT, Res::BITS6) => 0x00FC,
110 (Align::LEFT, Res::BITS8) => 0xFF00,
111 (Align::LEFT, Res::BITS10) => 0xFFC0,
112 (Align::LEFT, Res::BITS12) => 0xFFF0,
113 (Align::RIGHT, Res::BITS6) => 0x003F,
114 (Align::RIGHT, Res::BITS8) => 0x00FF,
115 (Align::RIGHT, Res::BITS10) => 0x03FF,
116 (Align::RIGHT, Res::BITS12) => 0x0FFF,
117 };
118 assert!(
119 high_threshold & !threshold_mask == 0,
120 "High threshold {:x} is invalid — only bits {:x} are allowed",
121 high_threshold,
122 threshold_mask
123 );
124 assert!(
125 low_threshold & !threshold_mask == 0,
126 "Low threshold {:x} is invalid — only bits {:x} are allowed",
127 low_threshold,
128 threshold_mask
129 );
130
131 T::regs().tr().modify(|w| {
132 w.set_lt(low_threshold << threshold_mask.leading_zeros() >> 4);
133 w.set_ht(high_threshold << threshold_mask.leading_zeros() >> 4);
134 })
135 }
136
137 fn setup_awd() {
138 // Configure AWD
139 assert!(!T::regs().cr().read().adstart());
140 T::regs().cfgr1().modify(|w| w.set_awden(true));
141 }
142
143 fn start_awd() {
144 // Clear AWD interrupt flag
145 while T::regs().isr().read().awd() {
146 T::regs().isr().modify(|regs| {
147 regs.set_awd(true);
148 })
149 }
150
151 // Enable AWD interrupt
152 assert!(!T::regs().cr().read().adstart());
153 T::regs().ier().modify(|w| {
154 w.set_eocie(false);
155 w.set_awdie(true)
156 });
157
158 // Start conversion
159 T::regs().cfgr1().modify(|w| w.set_cont(true));
160 T::regs().cr().modify(|w| w.set_adstart(true));
161 }
162
163 fn stop_awd() {
164 // Stop conversion
165 while T::regs().cr().read().addis() {}
166 if T::regs().cr().read().adstart() {
167 T::regs().cr().write(|x| x.set_adstp(true));
168 while T::regs().cr().read().adstp() {}
169 }
170 T::regs().cfgr1().modify(|w| w.set_cont(false));
171
172 // Disable AWD interrupt
173 assert!(!T::regs().cr().read().adstart());
174 T::regs().ier().modify(|w| w.set_awdie(false));
175
176 // Clear AWD interrupt flag
177 while T::regs().isr().read().awd() {
178 T::regs().isr().modify(|regs| {
179 regs.set_awd(true);
180 })
181 }
182 }
183
184 pub(crate) fn teardown_awd() {
185 Self::stop_awd();
186 T::regs().cfgr1().modify(|w| w.set_awden(false));
187 }
188}