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<W: dma::Word>(
         &mut self,
-        ch: &mut Channel<'_>,
+        channels: impl Iterator<Item = u8>,
         buf: &mut [W],
         fcs_err: bool,
         div: u16,
         dma: impl Peripheral<P = impl dma::Channel>,
     ) -> 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<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.
+    /// `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<S: AdcSample>(
         &mut self,
@@ -292,10 +344,13 @@ impl<'d> Adc<'d, Async> {
         div: u16,
         dma: impl Peripheral<P = impl dma::Channel>,
     ) -> 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();