From 440b94aecf5964aeda192eb8bb5d1d2b8648e7e4 Mon Sep 17 00:00:00 2001 From: Iris Artin Date: Sun, 22 Jun 2025 00:05:49 -0400 Subject: [PATCH] Added STM32 ADCv1 analog watchdog implementation --- embassy-stm32/src/adc/v1.rs | 23 ++- embassy-stm32/src/adc/watchdog_v1.rs | 188 +++++++++++++++++++++++ examples/stm32f0/src/bin/adc-watchdog.rs | 34 ++++ 3 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 embassy-stm32/src/adc/watchdog_v1.rs create mode 100644 examples/stm32f0/src/bin/adc-watchdog.rs 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; use crate::peripherals::ADC1; use crate::{interrupt, rcc, Peri}; +mod watchdog_v1; +pub use watchdog_v1::WatchdogChannels; + pub const VDDA_CALIB_MV: u32 = 3300; pub const VREF_INT: u32 = 1230; @@ -21,8 +24,15 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - if T::regs().isr().read().eoc() { + let isr = T::regs().isr().read(); + let ier = T::regs().ier().read(); + if ier.eocie() && isr.eoc() { + // eocie is set during adc.read() T::regs().ier().modify(|w| w.set_eocie(false)); + } else if ier.awdie() && isr.awd() { + // awdie is set during adc.monitor_watchdog() + T::regs().cr().read().set_adstp(true); + T::regs().ier().modify(|w| w.set_awdie(false)); } else { return; } @@ -186,16 +196,21 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().dr().read().data() } -} -impl<'d, T: Instance> Drop for Adc<'d, T> { - fn drop(&mut self) { + fn teardown_adc() { // A.7.3 ADC disable code example T::regs().cr().modify(|reg| reg.set_adstp(true)); while T::regs().cr().read().adstp() {} T::regs().cr().modify(|reg| reg.set_addis(true)); while T::regs().cr().read().aden() {} + } +} + +impl<'d, T: Instance> Drop for Adc<'d, T> { + fn drop(&mut self) { + Self::teardown_adc(); + Self::teardown_awd(); rcc::disable::(); } 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 @@ +use core::future::poll_fn; +use core::task::Poll; + +use stm32_metapac::adc::vals::{Align, Awdsgl, Res}; + +use crate::adc::{Adc, AdcChannel, Instance}; + +/// This enum is passed into `Adc::init_watchdog` to specify the channels for the watchdog to monitor +pub enum WatchdogChannels { + // Single channel identified by index + Single(u8), + // Multiple channels identified by mask + Multiple(u16), +} + +impl WatchdogChannels { + pub fn from_channel(channel: &impl AdcChannel) -> Self { + Self::Single(channel.channel()) + } + + pub fn add_channel(self, channel: &impl AdcChannel) -> Self { + WatchdogChannels::Multiple( + (match self { + WatchdogChannels::Single(ch) => 1 << ch, + WatchdogChannels::Multiple(ch) => ch, + }) | 1 << channel.channel(), + ) + } +} + +impl<'d, T: Instance> Adc<'d, T> { + /// Configure the analog window watchdog to monitor one or more ADC channels + /// + /// `high_threshold` and `low_threshold` are expressed in the same way as ADC results. The format + /// depends on the values of CFGR1.ALIGN and CFGR1.RES. + pub fn init_watchdog(&mut self, channels: WatchdogChannels, low_threshold: u16, high_threshold: u16) { + Self::stop_awd(); + + match channels { + WatchdogChannels::Single(ch) => { + T::regs().chselr().modify(|w| { + w.set_chsel_x(ch.into(), true); + }); + T::regs().cfgr1().modify(|w| { + w.set_awdch(ch); + w.set_awdsgl(Awdsgl::SINGLE_CHANNEL) + }); + } + WatchdogChannels::Multiple(ch) => { + T::regs().chselr().modify(|w| w.0 = ch.into()); + T::regs().cfgr1().modify(|w| { + w.set_awdch(0); + w.set_awdsgl(Awdsgl::ALL_CHANNELS) + }); + } + } + + Self::set_watchdog_thresholds(low_threshold, high_threshold); + Self::setup_awd(); + } + + /// Monitor the voltage on the selected channels; return when it crosses the thresholds. + /// + /// ```rust,ignore + /// // Wait for pin to go high + /// adc.init_watchdog(WatchdogChannels::from_channel(&pin), 0, 0x07F); + /// let v_high = adc.monitor_watchdog().await; + /// info!("ADC sample is high {}", v_high); + /// ``` + pub async fn monitor_watchdog(&mut self) -> u16 { + assert!( + match T::regs().cfgr1().read().awdsgl() { + Awdsgl::SINGLE_CHANNEL => T::regs().cfgr1().read().awdch() != 0, + Awdsgl::ALL_CHANNELS => T::regs().cfgr1().read().awdch() == 0, + }, + "`set_channel` should be called before `monitor`", + ); + assert!(T::regs().chselr().read().0 != 0); + T::regs().smpr().modify(|reg| reg.set_smp(self.sample_time.into())); + Self::start_awd(); + + let sample = poll_fn(|cx| { + T::state().waker.register(cx.waker()); + + if T::regs().isr().read().awd() { + Poll::Ready(T::regs().dr().read().data()) + } else { + Poll::Pending + } + }) + .await; + + self.stop_watchdog(); + sample + } + + /// Stop monitoring the selected channels + pub fn stop_watchdog(&mut self) { + Self::stop_awd(); + } + + fn set_watchdog_thresholds(low_threshold: u16, high_threshold: u16) { + // This function takes `high_threshold` and `low_threshold` in the same alignment and resolution + // as ADC results, and programs them into ADC_DR. Because ADC_DR is always right-aligned on 12 bits, + // some bit-shifting may be necessary. See more in table 47 §13.7.1 Analog Watchdog Comparison + + // Verify that the thresholds are in the correct bit positions according to alignment and resolution + let threshold_mask = match (T::regs().cfgr1().read().align(), T::regs().cfgr1().read().res()) { + (Align::LEFT, Res::BITS6) => 0x00FC, + (Align::LEFT, Res::BITS8) => 0xFF00, + (Align::LEFT, Res::BITS10) => 0xFFC0, + (Align::LEFT, Res::BITS12) => 0xFFF0, + (Align::RIGHT, Res::BITS6) => 0x003F, + (Align::RIGHT, Res::BITS8) => 0x00FF, + (Align::RIGHT, Res::BITS10) => 0x03FF, + (Align::RIGHT, Res::BITS12) => 0x0FFF, + }; + assert!( + high_threshold & !threshold_mask == 0, + "High threshold {:x} is invalid — only bits {:x} are allowed", + high_threshold, + threshold_mask + ); + assert!( + low_threshold & !threshold_mask == 0, + "Low threshold {:x} is invalid — only bits {:x} are allowed", + low_threshold, + threshold_mask + ); + + T::regs().tr().modify(|w| { + w.set_lt(low_threshold << threshold_mask.leading_zeros() >> 4); + w.set_ht(high_threshold << threshold_mask.leading_zeros() >> 4); + }) + } + + fn setup_awd() { + // Configure AWD + assert!(!T::regs().cr().read().adstart()); + T::regs().cfgr1().modify(|w| w.set_awden(true)); + } + + fn start_awd() { + // Clear AWD interrupt flag + while T::regs().isr().read().awd() { + T::regs().isr().modify(|regs| { + regs.set_awd(true); + }) + } + + // Enable AWD interrupt + assert!(!T::regs().cr().read().adstart()); + T::regs().ier().modify(|w| { + w.set_eocie(false); + w.set_awdie(true) + }); + + // Start conversion + T::regs().cfgr1().modify(|w| w.set_cont(true)); + T::regs().cr().modify(|w| w.set_adstart(true)); + } + + fn stop_awd() { + // Stop conversion + while T::regs().cr().read().addis() {} + if T::regs().cr().read().adstart() { + T::regs().cr().write(|x| x.set_adstp(true)); + while T::regs().cr().read().adstp() {} + } + T::regs().cfgr1().modify(|w| w.set_cont(false)); + + // Disable AWD interrupt + assert!(!T::regs().cr().read().adstart()); + T::regs().ier().modify(|w| w.set_awdie(false)); + + // Clear AWD interrupt flag + while T::regs().isr().read().awd() { + T::regs().isr().modify(|regs| { + regs.set_awd(true); + }) + } + } + + pub(crate) fn teardown_awd() { + Self::stop_awd(); + T::regs().cfgr1().modify(|w| w.set_awden(false)); + } +} 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 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{self, Adc, WatchdogChannels}; +use embassy_stm32::bind_interrupts; +use embassy_stm32::peripherals::ADC1; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1_COMP => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("ADC watchdog example"); + + let mut adc = Adc::new(p.ADC1, Irqs); + let pin = p.PC1; + + loop { + // Wait for pin to go high + adc.init_watchdog(WatchdogChannels::from_channel(&pin), 0, 0x07F); + let v_high = adc.monitor_watchdog().await; + info!("ADC sample is high {}", v_high); + + // Wait for pin to go low + adc.init_watchdog(WatchdogChannels::from_channel(&pin), 0x01f, 0xFFF); + let v_low = adc.monitor_watchdog().await; + info!("ADC sample is low {}", v_low); + } +}