multi-sample-rate support, evk fixes

This commit is contained in:
2026-08-26 14:28:14 -07:00
parent bdd946ad11
commit 34df570dda
5 changed files with 209 additions and 133 deletions
+40 -53
View File
@@ -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();
}
}