multi-sample-rate support, evk fixes
This commit is contained in:
+40
-53
@@ -70,29 +70,22 @@ impl core::fmt::Display for DmaError {
|
||||
/// Slot-based DMA ring
|
||||
pub struct DmaRing<const N: usize, const MAX_SLOT_BYTES: usize> {
|
||||
dma: pac::DMA0,
|
||||
|
||||
/// Destination peripheral register (FIFO write register)
|
||||
dst_reg: *mut u32,
|
||||
|
||||
// SAFETY: only written by USB task (on start)
|
||||
pub(crate) channel_desc: UnsafeCell<DescriptorTable>,
|
||||
// SAFETY: only written by USB task (on start)
|
||||
pub(crate) desc: UnsafeCell<RingDescriptors<N>>,
|
||||
slots: UnsafeCell<[[u8; MAX_SLOT_BYTES]; N]>,
|
||||
|
||||
/// Effective bytes per slot. Maybe be smaller than MAX_SLOT_BYTES (e.g. at lower sample rates), as the setup is designed for constant rate not constant size.
|
||||
slot_bytes: usize,
|
||||
/// How many bytes to transfer to the FIFO
|
||||
/// Effective bytes per slot (atomic for interior mutability)
|
||||
slot_bytes: AtomicUsize,
|
||||
word_bytes: usize,
|
||||
|
||||
// SAFETY: producer only
|
||||
write_slot: UnsafeCell<usize>,
|
||||
write_off: UnsafeCell<usize>,
|
||||
|
||||
produced: AtomicUsize,
|
||||
consumed: AtomicUsize,
|
||||
|
||||
/// Leave at least one slot empty so producer never overwrites a slot DMA may still read.
|
||||
safety_gap: usize,
|
||||
pub produced_bytes: AtomicUsize,
|
||||
pub consumed_bytes: AtomicUsize,
|
||||
@@ -132,7 +125,7 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
}; N],
|
||||
}),
|
||||
slots: UnsafeCell::new([[0u8; MAX_SLOT_BYTES]; N]),
|
||||
slot_bytes: MAX_SLOT_BYTES,
|
||||
slot_bytes: AtomicUsize::new(MAX_SLOT_BYTES),
|
||||
word_bytes,
|
||||
write_slot: UnsafeCell::new(0),
|
||||
write_off: UnsafeCell::new(0),
|
||||
@@ -149,9 +142,10 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
self.safety_gap = gap_slots.min(N);
|
||||
}
|
||||
pub fn slot_size(&self) -> usize {
|
||||
self.slot_bytes
|
||||
self.slot_bytes.load(Ordering::Acquire)
|
||||
}
|
||||
pub fn set_slot_size(&mut self, slot_bytes: usize) -> Result<(), ConfigError> {
|
||||
|
||||
pub fn set_slot_size(&self, slot_bytes: usize) -> Result<(), ConfigError> {
|
||||
if slot_bytes == 0 {
|
||||
return Err(ConfigError::SlotTooSmall);
|
||||
}
|
||||
@@ -161,8 +155,13 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
if slot_bytes % self.word_bytes != 0 {
|
||||
return Err(ConfigError::SlotNotAligned);
|
||||
}
|
||||
self.slot_bytes = slot_bytes;
|
||||
self.reset_producer();
|
||||
|
||||
// Update atomic size
|
||||
self.slot_bytes.store(slot_bytes, Ordering::Release);
|
||||
|
||||
// Re-initialize descriptors and reset producer state safely through internal mutability
|
||||
self.init_descriptors();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -185,7 +184,8 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
break;
|
||||
}
|
||||
|
||||
let cap = self.slot_bytes - *write_off;
|
||||
let slot_bytes = self.slot_bytes.load(Ordering::Acquire);
|
||||
let cap = slot_bytes - *write_off;
|
||||
let n = core::cmp::min(cap, data.len());
|
||||
|
||||
unsafe {
|
||||
@@ -197,7 +197,7 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
written += n;
|
||||
data = &data[n..];
|
||||
|
||||
if *write_off == self.slot_bytes {
|
||||
if *write_off == slot_bytes {
|
||||
// publish completed slot
|
||||
compiler_fence(Ordering::Release);
|
||||
self.produced.fetch_add(1, Ordering::Release);
|
||||
@@ -221,8 +221,9 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
let consumed = self.consumed.load(Ordering::Relaxed);
|
||||
if consumed < produced {
|
||||
self.consumed.fetch_add(slots, Ordering::Release);
|
||||
let slot_bytes = self.slot_bytes.load(Ordering::Acquire);
|
||||
self.consumed_bytes
|
||||
.fetch_add(slots * self.slot_bytes, Ordering::Relaxed);
|
||||
.fetch_add(slots * slot_bytes, Ordering::Relaxed);
|
||||
Ok(())
|
||||
} else {
|
||||
defmt::error!("DMA underrun!");
|
||||
@@ -243,25 +244,23 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
loop {
|
||||
let consumed_start = self.consumed.load(Ordering::Acquire);
|
||||
|
||||
let reg_1 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
|
||||
let reg_2 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
|
||||
let reg_1 = (self.dma.channel19.xfercfg.read().bits() >> 16) & 0x3FF;
|
||||
let reg_2 = (self.dma.channel19.xfercfg.read().bits() >> 16) & 0x3FF;
|
||||
|
||||
let consumed_end = self.consumed.load(Ordering::Acquire);
|
||||
|
||||
if consumed_start == consumed_end && reg_1 == reg_2 {
|
||||
// 1. Map the hardware remaining countdown into a clean byte count
|
||||
let remaining_bytes = if reg_1 == 0x3ff {
|
||||
0 // 0x3FF means all transfers completed, 0 bytes remaining
|
||||
let remaining_bytes = if reg_1 == 0x3FF {
|
||||
0
|
||||
} else {
|
||||
// Formula from NXP manual: (XFERCOUNT + 1) * Data Width
|
||||
(reg_1 + 1) * self.word_bytes
|
||||
(reg_1 as usize + 1) * self.word_bytes
|
||||
};
|
||||
|
||||
// 2. Total bytes consumed in this specific active slot
|
||||
let active_slot_consumed = self.slot_bytes - remaining_bytes;
|
||||
// Active slot consumed calculation accounts for dynamic slot size
|
||||
let slot_bytes = self.slot_bytes.load(Ordering::Acquire);
|
||||
let active_slot_consumed = slot_bytes.saturating_sub(remaining_bytes);
|
||||
|
||||
// 3. Combine with your software index history accumulator
|
||||
return consumed_start * self.slot_bytes + active_slot_consumed;
|
||||
return consumed_start * slot_bytes + active_slot_consumed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,8 +310,8 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
|
||||
fn reset_producer(&self) {
|
||||
unsafe {
|
||||
*(&mut *self.write_slot.get()) = 0;
|
||||
*(&mut *self.write_off.get()) = 0;
|
||||
*self.write_slot.get() = 0;
|
||||
*self.write_off.get() = 0;
|
||||
}
|
||||
self.produced.store(0, Ordering::Relaxed);
|
||||
self.produced_bytes.store(0, Ordering::Relaxed);
|
||||
@@ -324,45 +323,30 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
let fill = self.fill_slots();
|
||||
fill >= N.wrapping_sub(self.safety_gap)
|
||||
}
|
||||
fn reset_producer_init_only(&self) {
|
||||
unsafe {
|
||||
*self.write_slot.get() = 0;
|
||||
}
|
||||
unsafe {
|
||||
*self.write_off.get() = 0;
|
||||
}
|
||||
|
||||
self.produced.store(0, Ordering::Relaxed);
|
||||
self.consumed.store(0, Ordering::Relaxed);
|
||||
|
||||
self.produced_bytes.store(0, Ordering::Relaxed);
|
||||
self.consumed_bytes.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn init_descriptors(&self) {
|
||||
let slot_bytes = self.slot_bytes.load(Ordering::Acquire);
|
||||
let slots = unsafe { &mut *self.slots.get() };
|
||||
let desc = unsafe { &mut *self.desc.get() };
|
||||
let chan_desc = unsafe { &mut *self.channel_desc.get() };
|
||||
defmt::debug!("slots base: &{:x}", self.slots.get());
|
||||
|
||||
// Pre-fill with silence so underrun replays silence.
|
||||
// Pre-fill active slot regions with silence
|
||||
for i in 0..N {
|
||||
slots[i][..self.slot_bytes].fill(0);
|
||||
slots[i][..slot_bytes].fill(0);
|
||||
}
|
||||
|
||||
let transfers = (self.slot_bytes / self.word_bytes) as u32;
|
||||
let transfers = (slot_bytes / self.word_bytes) as u32;
|
||||
|
||||
for i in 0..N {
|
||||
let src_start = slots[i].as_ptr() as usize;
|
||||
let src_end = (src_start + self.slot_bytes - self.word_bytes) as *const u8;
|
||||
|
||||
let src_end = (src_start + slot_bytes - self.word_bytes) as *const u8;
|
||||
let next = &desc.d[(i + 1) % N] as *const DmaDescriptor;
|
||||
|
||||
desc.d[i] = DmaDescriptor {
|
||||
xfercfg: encode_xfercfg(
|
||||
true, // valid
|
||||
true, // reload
|
||||
false, // swtrig (we use XFERCFG SWTRIG kick)
|
||||
false, // swtrig
|
||||
false, // clrtrig
|
||||
true, // intA
|
||||
false, // intB
|
||||
@@ -376,11 +360,14 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure memory writes complete before reloading DMA hardware pointers
|
||||
compiler_fence(Ordering::Release);
|
||||
|
||||
chan_desc.d[19] = desc.d[0];
|
||||
chan_desc.d[19].xfercfg = 0;
|
||||
|
||||
// reset producer indices + counters (init-only action)
|
||||
self.reset_producer_init_only();
|
||||
self.reset_producer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user