Merge pull request #3044 from kalkyl/adc-multi

rp: Add multichannel ADC
This commit is contained in:
Henrik Alsér 2024-06-22 21:05:17 +00:00 committed by GitHub
commit cfe8561550
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 135 additions and 5 deletions

View File

@ -219,18 +219,30 @@ impl<'d> Adc<'d, Async> {
} }
} }
// Note for refactoring: we don't require the actual Channels here, just the channel numbers.
// The public api is responsible for asserting ownership of the actual Channels.
async fn read_many_inner<W: dma::Word>( async fn read_many_inner<W: dma::Word>(
&mut self, &mut self,
ch: &mut Channel<'_>, channels: impl Iterator<Item = u8>,
buf: &mut [W], buf: &mut [W],
fcs_err: bool, fcs_err: bool,
div: u16, div: u16,
dma: impl Peripheral<P = impl dma::Channel>, dma: impl Peripheral<P = impl dma::Channel>,
) -> Result<(), Error> { ) -> 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(); let r = Self::regs();
// clear previous errors and set channel // clear previous errors and set channel
r.cs().modify(|w| { 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_err_sticky(true); // clear previous errors
w.set_start_many(false); w.set_start_many(false);
}); });
@ -283,7 +295,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<S: AdcSample>(
&mut self,
ch: &mut [Channel<'_>],
buf: &mut [S],
div: u16,
dma: impl Peripheral<P = impl dma::Channel>,
) -> 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<P = impl dma::Channel>,
) {
// 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. /// 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] #[inline]
pub async fn read_many<S: AdcSample>( pub async fn read_many<S: AdcSample>(
&mut self, &mut self,
@ -292,10 +346,13 @@ impl<'d> Adc<'d, Async> {
div: u16, div: u16,
dma: impl Peripheral<P = impl dma::Channel>, dma: impl Peripheral<P = impl dma::Channel>,
) -> Result<(), Error> { ) -> 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] #[inline]
pub async fn read_many_raw( pub async fn read_many_raw(
&mut self, &mut self,
@ -306,7 +363,13 @@ impl<'d> Adc<'d, Async> {
) { ) {
// errors are reported in individual samples // errors are reported in individual samples
let _ = self 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; .await;
} }
} }

View File

@ -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;
}
}

View File

@ -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 > 0.0));
defmt::assert!(temp.iter().all(|t| *t < 60.0)); defmt::assert!(temp.iter().all(|t| *t < 60.0));
} }
{
let mut multi = [0u16; 2];
let mut channels = [
Channel::new_pin(&mut p.PIN_26, 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"); info!("Test OK");
cortex_m::asm::bkpt(); cortex_m::asm::bkpt();