diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 4fba3116..bac45574 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -1,5 +1,6 @@ use core::future::poll_fn; use core::marker::PhantomData; +use core::mem; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -10,8 +11,8 @@ use crate::gpio::sealed::Pin as GpioPin; use crate::gpio::{self, AnyPin, Pull}; use crate::interrupt::typelevel::Binding; use crate::interrupt::InterruptExt; -use crate::peripherals::ADC; -use crate::{interrupt, pac, peripherals, Peripheral}; +use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; +use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt}; static WAKER: AtomicWaker = AtomicWaker::new(); @@ -24,12 +25,15 @@ impl Default for Config { } } -pub struct Pin<'p> { - pin: PeripheralRef<'p, AnyPin>, +enum Source<'p> { + Pin(PeripheralRef<'p, AnyPin>), + TempSensor(PeripheralRef<'p, ADC_TEMP_SENSOR>), } -impl<'p> Pin<'p> { - pub fn new(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { +pub struct Channel<'p>(Source<'p>); + +impl<'p> Channel<'p> { + pub fn new_pin(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { into_ref!(pin); pin.pad_ctrl().modify(|w| { // manual says: @@ -42,24 +46,55 @@ impl<'p> Pin<'p> { w.set_pue(pull == Pull::Up); w.set_pde(pull == Pull::Down); }); - Self { pin: pin.map_into() } + Self(Source::Pin(pin.map_into())) + } + + 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 { - // this requires adc pins to be sequential and matching the adc channels, - // which is the case for rp2040 - self.pin._pin() - 26 + match &self.0 { + // this requires adc pins to be sequential and matching the adc channels, + // which is the case for rp2040 + Source::Pin(p) => p._pin() - 26, + Source::TempSensor(_) => 4, + } } } -impl<'d> Drop for Pin<'d> { +impl<'p> Drop for Source<'p> { fn drop(&mut self) { - self.pin.pad_ctrl().modify(|w| { - w.set_ie(true); - w.set_od(false); - w.set_pue(false); - w.set_pde(true); - }); + 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)); + } + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct Sample(u16); + +impl Sample { + pub fn good(&self) -> bool { + self.0 < 0x8000 + } + + pub fn value(&self) -> u16 { + self.0 & !0x8000 } } @@ -115,10 +150,10 @@ impl<'d, M: Mode> Adc<'d, M> { while !r.cs().read().ready() {} } - fn sample_blocking(channel: u8) -> Result { + pub fn blocking_read(&mut self, ch: &mut Channel) -> Result { let r = Self::regs(); r.cs().modify(|w| { - w.set_ainsel(channel); + w.set_ainsel(ch.channel()); w.set_start_once(true); w.set_err(true); }); @@ -128,19 +163,6 @@ impl<'d, M: Mode> Adc<'d, M> { false => Ok(r.result().read().result().into()), } } - - pub fn blocking_read(&mut self, pin: &mut Pin) -> Result { - Self::sample_blocking(pin.channel()) - } - - pub fn blocking_read_temperature(&mut self) -> Result { - let r = Self::regs(); - r.cs().modify(|w| w.set_ts_en(true)); - while !r.cs().read().ready() {} - let result = Self::sample_blocking(4); - r.cs().modify(|w| w.set_ts_en(false)); - result - } } impl<'d> Adc<'d, Async> { @@ -172,10 +194,10 @@ impl<'d> Adc<'d, Async> { .await; } - async fn sample_async(channel: u8) -> Result { + pub async fn read(&mut self, ch: &mut Channel<'_>) -> Result { let r = Self::regs(); r.cs().modify(|w| { - w.set_ainsel(channel); + w.set_ainsel(ch.channel()); w.set_start_once(true); w.set_err(true); }); @@ -186,19 +208,89 @@ impl<'d> Adc<'d, Async> { } } - pub async fn read(&mut self, pin: &mut Pin<'_>) -> Result { - Self::sample_async(pin.channel()).await + async fn read_many_inner( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [W], + fcs_err: bool, + dma: impl Peripheral

, + ) -> Result<(), Error> { + let r = Self::regs(); + // clear previous errors and set channel + r.cs().modify(|w| { + w.set_ainsel(ch.channel()); + w.set_err_sticky(true); // clear previous errors + w.set_start_many(false); + }); + // wait for previous conversions and drain fifo. an earlier batch read may have + // been cancelled, leaving the adc running. + while !r.cs().read().ready() {} + while !r.fcs().read().empty() { + r.fifo().read(); + } + + // set up fifo for dma + r.fcs().write(|w| { + w.set_thresh(1); + w.set_dreq_en(true); + w.set_shift(mem::size_of::() == 1); + w.set_en(true); + w.set_err(fcs_err); + }); + + // reset dma config on drop, regardless of whether it was a future being cancelled + // or the method returning normally. + struct ResetDmaConfig; + impl Drop for ResetDmaConfig { + fn drop(&mut self) { + pac::ADC.cs().write_clear(|w| w.set_start_many(true)); + while !pac::ADC.cs().read().ready() {} + pac::ADC.fcs().write_clear(|w| { + w.set_dreq_en(true); + w.set_shift(true); + w.set_en(true); + }); + } + } + let auto_reset = ResetDmaConfig; + + let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], 36) }; + // start conversions and wait for dma to finish. we can't report errors early + // because there's no interrupt to signal them, and inspecting every element + // of the fifo is too costly to do here. + r.cs().write_set(|w| w.set_start_many(true)); + dma.await; + mem::drop(auto_reset); + // we can't report errors before the conversions have ended since no interrupt + // exists to report them early, and since they're exceedingly rare we probably don't + // want to anyway. + match r.cs().read().err_sticky() { + false => Ok(()), + true => Err(Error::ConversionFailed), + } } - pub async fn read_temperature(&mut self) -> Result { - let r = Self::regs(); - r.cs().modify(|w| w.set_ts_en(true)); - if !r.cs().read().ready() { - Self::wait_for_ready().await; - } - let result = Self::sample_async(4).await; - r.cs().modify(|w| w.set_ts_en(false)); - result + #[inline] + pub async fn read_many( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [S], + dma: impl Peripheral

, + ) -> Result<(), Error> { + self.read_many_inner(ch, buf, false, dma).await + } + + #[inline] + pub async fn read_many_raw( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [Sample], + dma: impl Peripheral

, + ) { + // errors are reported in individual samples + let _ = self + .read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, dma) + .await; } } @@ -223,21 +315,26 @@ impl interrupt::typelevel::Handler for Inter } mod sealed { - pub trait AdcPin: crate::gpio::sealed::Pin { - fn channel(&mut self) -> u8; - } + pub trait AdcSample: crate::dma::Word {} + + pub trait AdcChannel {} } -pub trait AdcPin: sealed::AdcPin + gpio::Pin {} +pub trait AdcSample: sealed::AdcSample {} + +impl sealed::AdcSample for u16 {} +impl AdcSample for u16 {} + +impl sealed::AdcSample for u8 {} +impl AdcSample for u8 {} + +pub trait AdcChannel: sealed::AdcChannel {} +pub trait AdcPin: AdcChannel + gpio::Pin {} macro_rules! impl_pin { ($pin:ident, $channel:expr) => { - impl sealed::AdcPin for peripherals::$pin { - fn channel(&mut self) -> u8 { - $channel - } - } - + impl sealed::AdcChannel for peripherals::$pin {} + impl AdcChannel for peripherals::$pin {} impl AdcPin for peripherals::$pin {} }; } @@ -246,3 +343,6 @@ impl_pin!(PIN_26, 0); impl_pin!(PIN_27, 1); impl_pin!(PIN_28, 2); impl_pin!(PIN_29, 3); + +impl sealed::AdcChannel for peripherals::ADC_TEMP_SENSOR {} +impl AdcChannel for peripherals::ADC_TEMP_SENSOR {} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 45156458..49bd3533 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -183,6 +183,7 @@ embassy_hal_internal::peripherals! { FLASH, ADC, + ADC_TEMP_SENSOR, CORE1, diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs index 81a8b834..02bc493b 100644 --- a/examples/rp/src/bin/adc.rs +++ b/examples/rp/src/bin/adc.rs @@ -7,7 +7,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; use embassy_rp::bind_interrupts; use embassy_rp::gpio::Pull; use embassy_time::{Duration, Timer}; @@ -22,9 +22,10 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let mut adc = Adc::new(p.ADC, Irqs, Config::default()); - let mut p26 = Pin::new(p.PIN_26, Pull::None); - let mut p27 = Pin::new(p.PIN_27, Pull::None); - let mut p28 = Pin::new(p.PIN_28, Pull::None); + let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); + let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); + let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); + let mut ts = Channel::new_temp_sensor(p.ADC_TEMP_SENSOR); loop { let level = adc.read(&mut p26).await.unwrap(); @@ -33,7 +34,7 @@ async fn main(_spawner: Spawner) { info!("Pin 27 ADC: {}", level); let level = adc.read(&mut p28).await.unwrap(); info!("Pin 28 ADC: {}", level); - let temp = adc.read_temperature().await.unwrap(); + let temp = adc.read(&mut ts).await.unwrap(); info!("Temp: {} degrees", convert_to_celsius(temp)); Timer::after(Duration::from_secs(1)).await; } diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index e659844a..d6d58f0c 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs @@ -6,7 +6,7 @@ mod common; use defmt::*; use embassy_executor::Spawner; -use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler, Sample}; use embassy_rp::bind_interrupts; use embassy_rp::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; @@ -22,12 +22,12 @@ async fn main(_spawner: Spawner) { { { - let mut p = Pin::new(&mut p.PIN_26, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); defmt::assert!(adc.read(&mut p).await.unwrap() < 0b01_0000_0000); } { - let mut p = Pin::new(&mut p.PIN_26, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); defmt::assert!(adc.read(&mut p).await.unwrap() > 0b11_0000_0000); } @@ -35,21 +35,21 @@ async fn main(_spawner: Spawner) { // not bothering with async reads from now on { { - let mut p = Pin::new(&mut p.PIN_27, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); } { - let mut p = Pin::new(&mut p.PIN_27, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); } } { { - let mut p = Pin::new(&mut p.PIN_28, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); } { - let mut p = Pin::new(&mut p.PIN_28, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); } } @@ -57,24 +57,71 @@ async fn main(_spawner: Spawner) { // gp29 is connected to vsys through a 200k/100k divider, // adding pulls should change the value let low = { - let mut p = Pin::new(&mut p.PIN_29, Pull::Down); + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Down); adc.blocking_read(&mut p).unwrap() }; let none = { - let mut p = Pin::new(&mut p.PIN_29, Pull::None); + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::None); adc.blocking_read(&mut p).unwrap() }; let up = { - let mut p = Pin::new(&mut p.PIN_29, Pull::Up); + let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Up); adc.blocking_read(&mut p).unwrap() }; defmt::assert!(low < none); defmt::assert!(none < up); } + { + let temp = convert_to_celsius( + adc.read(&mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR)) + .await + .unwrap(), + ); + defmt::assert!(temp > 0.0); + defmt::assert!(temp < 60.0); + } - let temp = convert_to_celsius(adc.read_temperature().await.unwrap()); - defmt::assert!(temp > 0.0); - defmt::assert!(temp < 60.0); + // run a bunch of conversions. we'll only check gp29 and the temp + // sensor here for brevity, if those two work the rest will too. + { + // gp29 is connected to vsys through a 200k/100k divider, + // adding pulls should change the value + let mut low = [0u16; 16]; + let mut none = [0u8; 16]; + let mut up = [Sample::default(); 16]; + adc.read_many( + &mut Channel::new_pin(&mut p.PIN_29, Pull::Down), + &mut low, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + adc.read_many( + &mut Channel::new_pin(&mut p.PIN_29, Pull::None), + &mut none, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + adc.read_many_raw(&mut Channel::new_pin(&mut p.PIN_29, Pull::Up), &mut up, &mut p.DMA_CH0) + .await; + defmt::assert!(low.iter().zip(none.iter()).all(|(l, n)| *l >> 4 < *n as u16)); + defmt::assert!(up.iter().all(|s| s.good())); + defmt::assert!(none.iter().zip(up.iter()).all(|(n, u)| (*n as u16) < u.value())); + } + { + let mut temp = [0u16; 16]; + adc.read_many( + &mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), + &mut temp, + &mut p.DMA_CH0, + ) + .await + .unwrap(); + let temp = temp.map(convert_to_celsius); + defmt::assert!(temp.iter().all(|t| *t > 0.0)); + defmt::assert!(temp.iter().all(|t| *t < 60.0)); + } info!("Test OK"); cortex_m::asm::bkpt();