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

@ -3,14 +3,19 @@ use core::marker::PhantomData;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll; use core::task::Poll;
use embassy_hal_common::PeripheralRef;
use embassy_sync::waitqueue::AtomicWaker; use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_02::adc::{Channel, OneShot}; 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::gpio::Pin;
use crate::interrupt::typelevel::Binding; use crate::interrupt::typelevel::Binding;
use crate::interrupt::InterruptExt; use crate::interrupt::InterruptExt;
use crate::pac::dma::vals::TreqSel;
use crate::peripherals::ADC; use crate::peripherals::ADC;
use crate::{interrupt, pac, peripherals, Peripheral}; use crate::{interrupt, pac, peripherals, Peripheral};
static WAKER: AtomicWaker = AtomicWaker::new(); static WAKER: AtomicWaker = AtomicWaker::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -32,6 +37,555 @@ pub struct Adc<'d> {
phantom: PhantomData<&'d ADC>, 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<Adc<'d>, 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<u8>; 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<Adc<'d>, ID = u8> + Pin,
{
pin1: &'a mut Pin1,
config: RoundRobinConfig,
phantom: PhantomData<&'d bool>,
}
pub struct Input2<'d, 'a, Pin1, Pin2>
where
Pin1: Channel<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, ID = u8> + Pin,
Pin3: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, ID = u8> + Pin,
Pin3: Channel<Adc<'d>, ID = u8> + Pin,
Pin4: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
{
Input1 {
pin1: pin,
config: self.config,
phantom: PhantomData {},
}
}
}
impl<'d, 'a, Pin1> Input1<'d, 'a, Pin1>
where
Pin1: Channel<Adc<'d>, ID = u8> + Pin,
{
pub fn add<PIN>(self, pin: &'a mut PIN) -> Input2<'d, 'a, Pin1, PIN>
where
PIN: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, ID = u8> + Pin,
{
pub fn add<PIN>(self, pin: &'a mut PIN) -> Input3<'d, 'a, Pin1, Pin2, PIN>
where
PIN: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, ID = u8> + Pin,
Pin3: Channel<Adc<'d>, ID = u8> + Pin,
{
pub fn add<PIN>(self, pin: &'a mut PIN) -> Input4<'d, 'a, Pin1, Pin2, Pin3, PIN>
where
PIN: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, ID = u8> + Pin,
Pin3: Channel<Adc<'d>, ID = u8> + Pin,
Pin4: Channel<Adc<'d>, 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<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, ID = u8> + Pin,
Pin3: Channel<Adc<'d>, 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<Adc<'d>, ID = u8> + Pin,
Pin2: Channel<Adc<'d>, ID = u8> + Pin,
Pin3: Channel<Adc<'d>, ID = u8> + Pin,
Pin4: Channel<Adc<'d>, 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<Adc<'d>, 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> { impl<'d> Adc<'d> {
#[inline] #[inline]
fn regs() -> pac::adc::Adc { fn regs() -> pac::adc::Adc {
@ -80,16 +634,93 @@ impl<'d> Adc<'d> {
.await; .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<PIN: Channel<Adc<'d>, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 { pub async fn read<PIN: Channel<Adc<'d>, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 {
let r = Self::regs(); let r = Self::regs();
// disable pull-down and pull-up resistors prepare_resistors(pin);
// 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);
});
r.cs().modify(|w| { r.cs().modify(|w| {
w.set_ainsel(PIN::channel()); w.set_ainsel(PIN::channel());
w.set_start_once(true) w.set_start_once(true)
@ -114,12 +745,7 @@ impl<'d> Adc<'d> {
pub fn blocking_read<PIN: Channel<Adc<'d>, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 { pub fn blocking_read<PIN: Channel<Adc<'d>, ID = u8> + Pin>(&mut self, pin: &mut PIN) -> u16 {
let r = Self::regs(); let r = Self::regs();
pin.pad_ctrl().modify(|w| { prepare_resistors(pin);
w.set_ie(true);
let (pu, pd) = (false, false);
w.set_pue(pu);
w.set_pde(pd);
});
r.cs().modify(|w| { r.cs().modify(|w| {
w.set_ainsel(PIN::channel()); w.set_ainsel(PIN::channel());
w.set_start_once(true) 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 { macro_rules! impl_pin {
($pin:ident, $channel:expr) => { ($pin:ident, $channel:expr) => {
impl Channel<Adc<'static>> for peripherals::$pin { impl Channel<Adc<'static>> for peripherals::$pin {

View File

@ -1,12 +1,13 @@
//! Direct Memory Access (DMA) //! Direct Memory Access (DMA)
use core::future::Future; use core::future::{poll_fn, Future};
use core::pin::Pin; use core::pin::Pin;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
use core::task::{Context, Poll}; use core::task::{Context, Poll};
use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker; 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::interrupt::InterruptExt;
use crate::pac::dma::vals; 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; pub(crate) const CHANNEL_COUNT: usize = 12;
const NEW_AW: AtomicWaker = AtomicWaker::new(); const NEW_AW: AtomicWaker = AtomicWaker::new();
static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT];
@ -290,3 +532,51 @@ channel!(DMA_CH8, 8);
channel!(DMA_CH9, 9); channel!(DMA_CH9, 9);
channel!(DMA_CH10, 10); channel!(DMA_CH10, 10);
channel!(DMA_CH11, 11); 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,
}