From 74c165ece0a7a70c656b571718030e670955196f Mon Sep 17 00:00:00 2001 From: Dietrich Beck Date: Sat, 1 Jul 2023 20:17:34 +0200 Subject: [PATCH] adc & dma --- embassy-rp/src/adc.rs | 666 +++++++++++++++++++++++++++++++++++++++++- embassy-rp/src/dma.rs | 294 ++++++++++++++++++- 2 files changed, 944 insertions(+), 16 deletions(-) diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 699a0d61..3bf164d4 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -3,14 +3,19 @@ use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; +use embassy_hal_common::PeripheralRef; use embassy_sync::waitqueue::AtomicWaker; use embedded_hal_02::adc::{Channel, OneShot}; +use pac::adc::regs::{Cs, Fcs}; +use crate::dma::{Channel as DmaChannel, ContinuousTransfer, Dreq, Read, Transfer, Word}; use crate::gpio::Pin; use crate::interrupt::typelevel::Binding; use crate::interrupt::InterruptExt; +use crate::pac::dma::vals::TreqSel; use crate::peripherals::ADC; use crate::{interrupt, pac, peripherals, Peripheral}; + static WAKER: AtomicWaker = AtomicWaker::new(); #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -32,6 +37,555 @@ pub struct Adc<'d> { phantom: PhantomData<&'d ADC>, } +pub trait Input { + fn round_robin_byte(&self) -> u8; + fn ainsel_channel(&self) -> u8; + fn measure_temperature(&self) -> bool; + + // disable pull-up and enable pull-down resistors + fn prepare_resistors(&mut self) -> (); +} + +// convention: only Pin1 or Temp can be starting channels (ainsel) +#[derive(Copy, Clone)] +enum RoundRobinConfig { + // T: temperature is measured + // P: pin1 is ainsel (else temperature) + // R: ainsel is part of round robin + T, + TR, + PT, + PTR, + P, + PR, +} + +impl RoundRobinConfig { + // combine byte enabling temperature round robin + // and byte enabling pin1 round robin according to config + fn temp_pin1_byte(self, p1: u8) -> u8 { + use RoundRobinConfig::*; + let t = 1u8 << 4; // temperature channel has id 4 + match self { + T | PR => p1, + TR | PTR => t | p1, + PT => t, + P => 0, + } + } + + fn ainsel_channel(self, p1_channel: u8) -> u8 { + use RoundRobinConfig::*; + match self { + T | TR => 4, + _ => p1_channel, + } + } + + fn is_t(self) -> bool { + use RoundRobinConfig::*; + match self { + P | PR => false, + T | TR | PT | PTR => true, + } + } + fn set_t(&mut self) { + use RoundRobinConfig::*; + match self { + T | TR | PT | PTR => (), + P => *self = PT, + PR => *self = PTR, + } + } +} + +fn prepare_resistors<'d, PIN>(pin: &mut PIN) +where + PIN: Channel, ID = u8> + Pin, +{ + pin.pad_ctrl().modify(|w| { + w.set_ie(true); + let (pu, pd) = (false, true); // TODO there is another pull request related to this change, also check datasheet chapter 4.9 + w.set_pue(pu); + w.set_pde(pd); + }); +} + +pub fn round_robin_order(round_robin_byte: u8, ainsel_channel: u8) -> (u8, [Option; 5]) { + let mut result = [None; 5]; + let length = u8::count_ones(round_robin_byte) as u8; + let mut channel = ainsel_channel; + for i in 0..length { + let b = round_robin_byte >> (channel + 1); + channel = match b { + 0 => round_robin_byte.trailing_zeros() as u8, + _ => b.trailing_zeros() as u8 + channel + 1, + }; + result[i as usize] = Some(channel); + } + (length, result) +} + +pub struct Input0 { + // has to be temperature + config: RoundRobinConfig, +} +pub struct Input1<'d, 'a, Pin1> +where + Pin1: Channel, ID = u8> + Pin, +{ + pin1: &'a mut Pin1, + config: RoundRobinConfig, + phantom: PhantomData<&'d bool>, +} +pub struct Input2<'d, 'a, Pin1, Pin2> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, +{ + pin1: &'a mut Pin1, + pin2: &'a mut Pin2, + config: RoundRobinConfig, + phantom: PhantomData<&'d bool>, +} +pub struct Input3<'d, 'a, Pin1, Pin2, Pin3> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, + Pin3: Channel, ID = u8> + Pin, +{ + pin1: &'a mut Pin1, + pin2: &'a mut Pin2, + pin3: &'a mut Pin3, + config: RoundRobinConfig, + phantom: PhantomData<&'d bool>, +} +pub struct Input4<'d, 'a, Pin1, Pin2, Pin3, Pin4> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, + Pin3: Channel, ID = u8> + Pin, + Pin4: Channel, ID = u8> + Pin, +{ + pin1: &'a mut Pin1, + pin2: &'a mut Pin2, + pin3: &'a mut Pin3, + pin4: &'a mut Pin4, + config: RoundRobinConfig, + phantom: PhantomData<&'d bool>, +} + +impl Input0 { + pub fn add<'d, 'a, PIN>(self, pin: &'a mut PIN) -> Input1<'d, 'a, PIN> + where + PIN: Channel, ID = u8> + Pin, + { + Input1 { + pin1: pin, + config: self.config, + phantom: PhantomData {}, + } + } +} + +impl<'d, 'a, Pin1> Input1<'d, 'a, Pin1> +where + Pin1: Channel, ID = u8> + Pin, +{ + pub fn add(self, pin: &'a mut PIN) -> Input2<'d, 'a, Pin1, PIN> + where + PIN: Channel, ID = u8> + Pin, + { + let Input1 { pin1, config, .. } = self; + Input2 { + pin1, + pin2: pin, + config, + phantom: PhantomData {}, + } + } + pub fn add_temperature(mut self) -> Input1<'d, 'a, Pin1> { + self.config.set_t(); + self + } +} + +impl<'d, 'a, Pin1, Pin2> Input2<'d, 'a, Pin1, Pin2> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, +{ + pub fn add(self, pin: &'a mut PIN) -> Input3<'d, 'a, Pin1, Pin2, PIN> + where + PIN: Channel, ID = u8> + Pin, + { + let Input2 { pin1, pin2, config, .. } = self; + Input3 { + pin1, + pin2, + pin3: pin, + config, + phantom: PhantomData {}, + } + } + pub fn add_temperature(mut self) -> Input2<'d, 'a, Pin1, Pin2> { + self.config.set_t(); + self + } +} + +impl<'d, 'a, Pin1, Pin2, Pin3> Input3<'d, 'a, Pin1, Pin2, Pin3> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, + Pin3: Channel, ID = u8> + Pin, +{ + pub fn add(self, pin: &'a mut PIN) -> Input4<'d, 'a, Pin1, Pin2, Pin3, PIN> + where + PIN: Channel, ID = u8> + Pin, + { + let Input3 { + pin1, + pin2, + pin3, + config, + .. + } = self; + Input4 { + pin1, + pin2, + pin3, + pin4: pin, + config, + phantom: PhantomData {}, + } + } + pub fn add_temperature(mut self) -> Input3<'d, 'a, Pin1, Pin2, Pin3> { + self.config.set_t(); + self + } +} + +impl<'d, 'a, Pin1, Pin2, Pin3, Pin4> Input4<'d, 'a, Pin1, Pin2, Pin3, Pin4> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, + Pin3: Channel, ID = u8> + Pin, + Pin4: Channel, ID = u8> + Pin, +{ + pub fn add_temperature(mut self) -> Input4<'d, 'a, Pin1, Pin2, Pin3, Pin4> { + self.config.set_t(); + self + } +} + +impl Input for Input0 { + fn round_robin_byte(&self) -> u8 { + // it should not matter whether we return b0001_0000 or b0000_0000 + self.config.temp_pin1_byte(0) + } + + fn ainsel_channel(&self) -> u8 { + 4 // measure temperature + } + + fn measure_temperature(&self) -> bool { + true + } + + fn prepare_resistors(&mut self) -> () {} +} + +impl<'d, 'a, Pin1> Input for Input1<'d, 'a, Pin1> +where + Pin1: Channel, ID = u8> + Pin, +{ + fn round_robin_byte(&self) -> u8 { + let p1 = 1u8 << Pin1::channel(); + self.config.temp_pin1_byte(p1) + } + + fn ainsel_channel(&self) -> u8 { + self.config.ainsel_channel(Pin1::channel()) + } + + fn measure_temperature(&self) -> bool { + self.config.is_t() + } + + fn prepare_resistors(&mut self) -> () { + prepare_resistors(self.pin1); + } +} + +impl<'d, 'a, Pin1, Pin2> Input for Input2<'d, 'a, Pin1, Pin2> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, +{ + fn round_robin_byte(&self) -> u8 { + let p1 = 1u8 << Pin1::channel(); + let p2 = 1u8 << Pin2::channel(); + self.config.temp_pin1_byte(p1) | p2 + } + + fn ainsel_channel(&self) -> u8 { + self.config.ainsel_channel(Pin1::channel()) + } + + fn measure_temperature(&self) -> bool { + self.config.is_t() + } + + fn prepare_resistors(&mut self) -> () { + prepare_resistors(self.pin1); + prepare_resistors(self.pin2); + } +} +impl<'d, 'a, Pin1, Pin2, Pin3> Input for Input3<'d, 'a, Pin1, Pin2, Pin3> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, + Pin3: Channel, ID = u8> + Pin, +{ + fn round_robin_byte(&self) -> u8 { + let p1 = 1u8 << Pin1::channel(); + let p2 = 1u8 << Pin2::channel(); + let p3 = 1u8 << Pin3::channel(); + self.config.temp_pin1_byte(p1) | p2 | p3 + } + + fn ainsel_channel(&self) -> u8 { + self.config.ainsel_channel(Pin1::channel()) + } + + fn measure_temperature(&self) -> bool { + self.config.is_t() + } + fn prepare_resistors(&mut self) -> () { + prepare_resistors(self.pin1); + prepare_resistors(self.pin2); + prepare_resistors(self.pin3); + } +} +impl<'d, 'a, Pin1, Pin2, Pin3, Pin4> Input for Input4<'d, 'a, Pin1, Pin2, Pin3, Pin4> +where + Pin1: Channel, ID = u8> + Pin, + Pin2: Channel, ID = u8> + Pin, + Pin3: Channel, ID = u8> + Pin, + Pin4: Channel, ID = u8> + Pin, +{ + fn round_robin_byte(&self) -> u8 { + let p1 = 1u8 << Pin1::channel(); + let p2 = 1u8 << Pin2::channel(); + let p3 = 1u8 << Pin3::channel(); + let p4 = 1u8 << Pin4::channel(); + self.config.temp_pin1_byte(p1) | p2 | p3 | p4 + } + + fn ainsel_channel(&self) -> u8 { + self.config.ainsel_channel(Pin1::channel()) + } + + fn measure_temperature(&self) -> bool { + self.config.is_t() + } + fn prepare_resistors(&mut self) -> () { + prepare_resistors(self.pin1); + prepare_resistors(self.pin2); + prepare_resistors(self.pin3); + prepare_resistors(self.pin4); + } +} + +// argument use_in_round_robin is only relevent when more pins are added +pub fn input_temperature(use_in_round_robin: bool) -> Input0 { + Input0 { + config: match use_in_round_robin { + true => RoundRobinConfig::TR, + false => RoundRobinConfig::T, + }, + } +} + +// argument use_in_round_robin is only relevent when more pins or temperature is added +pub fn input_from_pin<'d, 'a, PIN>(pin: &'a mut PIN, use_in_round_robin: bool) -> Input1<'d, 'a, PIN> +where + PIN: Channel, ID = u8> + Pin, +{ + Input1 { + pin1: pin, + config: match use_in_round_robin { + true => RoundRobinConfig::PR, + false => RoundRobinConfig::P, + }, + phantom: PhantomData {}, + } +} + +#[derive(Copy, Clone)] +pub enum SamplingSpeed { + Fastest, // 500kS/s + Interval { n: u16, frac: u8 }, // n >= 96 +} + +impl SamplingSpeed { + fn set_div(&self, w: &mut rp_pac::adc::regs::Div) { + match self { + Self::Fastest => { + w.set_int(0); + w.set_frac(0) + } + Self::Interval { n, frac } => { + assert!(*n >= 96); + w.set_int(*n); + w.set_frac(*frac); + } + } + } + + pub fn clock_cycles_per_256_samples(&self) -> u32 { + match self { + Self::Fastest => 256 * 96, + Self::Interval { n, frac } => { + assert!(*n >= 96); + 256 * (1 + *n as u32) + *frac as u32 + } + } + } + + // default adc clock speed is 48MHz + pub fn micros_per_sample(&self, clock_hz: u32) -> f32 { + self.clock_cycles_per_256_samples() as f32 / 256.0 / clock_hz as f32 * 1_000_000.0 + } +} + +pub struct ContinuousAdc<'a, 'b, 'c, 'd, 'r, W: Word, C1: DmaChannel, C2: DmaChannel, In: Input> { + #[allow(dead_code)] + adc: Adc<'d>, + transfer: ContinuousTransfer<'a, 'b, 'c, 'r, W, C1, C2>, + input: In, + corrupted: bool, +} + +impl<'a, 'b, 'c, 'd, 'r, W: Word, C1: DmaChannel, C2: DmaChannel, In: Input> + ContinuousAdc<'a, 'b, 'c, 'd, 'r, W, C1, C2, In> +{ + pub fn start_new( + adc: Adc<'d>, + ch1: PeripheralRef<'a, C1>, + ch2: PeripheralRef<'a, C2>, + mut input: In, + speed: SamplingSpeed, + control_input: &'c mut [[u32; 4]; 2], + buffer: &'b mut [W], + ) -> ContinuousAdc<'a, 'b, 'c, 'd, 'r, W, C1, C2, In> { + assert!(W::size() as u8 <= 1); // u16 or u8 (will right-shift) allowed TODO static_assert possible? + + // configure adc + let r = Adc::regs(); + assert!(r.fcs().read().empty()); + input.prepare_resistors(); + if input.measure_temperature() { + r.cs().modify(|w| w.set_ts_en(true)); + while !r.cs().read().ready() {} // blocking wait for ready + } + // set input channels + r.cs().modify(|w| { + w.set_ainsel(input.ainsel_channel()); + w.set_rrobin(input.round_robin_byte()); + }); + r.fcs().modify(|w| { + w.set_en(true); + w.set_dreq_en(true); + w.set_thresh(1); + w.set_err(false); + w.set_shift(W::size() as u8 == 0); + }); + r.div().modify(|w| { + speed.set_div(w); + }); + + // create and trigger transfer + let transfer = ContinuousTransfer::start_new( + ch1, + ch2, + control_input, + buffer, + TreqSel(Dreq::ADC as u8), + // SAFETY: adc is owned and cannot be used for anything else, fifo must only accessed by dma channel + Read::Constant(unsafe { &*(r.fifo().as_ptr() as *const W) }), + ); + + // start adc + compiler_fence(Ordering::SeqCst); + r.cs().modify(|w| { + w.set_start_many(true); + }); + + ContinuousAdc { + adc, + transfer, + input, + corrupted: false, + } + } + + pub async fn next<'new_buf>( + self, + buffer: &'new_buf mut [W], + ) -> (ContinuousAdc<'a, 'new_buf, 'c, 'd, 'r, W, C1, C2, In>, bool) { + let ContinuousAdc { + adc, + transfer, + input, + corrupted, + } = self; + let (transfer, in_time) = transfer.next(buffer).await; + ( + ContinuousAdc { + adc, + transfer, + input, + corrupted: corrupted | !in_time, + }, + in_time, + ) + } + + pub async fn stop(self) -> Adc<'d> { + self.transfer.stop().await; + + // stop adc + let r = Adc::regs(); + r.cs().modify(|w| { + w.set_start_many(false); + }); + if self.input.measure_temperature() { + r.cs().modify(|w| w.set_ts_en(false)); + } + Adc::fifo_drain().await; + + if self.corrupted { + // TODO this is a fix to a problem where round robin order is shifted and the first few samples of any following start_many measurements seem to have random order + // TODO I was not able to find the real cause, but it would only appear with a certain chance if the next buffer was not provided in time + // completely reset adc + let reset = Adc::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + let r = Adc::regs(); + // Enable ADC + r.cs().write(|w| w.set_en(true)); + // Wait for ADC ready + while !r.cs().read().ready() {} + } + + // you only get your adc back if you stop the ContinuousAdc like intended + // (i.e. don't drop it while it is still running) + self.adc + } +} + impl<'d> Adc<'d> { #[inline] fn regs() -> pac::adc::Adc { @@ -80,16 +634,93 @@ impl<'d> Adc<'d> { .await; } + async fn fifo_drain() { + let r = Self::regs(); + Self::wait_for_ready().await; + // drain remaining samples from the FIFO + while !r.fcs().read().empty() { + let _ = r.fifo().read().val(); + } + // reset fifo so samples don't fill the fifo when we don't want it + r.fcs().write_value(Fcs(0)); + } + + pub async fn dma_read<'a, C: DmaChannel, W: Word, I: Input>( + &mut self, + input: &mut I, + dma_ch: PeripheralRef<'a, C>, + data: &'a mut [W], + speed: SamplingSpeed, + ) -> () { + let r = Self::regs(); + assert!(r.fcs().read().empty()); + + input.prepare_resistors(); + + if input.measure_temperature() { + // enable temperature + r.cs().modify(|w| w.set_ts_en(true)); + if !r.cs().read().ready() { + Self::wait_for_ready().await; + } + } + + // set input channels + r.cs().modify(|w| { + w.set_ainsel(input.ainsel_channel()); + w.set_rrobin(input.round_robin_byte()); + }); + + r.fcs().modify(|w| { + w.set_en(true); + w.set_dreq_en(true); + w.set_thresh(1); + w.set_err(false); + w.set_shift(W::size() as u8 == 0); + }); + r.div().modify(|w| { + speed.set_div(w); + }); + + let p = dma_ch.regs(); + p.write_addr().write_value(data.as_ptr() as u32); + p.read_addr().write_value(r.fifo().as_ptr() as u32); + p.trans_count().write_value(data.len() as u32); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + w.set_treq_sel(TreqSel(Dreq::ADC as u8)); + w.set_data_size(W::size()); + w.set_incr_read(false); + w.set_incr_write(true); + w.set_chain_to(dma_ch.number()); + w.set_en(true); + }); // trigger dma start + compiler_fence(Ordering::SeqCst); + + // start adc + r.cs().modify(|w| { + w.set_start_many(true); + }); + + // wait for finish + Transfer::new(dma_ch).await; + + // stop adc + r.cs().modify(|w| { + w.set_start_many(false); + }); + + // disable temperature sensing + if input.measure_temperature() { + r.cs().modify(|w| w.set_ts_en(false)); + } + + Self::fifo_drain().await; + } + pub async fn read, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 { let r = Self::regs(); - // disable pull-down and pull-up resistors - // pull-down resistors are enabled by default - pin.pad_ctrl().modify(|w| { - w.set_ie(true); - let (pu, pd) = (false, false); - w.set_pue(pu); - w.set_pde(pd); - }); + prepare_resistors(pin); r.cs().modify(|w| { w.set_ainsel(PIN::channel()); w.set_start_once(true) @@ -114,12 +745,7 @@ impl<'d> Adc<'d> { pub fn blocking_read, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 { let r = Self::regs(); - pin.pad_ctrl().modify(|w| { - w.set_ie(true); - let (pu, pd) = (false, false); - w.set_pue(pu); - w.set_pde(pd); - }); + prepare_resistors(pin); r.cs().modify(|w| { w.set_ainsel(PIN::channel()); w.set_start_once(true) @@ -141,6 +767,18 @@ impl<'d> Adc<'d> { } } +impl<'d> Drop for Adc<'d> { + fn drop(&mut self) { + let r = Self::regs(); + r.cs().write_value(Cs(0)); + while !r.cs().read().ready() {} + while !r.fcs().read().empty() { + let _ = r.fifo().read().val(); + } + r.fcs().write_value(Fcs(0)); + } +} + macro_rules! impl_pin { ($pin:ident, $channel:expr) => { impl Channel> for peripherals::$pin { diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index 1a458778..67e1d985 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs @@ -1,12 +1,13 @@ //! Direct Memory Access (DMA) -use core::future::Future; +use core::future::{poll_fn, Future}; use core::pin::Pin; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::{Context, Poll}; use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use pac::dma::vals::DataSize; +use pac::dma::regs::CtrlTrig; +use pac::dma::vals::{DataSize, TreqSel}; use crate::interrupt::InterruptExt; use crate::pac::dma::vals; @@ -190,6 +191,247 @@ impl<'a, C: Channel> Future for Transfer<'a, C> { } } +pub enum Read<'a, W: Word> { + Constant(&'a W), + Increase(&'a [W]), + // TODO ring also possible, but more complicated due to generic size and alignment requirements +} + +impl<'a, W: Word> Read<'a, W> { + fn is_increase(&self) -> bool { + match *self { + Self::Constant(_) => false, + Self::Increase(_) => true, + } + } + + fn address(&self) -> u32 { + match *self { + Self::Constant(w) => (w as *const W) as u32, + Self::Increase(w) => w.as_ptr() as u32, + } + } + + fn forward(&mut self, n: usize) -> () { + if let Self::Increase(w) = *self { + *self = Self::Increase(&w[n..]); + } + } +} + +struct InnerChannels<'a, C1: Channel, C2: Channel> { + data: PeripheralRef<'a, C1>, + control: PeripheralRef<'a, C2>, +} + +impl<'a, C1: Channel, C2: Channel> Drop for InnerChannels<'a, C1, C2> { + fn drop(&mut self) { + pac::DMA + .chan_abort() + .modify(|m| m.set_chan_abort((1 << self.data.number()) | (1 << self.control.number()))); + + // wait until both channels are ready again, this should go quite fast so no async used here + while self.data.regs().ctrl_trig().read().busy() || self.control.regs().ctrl_trig().read().busy() {} + } +} + +pub struct ContinuousTransfer<'a, 'b, 'c, 'r, W: Word, C1: Channel, C2: Channel> { + channels: InnerChannels<'a, C1, C2>, + #[allow(dead_code)] // reference is kept to signal that dma channels are writing to it + buffer: &'b mut [W], + control_input: &'c mut [[u32; 4]; 2], + dreq: TreqSel, + read: Read<'r, W>, +} + +impl<'a, 'b, 'c, 'r, W: Word, C1: Channel, C2: Channel> ContinuousTransfer<'a, 'b, 'c, 'r, W, C1, C2> { + pub fn start_new( + ch1: PeripheralRef<'a, C1>, + ch2: PeripheralRef<'a, C2>, + control_input: &'c mut [[u32; 4]; 2], + buffer: &'b mut [W], + dreq: TreqSel, + mut read: Read<'r, W>, + ) -> ContinuousTransfer<'a, 'b, 'c, 'r, W, C1, C2> { + let channels = InnerChannels { + data: ch1, + control: ch2, + }; + + // configure what control channel writes + // using registers: READ_ADDR, WRITE_ADDR, TRANS_COUNT, CTRL_TRIG + let mut w = CtrlTrig(0); + w.set_treq_sel(dreq); + w.set_data_size(W::size()); + w.set_incr_read(read.is_increase()); + w.set_incr_write(true); + w.set_chain_to(channels.data.number()); // chain disabled by default + w.set_en(true); + w.set_irq_quiet(false); + + *control_input = [ + [read.address(), buffer.as_ptr() as u32, buffer.len() as u32, w.0], // first control write + [0; 4], // Null trigger to stop + ]; + + // Configure data channel + // will be set by control channel + let pd = channels.data.regs(); + pd.read_addr().write_value(0); + pd.write_addr().write_value(0); + pd.trans_count().write_value(0); + pd.al1_ctrl().write_value(0); + + // Configure control channel + let pc = channels.control.regs(); + pc.write_addr().write_value(pd.read_addr().as_ptr() as u32); + pc.read_addr().write_value(control_input.as_ptr() as u32); + pc.trans_count().write_value(4); // each control input is 4 u32s long + + // trigger control channel + compiler_fence(Ordering::SeqCst); + pc.ctrl_trig().write(|w| { + w.set_treq_sel(TreqSel::PERMANENT); + w.set_data_size(rp_pac::dma::vals::DataSize::SIZE_WORD); // 4 byte + w.set_incr_read(true); // step through control_input + w.set_incr_write(true); // yes, but ring is required + w.set_ring_sel(true); // wrap write addresses + w.set_ring_size(4); // 1 << 4 = 16 = 4 * sizeof(u32) bytes + w.set_chain_to(channels.control.number()); // disable chain, data channel is triggered by write + w.set_irq_quiet(false); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + + // wait until control ran + while pc.ctrl_trig().read().busy() {} + + // reset control + control_input[0] = [0; 4]; + pc.read_addr().write_value(control_input.as_ptr() as u32); + + read.forward(buffer.len()); + + ContinuousTransfer { + channels, + buffer, + control_input, + dreq, + read, + } + } + + pub async fn next<'new_buf>( + self, + buffer: &'new_buf mut [W], + ) -> (ContinuousTransfer<'a, 'new_buf, 'c, 'r, W, C1, C2>, bool) { + let ContinuousTransfer { + channels, + buffer: _old, // is free now, and the compiler knows it + control_input, + dreq, + mut read, + } = self; + + let pc = channels.control.regs(); + let pd = channels.data.regs(); + + let mut w = CtrlTrig(0); + w.set_treq_sel(dreq); + w.set_data_size(W::size()); + w.set_incr_read(read.is_increase()); + w.set_incr_write(true); + w.set_chain_to(channels.data.number()); // chain disabled by default + w.set_en(true); + w.set_irq_quiet(false); + + // configure control + control_input[0] = [read.address(), buffer.as_ptr() as u32, buffer.len() as u32, w.0]; + + // enable chain, now we can't change control safely anymore + compiler_fence(Ordering::SeqCst); + pd.al1_ctrl().write_value({ + let mut ctrl = pd.ctrl_trig().read(); + ctrl.set_chain_to(channels.control.number()); + ctrl.0 + }); + + if pc.read_addr().read() == control_input.as_ptr() as u32 && pd.ctrl_trig().read().busy() { + poll_fn(|cx: &mut Context<'_>| { + // the more efficient solution would be to use the interrupts, + // but I was not able to get it working robustly + cx.waker().wake_by_ref(); + if pc.read_addr().read() == control_input.as_ptr() as u32 + 16 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // reset control + assert!(!pc.ctrl_trig().read().busy()); + control_input[0] = [0; 4]; + pc.read_addr().write_value(control_input.as_ptr() as u32); + + read.forward(buffer.len()); + + ( + ContinuousTransfer { + channels, + buffer, + control_input, + dreq, + read, + }, + true, + ) + } else { + if pc.read_addr().read() == control_input.as_ptr() as u32 { + // trigger control to restart loop + pc.ctrl_trig().write_value(pc.ctrl_trig().read()); + compiler_fence(Ordering::SeqCst); + } + // if control read already moved, data has already been activated + + // wait for control to complete + while pc.ctrl_trig().read().busy() {} + // reset control + control_input[0] = [0; 4]; + pc.read_addr().write_value(control_input.as_ptr() as u32); + + read.forward(buffer.len()); + + ( + ContinuousTransfer { + channels, + control_input, + buffer, + dreq, + read, + }, + false, + ) + } + } + + pub async fn stop(self) { + // when no longer enabling the chain, data simply stops + poll_fn(|cx| { + // using interrupts would be nicer + cx.waker().wake_by_ref(); + if self.channels.data.regs().ctrl_trig().read().busy() { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } + + pub fn abort(self) {} // drop channels +} + pub(crate) const CHANNEL_COUNT: usize = 12; const NEW_AW: AtomicWaker = AtomicWaker::new(); static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; @@ -290,3 +532,51 @@ channel!(DMA_CH8, 8); channel!(DMA_CH9, 9); channel!(DMA_CH10, 10); channel!(DMA_CH11, 11); + +// TODO as in rp2040 datasheet 2.5.3.2, dreq can only be used by one +// channel at a time to prevent errors. Should we enforce this? +#[allow(non_camel_case_types, dead_code)] +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum Dreq { + PIO0_TX0 = 0x0, + PIO0_TX1 = 0x1, + PIO0_TX2 = 0x2, + PIO0_TX3 = 0x3, + PIO0_RX0 = 0x4, + PIO0_RX1 = 0x5, + PIO0_RX2 = 0x6, + PIO0_RX3 = 0x7, + PIO1_TX0 = 0x8, + PIO1_TX1 = 0x9, + PIO1_TX2 = 0xa, + PIO1_TX3 = 0xb, + PIO1_RX0 = 0xc, + PIO1_RX1 = 0xd, + PIO1_RX2 = 0xe, + PIO1_RX3 = 0xf, + SPI0_TX = 0x10, + SPI0_RX = 0x11, + SPI1_TX = 0x12, + SPI1_RX = 0x13, + UART0_TX = 0x14, + UART0_RX = 0x15, + UART1_TX = 0x16, + UART1_RX = 0x17, + PWM_WRAP0 = 0x18, + PWM_WRAP1 = 0x19, + PWM_WRAP2 = 0x1a, + PWM_WRAP3 = 0x1b, + PWM_WRAP4 = 0x1c, + PWM_WRAP5 = 0x1d, + PWM_WRAP6 = 0x1e, + PWM_WRAP7 = 0x1f, + I2C0_TX = 0x20, + I2C0_RX = 0x21, + I2C1_TX = 0x22, + I2C1_RX = 0x23, + ADC = 0x24, + XIP_STREAM = 0x25, + XIP_SSITX = 0x26, + XIP_SSIRX = 0x27, +}