//! Successive Approximation Analog-to-Digital Converter (SAADC) driver. #![macro_use] use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; pub(crate) use vals::Psel as InputChannel; use crate::interrupt::InterruptExt; use crate::pac::saadc::vals; use crate::ppi::{ConfigurableChannel, Event, Ppi, Task}; use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::{interrupt, pac, peripherals, Peripheral}; /// SAADC error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error {} /// Interrupt handler. pub struct InterruptHandler { _private: (), } impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { let r = pac::SAADC; if r.events_calibratedone().read() != 0 { r.intenclr().write(|w| w.set_calibratedone(true)); WAKER.wake(); } if r.events_end().read() != 0 { r.intenclr().write(|w| w.set_end(true)); WAKER.wake(); } if r.events_started().read() != 0 { r.intenclr().write(|w| w.set_started(true)); WAKER.wake(); } } } static WAKER: AtomicWaker = AtomicWaker::new(); /// Used to configure the SAADC peripheral. /// /// See the `Default` impl for suitable default values. #[non_exhaustive] pub struct Config { /// Output resolution in bits. pub resolution: Resolution, /// Average 2^`oversample` input samples before transferring the result into memory. pub oversample: Oversample, } impl Default for Config { /// Default configuration for single channel sampling. fn default() -> Self { Self { resolution: Resolution::_12BIT, oversample: Oversample::BYPASS, } } } /// Used to configure an individual SAADC peripheral channel. /// /// Construct using the `single_ended` or `differential` methods. These provide sensible defaults /// for the public fields, which can be overridden if required. #[non_exhaustive] pub struct ChannelConfig<'d> { /// Reference voltage of the SAADC input. pub reference: Reference, /// Gain used to control the effective input range of the SAADC. pub gain: Gain, /// Positive channel resistor control. pub resistor: Resistor, /// Acquisition time in microseconds. pub time: Time, /// Positive channel to sample p_channel: PeripheralRef<'d, AnyInput>, /// An optional negative channel to sample n_channel: Option>, } impl<'d> ChannelConfig<'d> { /// Default configuration for single ended channel sampling. pub fn single_ended(input: impl Peripheral

+ 'd) -> Self { into_ref!(input); Self { reference: Reference::INTERNAL, gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, time: Time::_10US, p_channel: input.map_into(), n_channel: None, } } /// Default configuration for differential channel sampling. pub fn differential( p_input: impl Peripheral

+ 'd, n_input: impl Peripheral

