diff options
| author | pennae <[email protected]> | 2023-07-21 23:34:12 +0200 |
|---|---|---|
| committer | pennae <[email protected]> | 2023-08-02 17:04:32 +0200 |
| commit | a6b8f3d99478266b4f110e9c150ce3add5c3ffc6 (patch) | |
| tree | fe1fc04105c93efc5a3bd056ce87e05495cfcb04 | |
| parent | b166ed6b78db0737005a65c1e444ce7563de7da3 (diff) | |
rp: add single-channel dma from adc
with uniform treatment of adc inputs it's easy enough to add a new
sampling method. dma sampling only supports one channel at the moment,
though round-robin sampling would be a simple extension (probably a new
trait that's implemented for Channel and &[Channel]). continuous dma as
proposed in #1608 also isn't done here, we'd expect that to be a
compound dma::Channel that internally splits a buffer in half and
dispatches callbacks or something like that.
| -rw-r--r-- | embassy-rp/src/adc.rs | 115 | ||||
| -rw-r--r-- | examples/rp/src/bin/adc.rs | 2 | ||||
| -rw-r--r-- | tests/rp/src/bin/adc.rs | 55 |
3 files changed, 165 insertions, 7 deletions
diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 2824d893c..bac455743 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | use core::future::poll_fn; | 1 | use core::future::poll_fn; |
| 2 | use core::marker::PhantomData; | 2 | use core::marker::PhantomData; |
| 3 | use core::mem; | ||
| 3 | use core::sync::atomic::{compiler_fence, Ordering}; | 4 | use core::sync::atomic::{compiler_fence, Ordering}; |
| 4 | use core::task::Poll; | 5 | use core::task::Poll; |
| 5 | 6 | ||
| @@ -11,7 +12,7 @@ use crate::gpio::{self, AnyPin, Pull}; | |||
| 11 | use crate::interrupt::typelevel::Binding; | 12 | use crate::interrupt::typelevel::Binding; |
| 12 | use crate::interrupt::InterruptExt; | 13 | use crate::interrupt::InterruptExt; |
| 13 | use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; | 14 | use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; |
| 14 | use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; | 15 | use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt}; |
| 15 | 16 | ||
| 16 | static WAKER: AtomicWaker = AtomicWaker::new(); | 17 | static WAKER: AtomicWaker = AtomicWaker::new(); |
| 17 | 18 | ||
| @@ -48,7 +49,7 @@ impl<'p> Channel<'p> { | |||
| 48 | Self(Source::Pin(pin.map_into())) | 49 | Self(Source::Pin(pin.map_into())) |
| 49 | } | 50 | } |
| 50 | 51 | ||
| 51 | pub fn new_sensor(s: impl Peripheral<P = ADC_TEMP_SENSOR> + 'p) -> Self { | 52 | pub fn new_temp_sensor(s: impl Peripheral<P = ADC_TEMP_SENSOR> + 'p) -> Self { |
| 52 | let r = pac::ADC; | 53 | let r = pac::ADC; |
| 53 | r.cs().write_set(|w| w.set_ts_en(true)); | 54 | r.cs().write_set(|w| w.set_ts_en(true)); |
| 54 | Self(Source::TempSensor(s.into_ref())) | 55 | Self(Source::TempSensor(s.into_ref())) |
| @@ -82,6 +83,21 @@ impl<'p> Drop for Source<'p> { | |||
| 82 | } | 83 | } |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 86 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] | ||
| 87 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 88 | #[repr(transparent)] | ||
| 89 | pub struct Sample(u16); | ||
| 90 | |||
| 91 | impl Sample { | ||
| 92 | pub fn good(&self) -> bool { | ||
| 93 | self.0 < 0x8000 | ||
| 94 | } | ||
| 95 | |||
| 96 | pub fn value(&self) -> u16 { | ||
| 97 | self.0 & !0x8000 | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 85 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] | 101 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] |
| 86 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 102 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 87 | pub enum Error { | 103 | pub enum Error { |
| @@ -191,6 +207,91 @@ impl<'d> Adc<'d, Async> { | |||
| 191 | false => Ok(r.result().read().result().into()), | 207 | false => Ok(r.result().read().result().into()), |
| 192 | } | 208 | } |
| 193 | } | 209 | } |
| 210 | |||
| 211 | async fn read_many_inner<W: dma::Word>( | ||
| 212 | &mut self, | ||
| 213 | ch: &mut Channel<'_>, | ||
| 214 | buf: &mut [W], | ||
| 215 | fcs_err: bool, | ||
| 216 | dma: impl Peripheral<P = impl dma::Channel>, | ||
| 217 | ) -> Result<(), Error> { | ||
| 218 | let r = Self::regs(); | ||
| 219 | // clear previous errors and set channel | ||
| 220 | r.cs().modify(|w| { | ||
| 221 | w.set_ainsel(ch.channel()); | ||
| 222 | w.set_err_sticky(true); // clear previous errors | ||
| 223 | w.set_start_many(false); | ||
| 224 | }); | ||
| 225 | // wait for previous conversions and drain fifo. an earlier batch read may have | ||
| 226 | // been cancelled, leaving the adc running. | ||
| 227 | while !r.cs().read().ready() {} | ||
| 228 | while !r.fcs().read().empty() { | ||
| 229 | r.fifo().read(); | ||
| 230 | } | ||
| 231 | |||
| 232 | // set up fifo for dma | ||
| 233 | r.fcs().write(|w| { | ||
| 234 | w.set_thresh(1); | ||
| 235 | w.set_dreq_en(true); | ||
| 236 | w.set_shift(mem::size_of::<W>() == 1); | ||
| 237 | w.set_en(true); | ||
| 238 | w.set_err(fcs_err); | ||
| 239 | }); | ||
| 240 | |||
| 241 | // reset dma config on drop, regardless of whether it was a future being cancelled | ||
| 242 | // or the method returning normally. | ||
| 243 | struct ResetDmaConfig; | ||
| 244 | impl Drop for ResetDmaConfig { | ||
| 245 | fn drop(&mut self) { | ||
| 246 | pac::ADC.cs().write_clear(|w| w.set_start_many(true)); | ||
| 247 | while !pac::ADC.cs().read().ready() {} | ||
| 248 | pac::ADC.fcs().write_clear(|w| { | ||
| 249 | w.set_dreq_en(true); | ||
| 250 | w.set_shift(true); | ||
| 251 | w.set_en(true); | ||
| 252 | }); | ||
| 253 | } | ||
| 254 | } | ||
| 255 | let auto_reset = ResetDmaConfig; | ||
| 256 | |||
| 257 | let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], 36) }; | ||
| 258 | // start conversions and wait for dma to finish. we can't report errors early | ||
| 259 | // because there's no interrupt to signal them, and inspecting every element | ||
| 260 | // of the fifo is too costly to do here. | ||
| 261 | r.cs().write_set(|w| w.set_start_many(true)); | ||
| 262 | dma.await; | ||
| 263 | mem::drop(auto_reset); | ||
| 264 | // we can't report errors before the conversions have ended since no interrupt | ||
| 265 | // exists to report them early, and since they're exceedingly rare we probably don't | ||
| 266 | // want to anyway. | ||
| 267 | match r.cs().read().err_sticky() { | ||
| 268 | false => Ok(()), | ||
| 269 | true => Err(Error::ConversionFailed), | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | #[inline] | ||
| 274 | pub async fn read_many<S: AdcSample>( | ||
| 275 | &mut self, | ||
| 276 | ch: &mut Channel<'_>, | ||
| 277 | buf: &mut [S], | ||
| 278 | dma: impl Peripheral<P = impl dma::Channel>, | ||
| 279 | ) -> Result<(), Error> { | ||
| 280 | self.read_many_inner(ch, buf, false, dma).await | ||
| 281 | } | ||
| 282 | |||
| 283 | #[inline] | ||
| 284 | pub async fn read_many_raw( | ||
| 285 | &mut self, | ||
| 286 | ch: &mut Channel<'_>, | ||
| 287 | buf: &mut [Sample], | ||
| 288 | dma: impl Peripheral<P = impl dma::Channel>, | ||
| 289 | ) { | ||
| 290 | // errors are reported in individual samples | ||
| 291 | let _ = self | ||
| 292 | .read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, dma) | ||
| 293 | .await; | ||
| 294 | } | ||
| 194 | } | 295 | } |
| 195 | 296 | ||
| 196 | impl<'d> Adc<'d, Blocking> { | 297 | impl<'d> Adc<'d, Blocking> { |
| @@ -214,9 +315,19 @@ impl interrupt::typelevel::Handler<interrupt::typelevel::ADC_IRQ_FIFO> for Inter | |||
| 214 | } | 315 | } |
| 215 | 316 | ||
| 216 | mod sealed { | 317 | mod sealed { |
| 318 | pub trait AdcSample: crate::dma::Word {} | ||
| 319 | |||
| 217 | pub trait AdcChannel {} | 320 | pub trait AdcChannel {} |
| 218 | } | 321 | } |
| 219 | 322 | ||
| 323 | pub trait AdcSample: sealed::AdcSample {} | ||
| 324 | |||
| 325 | impl sealed::AdcSample for u16 {} | ||
| 326 | impl AdcSample for u16 {} | ||
| 327 | |||
| 328 | impl sealed::AdcSample for u8 {} | ||
| 329 | impl AdcSample for u8 {} | ||
| 330 | |||
| 220 | pub trait AdcChannel: sealed::AdcChannel {} | 331 | pub trait AdcChannel: sealed::AdcChannel {} |
| 221 | pub trait AdcPin: AdcChannel + gpio::Pin {} | 332 | pub trait AdcPin: AdcChannel + gpio::Pin {} |
| 222 | 333 | ||
diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs index c58695512..02bc493b6 100644 --- a/examples/rp/src/bin/adc.rs +++ b/examples/rp/src/bin/adc.rs | |||
| @@ -25,7 +25,7 @@ async fn main(_spawner: Spawner) { | |||
| 25 | let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); | 25 | let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); |
| 26 | let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); | 26 | let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); |
| 27 | let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); | 27 | let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); |
| 28 | let mut ts = Channel::new_sensor(p.ADC_TEMP_SENSOR); | 28 | let mut ts = Channel::new_temp_sensor(p.ADC_TEMP_SENSOR); |
| 29 | 29 | ||
| 30 | loop { | 30 | loop { |
| 31 | let level = adc.read(&mut p26).await.unwrap(); | 31 | let level = adc.read(&mut p26).await.unwrap(); |
diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index 9006ce8cc..d6d58f0c0 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs | |||
| @@ -6,7 +6,7 @@ mod common; | |||
| 6 | 6 | ||
| 7 | use defmt::*; | 7 | use defmt::*; |
| 8 | use embassy_executor::Spawner; | 8 | use embassy_executor::Spawner; |
| 9 | use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; | 9 | use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler, Sample}; |
| 10 | use embassy_rp::bind_interrupts; | 10 | use embassy_rp::bind_interrupts; |
| 11 | use embassy_rp::gpio::Pull; | 11 | use embassy_rp::gpio::Pull; |
| 12 | use {defmt_rtt as _, panic_probe as _}; | 12 | use {defmt_rtt as _, panic_probe as _}; |
| @@ -71,10 +71,57 @@ async fn main(_spawner: Spawner) { | |||
| 71 | defmt::assert!(low < none); | 71 | defmt::assert!(low < none); |
| 72 | defmt::assert!(none < up); | 72 | defmt::assert!(none < up); |
| 73 | } | 73 | } |
| 74 | { | ||
| 75 | let temp = convert_to_celsius( | ||
| 76 | adc.read(&mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR)) | ||
| 77 | .await | ||
| 78 | .unwrap(), | ||
| 79 | ); | ||
| 80 | defmt::assert!(temp > 0.0); | ||
| 81 | defmt::assert!(temp < 60.0); | ||
| 82 | } | ||
| 74 | 83 | ||
| 75 | let temp = convert_to_celsius(adc.read(&mut Channel::new_sensor(p.ADC_TEMP_SENSOR)).await.unwrap()); | 84 | // run a bunch of conversions. we'll only check gp29 and the temp |
| 76 | defmt::assert!(temp > 0.0); | 85 | // sensor here for brevity, if those two work the rest will too. |
| 77 | defmt::assert!(temp < 60.0); | 86 | { |
| 87 | // gp29 is connected to vsys through a 200k/100k divider, | ||
| 88 | // adding pulls should change the value | ||
| 89 | let mut low = [0u16; 16]; | ||
| 90 | let mut none = [0u8; 16]; | ||
| 91 | let mut up = [Sample::default(); 16]; | ||
| 92 | adc.read_many( | ||
| 93 | &mut Channel::new_pin(&mut p.PIN_29, Pull::Down), | ||
| 94 | &mut low, | ||
| 95 | &mut p.DMA_CH0, | ||
| 96 | ) | ||
| 97 | .await | ||
| 98 | .unwrap(); | ||
| 99 | adc.read_many( | ||
| 100 | &mut Channel::new_pin(&mut p.PIN_29, Pull::None), | ||
| 101 | &mut none, | ||
| 102 | &mut p.DMA_CH0, | ||
| 103 | ) | ||
| 104 | .await | ||
| 105 | .unwrap(); | ||
| 106 | adc.read_many_raw(&mut Channel::new_pin(&mut p.PIN_29, Pull::Up), &mut up, &mut p.DMA_CH0) | ||
| 107 | .await; | ||
| 108 | defmt::assert!(low.iter().zip(none.iter()).all(|(l, n)| *l >> 4 < *n as u16)); | ||
| 109 | defmt::assert!(up.iter().all(|s| s.good())); | ||
| 110 | defmt::assert!(none.iter().zip(up.iter()).all(|(n, u)| (*n as u16) < u.value())); | ||
| 111 | } | ||
| 112 | { | ||
| 113 | let mut temp = [0u16; 16]; | ||
| 114 | adc.read_many( | ||
| 115 | &mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), | ||
| 116 | &mut temp, | ||
| 117 | &mut p.DMA_CH0, | ||
| 118 | ) | ||
| 119 | .await | ||
| 120 | .unwrap(); | ||
| 121 | let temp = temp.map(convert_to_celsius); | ||
| 122 | defmt::assert!(temp.iter().all(|t| *t > 0.0)); | ||
| 123 | defmt::assert!(temp.iter().all(|t| *t < 60.0)); | ||
| 124 | } | ||
| 78 | 125 | ||
| 79 | info!("Test OK"); | 126 | info!("Test OK"); |
| 80 | cortex_m::asm::bkpt(); | 127 | cortex_m::asm::bkpt(); |
