//! ADC driver.
use core::future::{poll_fn, Future};
use core::marker::PhantomData;
use core::mem;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin};
use crate::interrupt::typelevel::Binding;
use crate::interrupt::InterruptExt;
use crate::pac::dma::vals::TreqSel;
use crate::peripherals::{ADC, ADC_TEMP_SENSOR};
use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt};
static WAKER: AtomicWaker = AtomicWaker::new();
/// ADC config.
#[non_exhaustive]
#[derive(Default)]
pub struct Config {}
enum Source<'p> {
Pin(PeripheralRef<'p, AnyPin>),
TempSensor(PeripheralRef<'p, ADC_TEMP_SENSOR>),
}
/// ADC channel.
pub struct Channel<'p>(Source<'p>);
impl<'p> Channel<'p> {
/// Create a new ADC channel from pin with the provided [Pull] configuration.
pub fn new_pin(pin: impl Peripheral
+ 'p, pull: Pull) -> Self {
into_ref!(pin);
pin.pad_ctrl().modify(|w| {
#[cfg(feature = "_rp235x")]
w.set_iso(false);
// manual says:
//
// > When using an ADC input shared with a GPIO pin, the pin’s
// > digital functions must be disabled by setting IE low and OD
// > high in the pin’s pad control register
w.set_ie(false);
w.set_od(true);
w.set_pue(pull == Pull::Up);
w.set_pde(pull == Pull::Down);
});
Self(Source::Pin(pin.map_into()))
}
/// Create a new ADC channel for the internal temperature sensor.
pub fn new_temp_sensor(s: impl Peripheral
+ 'p) -> Self {
let r = pac::ADC;
r.cs().write_set(|w| w.set_ts_en(true));
Self(Source::TempSensor(s.into_ref()))
}
fn channel(&self) -> u8 {
#[cfg(any(feature = "rp2040", feature = "rp235xa"))]
const CH_OFFSET: u8 = 26;
#[cfg(feature = "rp235xb")]
const CH_OFFSET: u8 = 40;
#[cfg(any(feature = "rp2040", feature = "rp235xa"))]
const TS_CHAN: u8 = 4;
#[cfg(feature = "rp235xb")]
const TS_CHAN: u8 = 8;
match &self.0 {
// this requires adc pins to be sequential and matching the adc channels,
// which is the case for rp2040/rp235xy
Source::Pin(p) => p._pin() - CH_OFFSET,
Source::TempSensor(_) => TS_CHAN,
}
}
}
impl<'p> Drop for Source<'p> {
fn drop(&mut self) {
match self {
Source::Pin(p) => {
p.pad_ctrl().modify(|w| {
w.set_ie(true);
w.set_od(false);
w.set_pue(false);
w.set_pde(true);
});
}
Source::TempSensor(_) => {
pac::ADC.cs().write_clear(|w| w.set_ts_en(true));
}
}
}
}
/// ADC sample.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(transparent)]
pub struct Sample(u16);
impl Sample {
/// Sample is valid.
pub fn good(&self) -> bool {
self.0 < 0x8000
}
/// Sample value.
pub fn value(&self) -> u16 {
self.0 & !0x8000
}
}
/// ADC error.
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Error converting value.
ConversionFailed,
}
/// ADC mode.
pub trait Mode {}
/// ADC async mode.
pub struct Async;
impl Mode for Async {}
/// ADC blocking mode.
pub struct Blocking;
impl Mode for Blocking {}
/// ADC driver.
pub struct Adc<'d, M: Mode> {
phantom: PhantomData<(&'d ADC, M)>,
}
impl<'d, M: Mode> Drop for Adc<'d, M> {
fn drop(&mut self) {
let r = Self::regs();
// disable ADC. leaving it enabled comes with a ~150µA static
// current draw. the temperature sensor has already been disabled
// by the temperature-reading methods, so we don't need to touch that.
r.cs().write(|w| w.set_en(false));
}
}
impl<'d, M: Mode> Adc<'d, M> {
#[inline]
fn regs() -> pac::adc::Adc {
pac::ADC
}
#[inline]
fn reset() -> pac::resets::regs::Peripherals {
let mut ret = pac::resets::regs::Peripherals::default();
ret.set_adc(true);
ret
}
fn setup() {
let reset = Self::reset();
crate::reset::reset(reset);
crate::reset::unreset_wait(reset);
let r = Self::regs();
// Enable ADC
r.cs().write(|w| w.set_en(true));
// Wait for ADC ready
while !r.cs().read().ready() {}
}
/// Sample a value from a channel in blocking mode.
pub fn blocking_read(&mut self, ch: &mut Channel) -> Result {
let r = Self::regs();
r.cs().modify(|w| {
w.set_ainsel(ch.channel());
w.set_start_once(true);
w.set_err(true);
});
while !r.cs().read().ready() {}
match r.cs().read().err() {
true => Err(Error::ConversionFailed),
false => Ok(r.result().read().result()),
}
}
}
impl<'d> Adc<'d, Async> {
/// Create ADC driver in async mode.
pub fn new(
_inner: impl Peripheral + 'd,
_irq: impl Binding,
_config: Config,
) -> Self {
Self::setup();
// Setup IRQ
interrupt::ADC_IRQ_FIFO.unpend();
unsafe { interrupt::ADC_IRQ_FIFO.enable() };
Self { phantom: PhantomData }
}
fn wait_for_ready() -> impl Future