diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 22c71e7f..009cb99b 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -100,6 +100,7 @@ pub mod interrupt; #[cfg(feature = "52840")] pub mod qspi; pub mod rtc; +pub mod saadc; pub mod spim; pub mod uarte; @@ -130,6 +131,9 @@ embassy_extras::peripherals! { #[cfg(any(feature = "52833", feature = "52840"))] spim3: SPIM3, + // SAADC + saadc: SAADC, + // GPIOTE gpiote: GPIOTE, gpiote_ch_0: GPIOTE_CH0, diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs new file mode 100644 index 00000000..b854ce78 --- /dev/null +++ b/embassy-nrf/src/saadc.rs @@ -0,0 +1,242 @@ +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; +use embassy::traits; +use embassy::util::{wake_on_interrupt, PeripheralBorrow}; +use embassy_extras::unborrow; +use futures::future::poll_fn; +use traits::spi::FullDuplex; + +use crate::gpio::Pin as GpioPin; +use crate::interrupt::{self, Interrupt}; +use crate::{pac, peripherals, slice_in_ram_or}; + +#[cfg(feature = "9160")] +use pac::{saadc_ns as saadc, SAADC_NS as SAADC}; + +#[cfg(not(feature = "9160"))] +use pac::{saadc, SAADC}; + +pub use saadc::{ + ch::{ + config::{GAIN_A as Gain, REFSEL_A as Reference, RESP_A as Resistor, TACQ_A as Time}, + pselp::PSELP_A as PositiveChannel, + }, + oversample::OVERSAMPLE_A as Oversample, + resolution::VAL_A as Resolution, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error {} + +/// One-shot saadc. Continuous sample mode TODO. +pub struct OneShot<'d, T: PositivePin> { + peri: peripherals::SAADC, + positive_pin: T, + irq: interrupt::SAADC, + phantom: PhantomData<(&'d mut peripherals::SAADC, &'d mut T)>, +} + +/// Used to configure the SAADC peripheral. +/// +/// See the `Default` impl for suitable default values. +pub struct Config { + /// Output resolution in bits. + pub resolution: Resolution, + /// Average 2^`oversample` input samples before transferring the result into memory. + pub oversample: Oversample, + /// 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, +} + +impl Default for Config { + fn default() -> Self { + Self { + resolution: Resolution::_14BIT, + oversample: Oversample::OVER8X, + reference: Reference::VDD1_4, + gain: Gain::GAIN1_4, + resistor: Resistor::BYPASS, + time: Time::_20US, + } + } +} + +impl<'d, T: PositivePin> OneShot<'d, T> { + pub fn new( + saadc: impl PeripheralBorrow + 'd, + irq: impl PeripheralBorrow + 'd, + positive_pin: impl PeripheralBorrow + 'd, + config: Config, + ) -> Self { + unborrow!(saadc, irq, positive_pin); + + let r = unsafe { &*SAADC::ptr() }; + + let Config { + resolution, + oversample, + reference, + gain, + resistor, + time, + } = config; + + // Configure pins + r.enable.write(|w| w.enable().enabled()); + r.resolution.write(|w| w.val().variant(resolution)); + r.oversample.write(|w| w.oversample().variant(oversample)); + + r.ch[0].config.write(|w| { + w.refsel().variant(reference); + w.gain().variant(gain); + w.tacq().variant(time); + w.mode().se(); + w.resp().variant(resistor); + w.resn().bypass(); + if !matches!(oversample, Oversample::BYPASS) { + w.burst().enabled(); + } else { + w.burst().disabled(); + } + w + }); + + // Set positive channel + r.ch[0] + .pselp + .write(|w| w.pselp().variant(positive_pin.channel())); + + // Disable all events interrupts + r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); + + Self { + peri: saadc, + positive_pin, + irq, + phantom: PhantomData, + } + } + + fn regs(&self) -> &saadc::RegisterBlock { + unsafe { &*SAADC::ptr() } + } +} + +impl<'d, T: PositivePin> Drop for OneShot<'d, T> { + fn drop(&mut self) { + let r = self.regs(); + r.enable.write(|w| w.enable().disabled()); + } +} + +pub trait Sample { + type SampleFuture<'a>: Future + 'a + where + Self: 'a; + + fn sample<'a>(self: Pin<&'a mut Self>) -> Self::SampleFuture<'a>; +} + +impl<'d, T: PositivePin> Sample for OneShot<'d, T> { + #[rustfmt::skip] + type SampleFuture<'a> where Self: 'a = impl Future + 'a; + + fn sample<'a>(self: Pin<&'a mut Self>) -> Self::SampleFuture<'a> { + async move { + let this = unsafe { self.get_unchecked_mut() }; + let r = this.regs(); + + // Set up the DMA + let mut val: i16 = 0; + r.result + .ptr + .write(|w| unsafe { w.ptr().bits(((&mut val) as *mut _) as u32) }); + r.result.maxcnt.write(|w| unsafe { w.maxcnt().bits(1) }); + + // Reset and enable the end event + r.events_end.reset(); + r.intenset.write(|w| w.end().set()); + + // Don't reorder the ADC start event before the previous writes. Hopefully this + // wouldn't happen anyway. + compiler_fence(Ordering::SeqCst); + + r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.tasks_sample.write(|w| unsafe { w.bits(1) }); + + // Wait for 'end' event. + poll_fn(|cx| { + let r = this.regs(); + + if r.events_end.read().bits() != 0 { + r.events_end.reset(); + return Poll::Ready(()); + } + + wake_on_interrupt(&mut this.irq, cx.waker()); + + Poll::Pending + }) + .await; + + // The DMA wrote the sampled value to `val`. + val + } + } +} + +/// A pin that can be used as the positive end of a ADC differential in the SAADC periperhal. +/// +/// Currently negative is always shorted to ground (0V). +pub trait PositivePin { + fn channel(&self) -> PositiveChannel; +} + +macro_rules! positive_pin_mappings { + ( $($ch:ident => $pin:ident,)*) => { + $( + impl PositivePin for crate::peripherals::$pin { + fn channel(&self) -> PositiveChannel { + PositiveChannel::$ch + } + } + )* + }; +} + +// TODO the variant names are unchecked +// the pins are copied from nrf hal +#[cfg(feature = "9160")] +positive_pin_mappings! { + ANALOGINPUT0 => P0_13, + ANALOGINPUT1 => P0_14, + ANALOGINPUT2 => P0_15, + ANALOGINPUT3 => P0_16, + ANALOGINPUT4 => P0_17, + ANALOGINPUT5 => P0_18, + ANALOGINPUT6 => P0_19, + ANALOGINPUT7 => P0_20, +} + +#[cfg(not(feature = "9160"))] +positive_pin_mappings! { + ANALOGINPUT0 => P0_02, + ANALOGINPUT1 => P0_03, + ANALOGINPUT2 => P0_04, + ANALOGINPUT3 => P0_05, + ANALOGINPUT4 => P0_28, + ANALOGINPUT5 => P0_29, + ANALOGINPUT6 => P0_30, + ANALOGINPUT7 => P0_31, +}