From 8fae6f5a3f7055dc8250cd8926624010b14db6dc Mon Sep 17 00:00:00 2001 From: Michael Kefeder Date: Sun, 28 Sep 2025 18:32:40 +0200 Subject: Reset SAADC in Drop impl for nrf52, workaround for anomaly 241. --- embassy-nrf/CHANGELOG.md | 1 + embassy-nrf/src/saadc.rs | 13 ++++++ examples/nrf52810/src/bin/saadc_lowpower.rs | 62 +++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 examples/nrf52810/src/bin/saadc_lowpower.rs diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index b8d03a1f8..3ad3c8005 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - changed: impl Drop for Timer - added: expose `regs` for timer driver - added: timer driver CC `clear_events` method +- changed: Saadc reset in Drop impl, anomaly 241 - high power usage ## 0.7.0 - 2025-08-26 diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 92b6fb01f..d84246572 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -457,6 +457,19 @@ impl<'d> Saadc<'d, 1> { impl<'d, const N: usize> Drop for Saadc<'d, N> { fn drop(&mut self) { + // Reset of SAADC. + // + // This is needed when more than one pin is sampled to avoid needless power consumption. + // More information can be found in [nrf52 Anomaly 241](https://docs.nordicsemi.com/bundle/errata_nRF52810_Rev1/page/ERR/nRF52810/Rev1/latest/anomaly_810_241.html). + // The workaround seems like it copies the configuration before reset and reapplies it after. + // This method consumes the instance forcing a reconfiguration at compile time, hence we only + // call what is the reset portion of the workaround. + #[cfg(feature = "_nrf52")] + { + unsafe { core::ptr::write_volatile(0x40007FFC as *mut u32, 0) } + unsafe { core::ptr::read_volatile(0x40007FFC as *const ()) } + unsafe { core::ptr::write_volatile(0x40007FFC as *mut u32, 1) } + } let r = Self::regs(); r.enable().write(|w| w.set_enable(false)); for i in 0..N { diff --git a/examples/nrf52810/src/bin/saadc_lowpower.rs b/examples/nrf52810/src/bin/saadc_lowpower.rs new file mode 100644 index 000000000..d7e7f09a4 --- /dev/null +++ b/examples/nrf52810/src/bin/saadc_lowpower.rs @@ -0,0 +1,62 @@ +//! Run SAADC on multiple pins only every 3rd time, to show anomaly 241 workaround. +//! +//! To correctly measure the MCU current on the NRF52DK follow the instructions +//! +//! otherwise you will measure the whole board, including the segger j-link chip for example + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::saadc::{Oversample, Saadc}; +use embassy_nrf::{bind_interrupts, saadc}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SAADC => saadc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + + // For PPK2 digital channel plot to track when SAADC is on/off. + let mut ppk2_d0 = Output::new(p.P0_27, Level::Low, OutputDrive::Standard); + let mut num_loops: usize = 0; + loop { + num_loops += 1; + if num_loops.is_multiple_of(3) { + ppk2_d0.set_high(); + let battery_pin = p.P0_02.reborrow(); + let sensor1_pin = p.P0_03.reborrow(); + let mut adc_config = saadc::Config::default(); + adc_config.oversample = Oversample::OVER4X; + let battery = saadc::ChannelConfig::single_ended(battery_pin); + let sensor1 = saadc::ChannelConfig::single_ended(sensor1_pin); + let mut saadc = Saadc::new(p.SAADC.reborrow(), Irqs, adc_config, [battery, sensor1]); + // Indicated: wait for ADC calibration. + saadc.calibrate().await; + let mut buf = [0; 2]; + info!("sampling..."); + saadc.sample(&mut buf).await; + info!("data: {:x}", buf); + + // Sleep to show the high power usage on the plot, even though sampling is done. + Timer::after_millis(100).await; + ppk2_d0.set_low(); + // disable the following line to show the anomaly on the power profiler plot. + core::mem::drop(saadc); + // Sleep to show the power usage when drop did not happen. + Timer::after_millis(100).await; + // worst case drop happens here + } else { + info!("waiting"); + } + // Sleep for 1 second. The executor ensures the core sleeps with a WFE when it has nothing to do. + // During this sleep, the nRF chip should only use ~3uA + Timer::after_secs(1).await; + } +} -- cgit