Added STM32 ADCv1 analog watchdog implementation

This commit is contained in:
Iris Artin 2025-06-22 00:05:49 -04:00
parent 206a324cf4
commit 440b94aecf
No known key found for this signature in database
GPG Key ID: DAF97A3F835DDD90
3 changed files with 241 additions and 4 deletions

View File

@ -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<T: Instance> {
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
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::<T>();
}

View File

@ -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<T>(channel: &impl AdcChannel<T>) -> Self {
Self::Single(channel.channel())
}
pub fn add_channel<T>(self, channel: &impl AdcChannel<T>) -> 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));
}
}

View File

@ -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<ADC1>;
});
#[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);
}
}