adc & dma

This commit is contained in:
Dietrich Beck
2023-07-01 20:17:34 +02:00
parent ba43444292
commit 74c165ece0
2 changed files with 944 additions and 16 deletions

View File

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