diff options
| author | Iris Artin <[email protected]> | 2025-06-22 00:05:49 -0400 |
|---|---|---|
| committer | Iris Artin <[email protected]> | 2025-06-26 21:07:12 -0400 |
| commit | 440b94aecf5964aeda192eb8bb5d1d2b8648e7e4 (patch) | |
| tree | 74576febc4b65f5c761f8805dd5f7a4a7bd61675 | |
| parent | 206a324cf4d612122356fb350b4a3b56391d6f20 (diff) | |
Added STM32 ADCv1 analog watchdog implementation
| -rw-r--r-- | embassy-stm32/src/adc/v1.rs | 23 | ||||
| -rw-r--r-- | embassy-stm32/src/adc/watchdog_v1.rs | 188 | ||||
| -rw-r--r-- | examples/stm32f0/src/bin/adc-watchdog.rs | 34 |
3 files changed, 241 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; | |||
| 11 | use crate::peripherals::ADC1; | 11 | use crate::peripherals::ADC1; |
| 12 | use crate::{interrupt, rcc, Peri}; | 12 | use crate::{interrupt, rcc, Peri}; |
| 13 | 13 | ||
| 14 | mod watchdog_v1; | ||
| 15 | pub use watchdog_v1::WatchdogChannels; | ||
| 16 | |||
| 14 | pub const VDDA_CALIB_MV: u32 = 3300; | 17 | pub const VDDA_CALIB_MV: u32 = 3300; |
| 15 | pub const VREF_INT: u32 = 1230; | 18 | pub const VREF_INT: u32 = 1230; |
| 16 | 19 | ||
| @@ -21,8 +24,15 @@ pub struct InterruptHandler<T: Instance> { | |||
| 21 | 24 | ||
| 22 | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | 25 | impl<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 | ||
| 191 | impl<'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 | |||
| 210 | impl<'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 @@ | |||
| 1 | use core::future::poll_fn; | ||
| 2 | use core::task::Poll; | ||
| 3 | |||
| 4 | use stm32_metapac::adc::vals::{Align, Awdsgl, Res}; | ||
| 5 | |||
| 6 | use crate::adc::{Adc, AdcChannel, Instance}; | ||
| 7 | |||
| 8 | /// This enum is passed into `Adc::init_watchdog` to specify the channels for the watchdog to monitor | ||
| 9 | pub enum WatchdogChannels { | ||
| 10 | // Single channel identified by index | ||
| 11 | Single(u8), | ||
| 12 | // Multiple channels identified by mask | ||
| 13 | Multiple(u16), | ||
| 14 | } | ||
| 15 | |||
| 16 | impl 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 | |||
| 31 | impl<'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 | } | ||
diff --git a/examples/stm32f0/src/bin/adc-watchdog.rs b/examples/stm32f0/src/bin/adc-watchdog.rs new file mode 100644 index 000000000..ff98aac8e --- /dev/null +++ b/examples/stm32f0/src/bin/adc-watchdog.rs | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::*; | ||
| 5 | use embassy_executor::Spawner; | ||
| 6 | use embassy_stm32::adc::{self, Adc, WatchdogChannels}; | ||
| 7 | use embassy_stm32::bind_interrupts; | ||
| 8 | use embassy_stm32::peripherals::ADC1; | ||
| 9 | use {defmt_rtt as _, panic_probe as _}; | ||
| 10 | |||
| 11 | bind_interrupts!(struct Irqs { | ||
| 12 | ADC1_COMP => adc::InterruptHandler<ADC1>; | ||
| 13 | }); | ||
| 14 | |||
| 15 | #[embassy_executor::main] | ||
| 16 | async fn main(_spawner: Spawner) { | ||
| 17 | let p = embassy_stm32::init(Default::default()); | ||
| 18 | info!("ADC watchdog example"); | ||
| 19 | |||
| 20 | let mut adc = Adc::new(p.ADC1, Irqs); | ||
| 21 | let pin = p.PC1; | ||
| 22 | |||
| 23 | loop { | ||
| 24 | // Wait for pin to go high | ||
| 25 | adc.init_watchdog(WatchdogChannels::from_channel(&pin), 0, 0x07F); | ||
| 26 | let v_high = adc.monitor_watchdog().await; | ||
| 27 | info!("ADC sample is high {}", v_high); | ||
| 28 | |||
| 29 | // Wait for pin to go low | ||
| 30 | adc.init_watchdog(WatchdogChannels::from_channel(&pin), 0x01f, 0xFFF); | ||
| 31 | let v_low = adc.monitor_watchdog().await; | ||
| 32 | info!("ADC sample is low {}", v_low); | ||
| 33 | } | ||
| 34 | } | ||
