From bf36bec9bb85caea613687ab9e0c1652f414b95c Mon Sep 17 00:00:00 2001 From: kalkyl Date: Wed, 5 Jun 2024 09:42:08 +0200 Subject: [PATCH] rp: Add multichannel ADC --- embassy-rp/src/adc.rs | 71 +++++++++++++++++++++++++++++++--- examples/rp/src/bin/adc_dma.rs | 54 ++++++++++++++++++++++++++ tests/rp/src/bin/adc.rs | 13 +++++++ 3 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 examples/rp/src/bin/adc_dma.rs diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 101c5b71f..be453d08e 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -221,16 +221,26 @@ impl<'d> Adc<'d, Async> { async fn read_many_inner( &mut self, - ch: &mut Channel<'_>, + channels: impl Iterator, buf: &mut [W], fcs_err: bool, div: u16, dma: impl Peripheral

, ) -> Result<(), Error> { + let mut rrobin = 0_u8; + for c in channels { + rrobin |= 1 << c; + } + let first_ch = rrobin.trailing_zeros() as u8; + if rrobin.count_ones() == 1 { + rrobin = 0; + } + let r = Self::regs(); // clear previous errors and set channel r.cs().modify(|w| { - w.set_ainsel(ch.channel()); + w.set_ainsel(first_ch); + w.set_rrobin(rrobin); w.set_err_sticky(true); // clear previous errors w.set_start_many(false); }); @@ -283,7 +293,49 @@ impl<'d> Adc<'d, Async> { } } + /// Sample multiple values from multiple channels using DMA. + /// Samples are stored in an interleaved fashion inside the buffer. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_multichannel( + &mut self, + ch: &mut [Channel<'_>], + buf: &mut [S], + div: u16, + dma: impl Peripheral

, + ) -> Result<(), Error> { + self.read_many_inner(ch.iter().map(|c| c.channel()), buf, false, div, dma) + .await + } + + /// Sample multiple values from multiple channels using DMA, with errors inlined in samples. + /// Samples are stored in an interleaved fashion inside the buffer. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_multichannel_raw( + &mut self, + ch: &mut [Channel<'_>], + buf: &mut [Sample], + div: u16, + dma: impl Peripheral

, + ) { + // errors are reported in individual samples + let _ = self + .read_many_inner( + ch.iter().map(|c| c.channel()), + unsafe { mem::transmute::<_, &mut [u16]>(buf) }, + true, + div, + dma, + ) + .await; + } + /// Sample multiple values from a channel using DMA. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 #[inline] pub async fn read_many( &mut self, @@ -292,10 +344,13 @@ impl<'d> Adc<'d, Async> { div: u16, dma: impl Peripheral

, ) -> Result<(), Error> { - self.read_many_inner(ch, buf, false, div, dma).await + self.read_many_inner([ch.channel()].into_iter(), buf, false, div, dma) + .await } - /// Sample multiple values from a channel using DMA with errors inlined in samples. + /// Sample multiple values from a channel using DMA, with errors inlined in samples. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 #[inline] pub async fn read_many_raw( &mut self, @@ -306,7 +361,13 @@ impl<'d> Adc<'d, Async> { ) { // errors are reported in individual samples let _ = self - .read_many_inner(ch, unsafe { mem::transmute::<_, &mut [u16]>(buf) }, true, div, dma) + .read_many_inner( + [ch.channel()].into_iter(), + unsafe { mem::transmute::<_, &mut [u16]>(buf) }, + true, + div, + dma, + ) .await; } } diff --git a/examples/rp/src/bin/adc_dma.rs b/examples/rp/src/bin/adc_dma.rs new file mode 100644 index 000000000..f755cf5bf --- /dev/null +++ b/examples/rp/src/bin/adc_dma.rs @@ -0,0 +1,54 @@ +//! This example shows how to use the RP2040 ADC with DMA, both single- and multichannel reads. +//! For multichannel, the samples are interleaved in the buffer: +//! `[ch1, ch2, ch3, ch4, ch1, ch2, ch3, ch4, ...]` +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::Pull; +use embassy_time::{Duration, Ticker}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + let mut dma = p.DMA_CH0; + let mut pin = Channel::new_pin(p.PIN_26, Pull::Up); + let mut pins = [ + Channel::new_pin(p.PIN_27, Pull::Down), + Channel::new_pin(p.PIN_28, Pull::None), + Channel::new_pin(p.PIN_29, Pull::Up), + Channel::new_temp_sensor(p.ADC_TEMP_SENSOR), + ]; + + const BLOCK_SIZE: usize = 100; + const NUM_CHANNELS: usize = 4; + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + // Read 100 samples from a single channel + let mut buf = [0_u16; BLOCK_SIZE]; + let div = 479; // 100kHz sample rate (48Mhz / 100kHz - 1) + adc.read_many(&mut pin, &mut buf, div, &mut dma).await.unwrap(); + info!("single: {:?} ...etc", buf[..8]); + + // Read 100 samples from 4 channels interleaved + let mut buf = [0_u16; { BLOCK_SIZE * NUM_CHANNELS }]; + let div = 119; // 100kHz sample rate (48Mhz / 100kHz * 4ch - 1) + adc.read_many_multichannel(&mut pins, &mut buf, div, &mut dma) + .await + .unwrap(); + info!("multi: {:?} ...etc", buf[..NUM_CHANNELS * 2]); + + ticker.next().await; + } +} diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index 29eda95bf..0f13e626f 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs @@ -130,6 +130,19 @@ async fn main(_spawner: Spawner) { defmt::assert!(temp.iter().all(|t| *t > 0.0)); defmt::assert!(temp.iter().all(|t| *t < 60.0)); } + { + let mut multi = [0u16; 2]; + let mut channels = [ + Channel::new_pin(&mut p.PIN_29, Pull::Up), + Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), + ]; + adc.read_many_multichannel(&mut channels, &mut multi, 1, &mut p.DMA_CH0) + .await + .unwrap(); + defmt::assert!(multi[0] > 3_000); + let temp = convert_to_celsius(multi[1]); + defmt::assert!(temp > 0.0 && temp < 60.0); + } info!("Test OK"); cortex_m::asm::bkpt();