#![macro_use]
use core::future::poll_fn;
use core::marker::PhantomData;
use core::sync::atomic::{Ordering, compiler_fence};
use core::task::Poll;
use embassy_hal_internal::drop::OnDrop;
use embassy_hal_internal::{Peri, impl_peripheral};
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(not(feature = "_nrf54l"))]
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};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {}
pub struct InterruptHandler {
_private: (),
}
impl interrupt::typelevel::Handler<interrupt::typelevel::SAADC> 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();
#[non_exhaustive]
pub struct Config {
pub resolution: Resolution,
pub oversample: Oversample,
}
impl Default for Config {
fn default() -> Self {
Self {
resolution: Resolution::_12bit,
oversample: Oversample::Bypass,
}
}
}
#[non_exhaustive]
pub struct ChannelConfig<'d> {
pub reference: Reference,
pub gain: Gain,
#[cfg(not(feature = "_nrf54l"))]
pub resistor: Resistor,
pub time: Time,
p_channel: AnyInput<'d>,
n_channel: Option<AnyInput<'d>>,
}
impl<'d> ChannelConfig<'d> {
pub fn single_ended(input: impl Input + 'd) -> Self {
Self {
reference: Reference::Internal,
#[cfg(not(feature = "_nrf54l"))]
gain: Gain::Gain1_6,
#[cfg(feature = "_nrf54l")]
gain: Gain::Gain2_8,
#[cfg(not(feature = "_nrf54l"))]
resistor: Resistor::Bypass,
time: Time::_10US,
p_channel: input.degrade_saadc(),
n_channel: None,
}
}
pub fn differential(p_input: impl Input + 'd, n_input: impl Input + 'd) -> Self {
Self {
reference: Reference::Internal,
#[cfg(not(feature = "_nrf54l"))]
gain: Gain::Gain1_6,
#[cfg(feature = "_nrf54l")]
gain: Gain::Gain2_8,
#[cfg(not(feature = "_nrf54l"))]
resistor: Resistor::Bypass,
time: Time::_10US,
p_channel: p_input.degrade_saadc(),
n_channel: Some(n_input.degrade_saadc()),
}
}
}
const CNT_UNIT: usize = if cfg!(feature = "_nrf54l") { 2 } else { 1 };
#[derive(PartialEq)]
pub enum CallbackResult {
Continue,
Stop,
}
pub struct Saadc<'d, const N: usize> {
_p: Peri<'d, peripherals::SAADC>,
}
impl<'d, const N: usize> Saadc<'d, N> {
pub fn new(
saadc: Peri<'d, peripherals::SAADC>,
_irq: impl interrupt::typelevel::Binding<interrupt::typelevel::SAADC, InterruptHandler> + 'd,
config: Config,
channel_configs: [ChannelConfig; N],
) -> Self {
let r = pac::SAADC;
let Config { resolution, oversample } = config;
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() {
#[cfg(not(feature = "_nrf54l"))]
r.ch(i).pselp().write(|w| w.set_pselp(cc.p_channel.channel()));
#[cfg(feature = "_nrf54l")]
r.ch(i).pselp().write(|w| {
w.set_port(cc.p_channel.port());
w.set_pin(cc.p_channel.pin());
w.set_internal(cc.p_channel.internal());
w.set_connect(cc.p_channel.connect());
});
if let Some(n_channel) = &cc.n_channel {
#[cfg(not(feature = "_nrf54l"))]
r.ch(i).pseln().write(|w| w.set_pseln(n_channel.channel()));
#[cfg(feature = "_nrf54l")]
r.ch(i).pseln().write(|w| {
w.set_port(n_channel.port());
w.set_pin(n_channel.pin());
w.set_connect(n_channel.connect().to_bits().into());
});
}
r.ch(i).config().write(|w| {
w.set_refsel(cc.reference.into());
w.set_gain(cc.gain.into());
w.set_tacq(cc.time.into());
#[cfg(feature = "_nrf54l")]
w.set_tconv(7); w.set_mode(match cc.n_channel {
None => vals::ConfigMode::Se,
Some(_) => vals::ConfigMode::Diff,
});
#[cfg(not(feature = "_nrf54l"))]
w.set_resp(cc.resistor.into());
#[cfg(not(feature = "_nrf54l"))]
w.set_resn(vals::Resn::Bypass);
#[cfg(not(feature = "_nrf54lm20"))]
w.set_burst(!matches!(oversample, Oversample::Bypass));
});
#[cfg(feature = "_nrf54lm20")]
r.burst()
.write(|w| w.set_burst(!matches!(oversample, Oversample::Bypass)));
}
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
}
pub async fn calibrate(&self) {
let r = Self::regs();
r.events_calibratedone().write_value(0);
r.intenset().write(|w| w.set_calibratedone(true));
compiler_fence(Ordering::SeqCst);
r.tasks_calibrateoffset().write_value(1);
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;
}
pub async fn sample(&mut self, buf: &mut [i16; N]) {
let on_drop = OnDrop::new(Self::stop_sampling_immediately);
let r = Self::regs();
r.result().ptr().write_value(buf.as_mut_ptr() as u32);
r.result().maxcnt().write(|w| w.set_maxcnt((N * CNT_UNIT) as _));
r.events_end().write_value(0);
r.intenset().write(|w| w.set_end(true));
compiler_fence(Ordering::SeqCst);
r.tasks_start().write_value(1);
r.tasks_sample().write_value(1);
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);
}
pub async fn run_task_sampler<F, T: TimerInstance, const N0: usize>(
&mut self,
timer: Peri<'_, T>,
ppi_ch1: Peri<'_, impl ConfigurableChannel>,
ppi_ch2: Peri<'_, 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();
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<I, F, const N0: usize>(
&mut self,
bufs: &mut [[[i16; N]; N0]; 2],
sample_rate_divisor: Option<u16>,
mut init: I,
mut callback: F,
) where
I: FnMut(),
F: FnMut(&[[i16; N]]) -> CallbackResult,
{
let on_drop = OnDrop::new(Self::stop_sampling_immediately);
let r = Self::regs();
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); }
None => r.samplerate().write(|w| {
w.set_cc(0);
w.set_mode(vals::SamplerateMode::Task);
}),
}
r.result().ptr().write_value(bufs[0].as_mut_ptr() as u32);
r.result().maxcnt().write(|w| w.set_maxcnt((N0 * N * CNT_UNIT) as _));
r.events_end().write_value(0);
r.events_started().write_value(0);
r.intenset().write(|w| {
w.set_end(true);
w.set_started(true);
});
compiler_fence(Ordering::SeqCst);
r.tasks_start().write_value(1);
let mut inited = false;
let mut current_buffer = 0;
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
}
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> {
pub async fn run_timer_sampler<I, S, const N0: usize>(
&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) {
#[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 {
#[cfg(not(feature = "_nrf54l"))]
{
r.ch(i).pselp().write(|w| w.set_pselp(InputChannel::Nc));
r.ch(i).pseln().write(|w| w.set_pseln(InputChannel::Nc));
}
#[cfg(feature = "_nrf54l")]
{
r.ch(i).pselp().write(|w| w.set_connect(vals::PselpConnect::Nc));
r.ch(i).pseln().write(|w| w.set_connect(vals::PselnConnect::Nc));
}
}
}
}
#[cfg(not(feature = "_nrf54l"))]
impl From<Gain> for vals::Gain {
fn from(gain: Gain) -> Self {
match gain {
Gain::Gain1_6 => vals::Gain::Gain16,
Gain::Gain1_5 => vals::Gain::Gain15,
Gain::Gain1_4 => vals::Gain::Gain14,
Gain::Gain1_3 => vals::Gain::Gain13,
Gain::Gain1_2 => vals::Gain::Gain12,
Gain::Gain1 => vals::Gain::Gain1,
Gain::Gain2 => vals::Gain::Gain2,
Gain::Gain4 => vals::Gain::Gain4,
}
}
}
#[cfg(feature = "_nrf54l")]
impl From<Gain> for vals::Gain {
fn from(gain: Gain) -> Self {
match gain {
Gain::Gain2_8 => vals::Gain::Gain28,
Gain::Gain2_7 => vals::Gain::Gain27,
Gain::Gain2_6 => vals::Gain::Gain26,
Gain::Gain2_5 => vals::Gain::Gain25,
Gain::Gain2_4 => vals::Gain::Gain24,
Gain::Gain2_3 => vals::Gain::Gain23,
Gain::Gain1 => vals::Gain::Gain1,
Gain::Gain2 => vals::Gain::Gain2,
}
}
}
#[cfg(not(feature = "_nrf54l"))]
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum Gain {
Gain1_6 = 0,
Gain1_5 = 1,
Gain1_4 = 2,
Gain1_3 = 3,
Gain1_2 = 4,
Gain1 = 5,
Gain2 = 6,
Gain4 = 7,
}
#[cfg(feature = "_nrf54l")]
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum Gain {
Gain2_8 = 0,
Gain2_7 = 1,
Gain2_6 = 2,
Gain2_5 = 3,
Gain2_4 = 4,
Gain2_3 = 5,
Gain1 = 6,
Gain2 = 7,
}
impl From<Reference> for vals::Refsel {
fn from(reference: Reference) -> Self {
match reference {
Reference::Internal => vals::Refsel::Internal,
#[cfg(not(feature = "_nrf54l"))]
Reference::VDD1_4 => vals::Refsel::Vdd14,
#[cfg(feature = "_nrf54l")]
Reference::External => vals::Refsel::External,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum Reference {
Internal = 0,
#[cfg(not(feature = "_nrf54l"))]
VDD1_4 = 1,
#[cfg(feature = "_nrf54l")]
External = 1,
}
#[cfg(not(feature = "_nrf54l"))]
impl From<Resistor> 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::Vdd12,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy)]
#[cfg(not(feature = "_nrf54l"))]
pub enum Resistor {
Bypass = 0,
Pulldown = 1,
Pullup = 2,
Vdd1_2 = 3,
}
#[cfg(not(feature = "_nrf54l"))]
impl From<Time> for vals::Tacq {
fn from(time: Time) -> Self {
match time {
Time::_3US => vals::Tacq::_3us,
Time::_5US => vals::Tacq::_5us,
Time::_10US => vals::Tacq::_10us,
Time::_15US => vals::Tacq::_15us,
Time::_20US => vals::Tacq::_20us,
Time::_40US => vals::Tacq::_40us,
}
}
}
#[cfg(feature = "_nrf54l")]
impl From<Time> for u16 {
fn from(time: Time) -> Self {
match time {
Time::_3US => (3000 / 125) - 1,
Time::_5US => (5000 / 125) - 1,
Time::_10US => (10000 / 125) - 1,
Time::_15US => (15000 / 125) - 1,
Time::_20US => (20000 / 125) - 1,
Time::_40US => (40000 / 125) - 1,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum Time {
_3US = 0,
_5US = 1,
_10US = 2,
_15US = 3,
_20US = 4,
_40US = 5,
}
impl From<Oversample> for vals::Oversample {
fn from(oversample: Oversample) -> Self {
match oversample {
Oversample::Bypass => vals::Oversample::Bypass,
Oversample::Over2x => vals::Oversample::Over2x,
Oversample::Over4x => vals::Oversample::Over4x,
Oversample::Over8x => vals::Oversample::Over8x,
Oversample::Over16x => vals::Oversample::Over16x,
Oversample::Over32x => vals::Oversample::Over32x,
Oversample::Over64x => vals::Oversample::Over64x,
Oversample::Over128x => vals::Oversample::Over128x,
Oversample::Over256x => vals::Oversample::Over256x,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum Oversample {
Bypass = 0,
Over2x = 1,
Over4x = 2,
Over8x = 3,
Over16x = 4,
Over32x = 5,
Over64x = 6,
Over128x = 7,
Over256x = 8,
}
impl From<Resolution> for vals::Val {
fn from(resolution: Resolution) -> Self {
match resolution {
Resolution::_8bit => vals::Val::_8bit,
Resolution::_10bit => vals::Val::_10bit,
Resolution::_12bit => vals::Val::_12bit,
Resolution::_14bit => vals::Val::_14bit,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy)]
pub enum Resolution {
_8bit = 0,
_10bit = 1,
_12bit = 2,
_14bit = 3,
}
pub(crate) trait SealedInput {
#[cfg(not(feature = "_nrf54l"))]
fn channel(&self) -> InputChannel;
#[cfg(feature = "_nrf54l")]
fn pin(&self) -> u8;
#[cfg(feature = "_nrf54l")]
fn port(&self) -> u8;
#[cfg(feature = "_nrf54l")]
fn internal(&self) -> vals::PselpInternal;
#[cfg(feature = "_nrf54l")]
fn connect(&self) -> vals::PselpConnect;
}
#[allow(private_bounds)]
pub trait Input: SealedInput + Sized {
#[cfg(not(feature = "_nrf54l"))]
fn degrade_saadc<'a>(self) -> AnyInput<'a>
where
Self: 'a,
{
AnyInput {
channel: self.channel(),
_phantom: core::marker::PhantomData,
}
}
#[cfg(feature = "_nrf54l")]
fn degrade_saadc<'a>(self) -> AnyInput<'a>
where
Self: 'a,
{
AnyInput {
pin: self.pin(),
port: self.port(),
internal: self.internal(),
connect: self.connect(),
_phantom: core::marker::PhantomData,
}
}
}
#[cfg(not(feature = "_nrf54l"))]
pub struct AnyInput<'a> {
channel: InputChannel,
_phantom: PhantomData<&'a ()>,
}
#[cfg(feature = "_nrf54l")]
pub struct AnyInput<'a> {
pin: u8,
port: u8,
internal: vals::PselpInternal,
connect: vals::PselpConnect,
_phantom: PhantomData<&'a ()>,
}
impl<'a> AnyInput<'a> {
pub fn reborrow(&mut self) -> AnyInput<'_> {
#[cfg(not(feature = "_nrf54l"))]
{
Self {
channel: self.channel,
_phantom: PhantomData,
}
}
#[cfg(feature = "_nrf54l")]
{
Self {
pin: self.pin,
port: self.port,
internal: self.internal,
connect: self.connect,
_phantom: PhantomData,
}
}
}
}
impl SealedInput for AnyInput<'_> {
#[cfg(not(feature = "_nrf54l"))]
fn channel(&self) -> InputChannel {
self.channel
}
#[cfg(feature = "_nrf54l")]
fn pin(&self) -> u8 {
self.pin
}
#[cfg(feature = "_nrf54l")]
fn port(&self) -> u8 {
self.port
}
#[cfg(feature = "_nrf54l")]
fn internal(&self) -> vals::PselpInternal {
self.internal
}
#[cfg(feature = "_nrf54l")]
fn connect(&self) -> vals::PselpConnect {
self.connect
}
}
impl Input for AnyInput<'_> {}
#[cfg(not(feature = "_nrf54l"))]
macro_rules! impl_saadc_input {
($pin:ident, $ch:ident) => {
impl_saadc_input!(@local, crate::Peri<'_, 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 {}
};
}
#[cfg(feature = "_nrf54l")]
macro_rules! impl_saadc_input {
($pin:ident, $port:expr, $ain:expr) => {
impl_saadc_input!(@local, crate::Peri<'_, crate::peripherals::$pin>, $port, $ain, Avdd, AnalogInput);
};
(@local, $pin:ty, $port:expr, $ain:expr, $internal:ident, $connect:ident) => {
impl crate::saadc::SealedInput for $pin {
fn pin(&self) -> u8 {
$ain
}
fn port(&self) -> u8 {
$port
}
fn internal(&self) -> crate::pac::saadc::vals::PselpInternal {
crate::pac::saadc::vals::PselpInternal::$internal
}
fn connect(&self) -> crate::pac::saadc::vals::PselpConnect {
crate::pac::saadc::vals::PselpConnect::$connect
}
}
impl crate::saadc::Input for $pin {}
};
}
pub struct VddInput;
impl_peripheral!(VddInput);
#[cfg(not(feature = "_nrf54l"))]
#[cfg(not(feature = "_nrf91"))]
impl_saadc_input!(@local, VddInput, Vdd);
#[cfg(feature = "_nrf91")]
impl_saadc_input!(@local, VddInput, VddGpio);
#[cfg(feature = "_nrf54l")]
impl_saadc_input!(@local, VddInput, 0, 0, Vdd, Internal);
#[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);
#[cfg(feature = "_nrf54l")]
pub struct AVddInput;
#[cfg(feature = "_nrf54l")]
embassy_hal_internal::impl_peripheral!(AVddInput);
#[cfg(feature = "_nrf54l")]
impl_saadc_input!(@local, AVddInput, 0, 0, Avdd, Internal);
#[cfg(feature = "_nrf54l")]
pub struct DVddInput;
#[cfg(feature = "_nrf54l")]
embassy_hal_internal::impl_peripheral!(DVddInput);
#[cfg(feature = "_nrf54l")]
impl_saadc_input!(@local, DVddInput, 0, 0, Dvdd, Internal);