+ 'd, ) -> Self { into_ref!(p_input, n_input); Self { reference: Reference::INTERNAL, gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, time: Time::_10US, p_channel: p_input.map_into(), n_channel: Some(n_input.map_into()), } } } /// Value returned by the SAADC callback, deciding what happens next. #[derive(PartialEq)] pub enum CallbackResult { /// The SAADC should keep sampling and calling the callback. Continue, /// The SAADC should stop sampling, and return. Stop, } /// One-shot and continuous SAADC. pub struct Saadc<'d, const N: usize> { _p: PeripheralRef<'d, peripherals::SAADC>, } impl<'d, const N: usize> Saadc<'d, N> { /// Create a new SAADC driver. pub fn new( saadc: impl Peripheral

+ 'd, _irq: impl interrupt::typelevel::Binding + 'd, config: Config, channel_configs: [ChannelConfig; N], ) -> Self { into_ref!(saadc); let r = pac::SAADC; let Config { resolution, oversample } = config; // Configure channels r.enable().write(|w| w.set_enable(true)); r.resolution().write(|w| w.set_val(resolution.into())); r.oversample().write(|w| w.set_oversample(oversample.into())); for (i, cc) in channel_configs.iter().enumerate() { r.ch(i).pselp().write(|w| w.set_pselp(cc.p_channel.channel())); if let Some(n_channel) = &cc.n_channel { r.ch(i).pseln().write(|w| w.set_pseln(n_channel.channel())); } r.ch(i).config().write(|w| { w.set_refsel(cc.reference.into()); w.set_gain(cc.gain.into()); w.set_tacq(cc.time.into()); w.set_mode(match cc.n_channel { None => vals::ConfigMode::SE, Some(_) => vals::ConfigMode::DIFF, }); w.set_resp(cc.resistor.into()); w.set_resn(vals::Resn::BYPASS); w.set_burst(!matches!(oversample, Oversample::BYPASS)); }); } // Disable all events interrupts r.intenclr().write(|w| w.0 = 0x003F_FFFF); interrupt::SAADC.unpend(); unsafe { interrupt::SAADC.enable() }; Self { _p: saadc } } fn regs() -> pac::saadc::Saadc { pac::SAADC } /// Perform SAADC calibration. Completes when done. pub async fn calibrate(&self) { let r = Self::regs(); // Reset and enable the end event r.events_calibratedone().write_value(0); r.intenset().write(|w| w.set_calibratedone(true)); // Order is important compiler_fence(Ordering::SeqCst); r.tasks_calibrateoffset().write_value(1); // Wait for 'calibratedone' event. poll_fn(|cx| { let r = Self::regs(); WAKER.register(cx.waker()); if r.events_calibratedone().read() != 0 { r.events_calibratedone().write_value(0); return Poll::Ready(()); } Poll::Pending }) .await; } /// One shot sampling. The buffer must be the same size as the number of channels configured. /// The sampling is stopped prior to returning in order to reduce power consumption (power /// consumption remains higher if sampling is not stopped explicitly). Cancellation will /// also cause the sampling to be stopped. pub async fn sample(&mut self, buf: &mut [i16; N]) { // In case the future is dropped, stop the task and wait for it to end. let on_drop = OnDrop::new(Self::stop_sampling_immediately); let r = Self::regs(); // Set up the DMA r.result().ptr().write_value(buf.as_mut_ptr() as u32); r.result().maxcnt().write(|w| w.set_maxcnt(N as _)); // Reset and enable the end event r.events_end().write_value(0); r.intenset().write(|w| w.set_end(true)); // Don't reorder the ADC start event before the previous writes. Hopefully self // wouldn't happen anyway. compiler_fence(Ordering::SeqCst); r.tasks_start().write_value(1); r.tasks_sample().write_value(1); // Wait for 'end' event. poll_fn(|cx| { let r = Self::regs(); WAKER.register(cx.waker()); if r.events_end().read() != 0 { r.events_end().write_value(0); return Poll::Ready(()); } Poll::Pending }) .await; drop(on_drop); } /// Continuous sampling with double buffers. /// /// A TIMER and two PPI peripherals are passed in so that precise sampling /// can be attained. The sampling interval is expressed by selecting a /// timer clock frequency to use along with a counter threshold to be reached. /// For example, 1KHz can be achieved using a frequency of 1MHz and a counter /// threshold of 1000. /// /// A sampler closure is provided that receives the buffer of samples, noting /// that the size of this buffer can be less than the original buffer's size. /// A command is return from the closure that indicates whether the sampling /// should continue or stop. /// /// NOTE: The time spent within the callback supplied should not exceed the time /// taken to acquire the samples into a single buffer. You should measure the /// time taken by the callback and set the sample buffer size accordingly. /// Exceeding this time can lead to samples becoming dropped. /// /// The sampling is stopped prior to returning in order to reduce power consumption (power /// consumption remains higher if sampling is not stopped explicitly), and to /// free the buffers from being used by the peripheral. Cancellation will /// also cause the sampling to be stopped. pub async fn run_task_sampler( &mut self, timer: &mut T, ppi_ch1: &mut impl ConfigurableChannel, ppi_ch2: &mut impl ConfigurableChannel, frequency: Frequency, sample_counter: u32, bufs: &mut [[[i16; N]; N0]; 2], callback: F, ) where F: FnMut(&[[i16; N]]) -> CallbackResult, { let r = Self::regs(); // We want the task start to effectively short with the last one ending so // we don't miss any samples. It'd be great for the SAADC to offer a SHORTS // register instead, but it doesn't, so we must use PPI. let mut start_ppi = Ppi::new_one_to_one( ppi_ch1, Event::from_reg(r.events_end()), Task::from_reg(r.tasks_start()), ); start_ppi.enable(); let timer = Timer::new(timer); timer.set_frequency(frequency); timer.cc(0).write(sample_counter); timer.cc(0).short_compare_clear(); let timer_cc = timer.cc(0); let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer_cc.event_compare(), Task::from_reg(r.tasks_sample())); timer.start(); self.run_sampler( bufs, None, || { sample_ppi.enable(); }, callback, ) .await; } async fn run_sampler( &mut self, bufs: &mut [[[i16; N]; N0]; 2], sample_rate_divisor: Option, mut init: I, mut callback: F, ) where I: FnMut(), F: FnMut(&[[i16; N]]) -> CallbackResult, { // In case the future is dropped, stop the task and wait for it to end. let on_drop = OnDrop::new(Self::stop_sampling_immediately); let r = Self::regs(); // Establish mode and sample rate match sample_rate_divisor { Some(sr) => { r.samplerate().write(|w| { w.set_cc(sr); w.set_mode(vals::SamplerateMode::TIMERS); }); r.tasks_sample().write_value(1); // Need to kick-start the internal timer } None => r.samplerate().write(|w| { w.set_cc(0); w.set_mode(vals::SamplerateMode::TASK); }), } // Set up the initial DMA r.result().ptr().write_value(bufs[0].as_mut_ptr() as u32); r.result().maxcnt().write(|w| w.set_maxcnt((N0 * N) as _)); // Reset and enable the events r.events_end().write_value(0); r.events_started().write_value(0); r.intenset().write(|w| { w.set_end(true); w.set_started(true); }); // Don't reorder the ADC start event before the previous writes. Hopefully self // wouldn't happen anyway. compiler_fence(Ordering::SeqCst); r.tasks_start().write_value(1); let mut inited = false; let mut current_buffer = 0; // Wait for events and complete when the sampler indicates it has had enough. let r = poll_fn(|cx| { let r = Self::regs(); WAKER.register(cx.waker()); if r.events_end().read() != 0 { compiler_fence(Ordering::SeqCst); r.events_end().write_value(0); r.intenset().write(|w| w.set_end(true)); match callback(&bufs[current_buffer]) { CallbackResult::Continue => { let next_buffer = 1 - current_buffer; current_buffer = next_buffer; } CallbackResult::Stop => { return Poll::Ready(()); } } } if r.events_started().read() != 0 { r.events_started().write_value(0); r.intenset().write(|w| w.set_started(true)); if !inited { init(); inited = true; } let next_buffer = 1 - current_buffer; r.result().ptr().write_value(bufs[next_buffer].as_mut_ptr() as u32); } Poll::Pending }) .await; drop(on_drop); r } // Stop sampling and wait for it to stop in a blocking fashion fn stop_sampling_immediately() { let r = Self::regs(); compiler_fence(Ordering::SeqCst); r.events_stopped().write_value(0); r.tasks_stop().write_value(1); while r.events_stopped().read() == 0 {} r.events_stopped().write_value(0); } } impl<'d> Saadc<'d, 1> { /// Continuous sampling on a single channel with double buffers. /// /// The internal clock is to be used with a sample rate expressed as a divisor of /// 16MHz, ranging from 80..2047. For example, 1600 represents a sample rate of 10KHz /// given 16_000_000 / 10_000_000 = 1600. /// /// A sampler closure is provided that receives the buffer of samples, noting /// that the size of this buffer can be less than the original buffer's size. /// A command is return from the closure that indicates whether the sampling /// should continue or stop. pub async fn run_timer_sampler( &mut self, bufs: &mut [[[i16; 1]; N0]; 2], sample_rate_divisor: u16, sampler: S, ) where S: FnMut(&[[i16; 1]]) -> CallbackResult, { self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler).await; } } impl<'d, const N: usize> Drop for Saadc<'d, N> { fn drop(&mut self) { let r = Self::regs(); r.enable().write(|w| w.set_enable(false)); } } impl From for vals::Gain { fn from(gain: Gain) -> Self { match gain { Gain::GAIN1_6 => vals::Gain::GAIN1_6, Gain::GAIN1_5 => vals::Gain::GAIN1_5, Gain::GAIN1_4 => vals::Gain::GAIN1_4, Gain::GAIN1_3 => vals::Gain::GAIN1_3, Gain::GAIN1_2 => vals::Gain::GAIN1_2, Gain::GAIN1 => vals::Gain::GAIN1, Gain::GAIN2 => vals::Gain::GAIN2, Gain::GAIN4 => vals::Gain::GAIN4, } } } /// Gain control #[non_exhaustive] #[derive(Clone, Copy)] pub enum Gain { /// 1/6 GAIN1_6 = 0, /// 1/5 GAIN1_5 = 1, /// 1/4 GAIN1_4 = 2, /// 1/3 GAIN1_3 = 3, /// 1/2 GAIN1_2 = 4, /// 1 GAIN1 = 5, /// 2 GAIN2 = 6, /// 4 GAIN4 = 7, } impl From for vals::Refsel { fn from(reference: Reference) -> Self { match reference { Reference::INTERNAL => vals::Refsel::INTERNAL, Reference::VDD1_4 => vals::Refsel::VDD1_4, } } } /// Reference control #[non_exhaustive] #[derive(Clone, Copy)] pub enum Reference { /// Internal reference (0.6 V) INTERNAL = 0, /// VDD/4 as reference VDD1_4 = 1, } impl From for vals::Resp { fn from(resistor: Resistor) -> Self { match resistor { Resistor::BYPASS => vals::Resp::BYPASS, Resistor::PULLDOWN => vals::Resp::PULLDOWN, Resistor::PULLUP => vals::Resp::PULLUP, Resistor::VDD1_2 => vals::Resp::VDD1_2, } } } /// Positive channel resistor control #[non_exhaustive] #[derive(Clone, Copy)] pub enum Resistor { /// Bypass resistor ladder BYPASS = 0, /// Pull-down to GND PULLDOWN = 1, /// Pull-up to VDD PULLUP = 2, /// Set input at VDD/2 VDD1_2 = 3, } impl From

+ Sized + 'static { /// Convert this SAADC input to a type-erased `AnyInput`. /// /// This allows using several inputs in situations that might require /// them to be the same type, like putting them in an array. fn degrade_saadc(self) -> AnyInput { AnyInput { channel: self.channel(), } } } /// A type-erased SAADC input. /// /// This allows using several inputs in situations that might require /// them to be the same type, like putting them in an array. pub struct AnyInput { channel: InputChannel, } impl_peripheral!(AnyInput); impl SealedInput for AnyInput { fn channel(&self) -> InputChannel { self.channel } } impl Input for AnyInput {} macro_rules! impl_saadc_input { ($pin:ident, $ch:ident) => { impl_saadc_input!(@local, crate::peripherals::$pin, $ch); }; (@local, $pin:ty, $ch:ident) => { impl crate::saadc::SealedInput for $pin { fn channel(&self) -> crate::saadc::InputChannel { crate::saadc::InputChannel::$ch } } impl crate::saadc::Input for $pin {} impl From<$pin> for crate::saadc::AnyInput { fn from(val: $pin) -> Self { crate::saadc::Input::degrade_saadc(val) } } }; } /// A dummy `Input` pin implementation for SAADC peripheral sampling from the /// internal voltage. pub struct VddInput; impl_peripheral!(VddInput); #[cfg(not(feature = "_nrf91"))] impl_saadc_input!(@local, VddInput, VDD); #[cfg(feature = "_nrf91")] impl_saadc_input!(@local, VddInput, VDD_GPIO); /// A dummy `Input` pin implementation for SAADC peripheral sampling from the /// VDDH / 5 voltage. #[cfg(any(feature = "_nrf5340-app", feature = "nrf52833", feature = "nrf52840"))] pub struct VddhDiv5Input; #[cfg(any(feature = "_nrf5340-app", feature = "nrf52833", feature = "nrf52840"))] impl_peripheral!(VddhDiv5Input); #[cfg(any(feature = "_nrf5340-app", feature = "nrf52833", feature = "nrf52840"))] impl_saadc_input!(@local, VddhDiv5Input, VDDHDIV5);