multi-sample-rate support, evk fixes
This commit is contained in:
Generated
+3
-3
@@ -756,7 +756,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "guac"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"bbqueue",
|
||||
@@ -1795,9 +1795,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "usbd-uac2"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b77e75c12ae8e2be3b2d8a111117a0025ea17ad6de7e8f941ba500adfa8a7a"
|
||||
checksum = "07c0e506a7eadf9dee3039a4fa00dee84b8ed0c4a5931424b81659e0800c4965"
|
||||
dependencies = [
|
||||
"byteorder-embedded-io",
|
||||
"defmt 1.1.1",
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "guac"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
@@ -9,11 +9,11 @@ ak4490 = []
|
||||
cs4398 = []
|
||||
nodac = []
|
||||
wm8904 = []
|
||||
hid = [ "dep:usbd-hid", "dep:shared" ]
|
||||
hid = [ "dep:usbd-hid" ]
|
||||
evk = [ "wm8904" ]
|
||||
|
||||
[dependencies]
|
||||
shared = { path="../shared", optional = true }
|
||||
shared = { path="../shared" }
|
||||
atomic = "0.6.1"
|
||||
bbqueue = "0.7.0"
|
||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||
@@ -32,7 +32,7 @@ panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||
static_cell = "2.1.1"
|
||||
usb-device = { version = "0.3", features = ["control-buffer-256"] }
|
||||
usbd-hid = { version = "0.10.0", optional = true }
|
||||
usbd-uac2 = { version = "0.1.1", features = ["defmt"]}
|
||||
usbd-uac2 = { version = "0.1.2", features = ["defmt"]}
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
|
||||
@@ -130,7 +130,7 @@ where
|
||||
fn cr1_for_rate(&self, rate: u32) -> u16 {
|
||||
let fs_ratio = self.mclk / rate;
|
||||
if !self.mclk.is_multiple_of(rate) {
|
||||
defmt::warn!("sample rate should be a multiple of mclk");
|
||||
defmt::warn!("[wm8904] sample rate should be a multiple of mclk");
|
||||
}
|
||||
let clk_sys_rate: u16 = match fs_ratio {
|
||||
64 => 0,
|
||||
@@ -144,7 +144,7 @@ where
|
||||
1408 => 8,
|
||||
1536 => 9,
|
||||
_ => {
|
||||
defmt::warn!("unsupport ratio {}", fs_ratio);
|
||||
defmt::warn!("[wm8904] unsupport ratio {}", fs_ratio);
|
||||
0
|
||||
}
|
||||
};
|
||||
@@ -181,26 +181,30 @@ where
|
||||
match self.i2c.write_read(WM8904_I2C_ADDRESS, &[0], &mut buf) {
|
||||
Ok(_) => {
|
||||
let chip_id = ((buf[0] as u16) << 8) | buf[1] as u16;
|
||||
defmt::info!("Read chip ID: {:x}", chip_id)
|
||||
defmt::info!("[wm8904] Read chip ID: {:x}", chip_id)
|
||||
}
|
||||
Err(_) => defmt::error!("Error reading I2C"),
|
||||
Err(_) => defmt::error!("[wm8904] Error reading I2C"),
|
||||
}
|
||||
|
||||
self.write_reg(RegisterAddress::ClockRates2, 0x000f); // OPCLK_ENA | CLK_SYS_ENA | CLK_DSP_ENA | TOCLK_ENA
|
||||
self.write_reg(RegisterAddress::WriteSeq0, 0x0100); // write sequencer 0 ENA
|
||||
self.write_reg(RegisterAddress::WriteSeq3, 0x0100); // write sequencer 3 START, INDEX=0
|
||||
// wait on write sequencer
|
||||
defmt::info!("[codec] waiting on write seq");
|
||||
defmt::info!("[wm8904] waiting on write seq");
|
||||
loop {
|
||||
let mut buf = [0; 2];
|
||||
self.i2c
|
||||
.write_read(WM8904_I2C_ADDRESS, &[0x70], &mut buf)
|
||||
.write_read(
|
||||
WM8904_I2C_ADDRESS,
|
||||
&[RegisterAddress::WriteSeq4 as u8],
|
||||
&mut buf,
|
||||
)
|
||||
.ok();
|
||||
if buf[1] & 1 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
defmt::debug!("[codec] write seq done");
|
||||
defmt::debug!("[wm8904] write seq done");
|
||||
self.write_reg(RegisterAddress::ClockRates0, 0);
|
||||
self.write_reg(RegisterAddress::PowerMgmt0, 0); // IN PGAs disabled
|
||||
self.write_reg(RegisterAddress::PowerMgmt2, 0x0003); // HPL_PGA_ENA | HPR_PGA_ENA
|
||||
@@ -236,7 +240,7 @@ where
|
||||
}
|
||||
fn change_rate(&mut self, new_rate: u32) {
|
||||
// TODO: mute, stop clocks etc.
|
||||
defmt::info!("dac rate -> {}", new_rate);
|
||||
defmt::info!("[wm8904] dac rate -> {}", new_rate);
|
||||
self.write_reg(RegisterAddress::ClockRates1, self.cr1_for_rate(new_rate));
|
||||
self.write_reg(
|
||||
RegisterAddress::AudioInterface2,
|
||||
|
||||
+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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+150
-65
@@ -69,24 +69,41 @@ mod dma;
|
||||
mod hw;
|
||||
mod traits;
|
||||
|
||||
#[cfg(not(feature = "evk"))]
|
||||
const MAX_SAMPLE_RATE: u32 = 192000;
|
||||
#[cfg(feature = "evk")]
|
||||
const MAX_SAMPLE_RATE: u32 = 96000;
|
||||
|
||||
#[cfg(not(feature = "evk"))]
|
||||
const SAMPLE_RATES: [RangeEntry<u32>; 6] = [
|
||||
RangeEntry::new_fixed(44100),
|
||||
RangeEntry::new_fixed(48000),
|
||||
RangeEntry::new_fixed(44100 * 2),
|
||||
RangeEntry::new_fixed(48000 * 2),
|
||||
RangeEntry::new_fixed(44100 * 4),
|
||||
RangeEntry::new_fixed(48000 * 4),
|
||||
];
|
||||
|
||||
#[cfg(feature = "evk")]
|
||||
const SAMPLE_RATES: [RangeEntry<u32>; 2] = [
|
||||
RangeEntry::new_fixed(48000),
|
||||
RangeEntry::new_fixed(48000 * 2),
|
||||
];
|
||||
|
||||
const DMA_RATE: usize = 4000;
|
||||
const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples
|
||||
const BYTES_PER_FRAME: usize = BYTES_PER_SAMPLE * 2; // 2 channels
|
||||
const FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 4000; // run the DMA at 4khz
|
||||
const BYTES_PER_SLOT: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME;
|
||||
const MAX_FRAMES_PER_SLOT: usize = MAX_SAMPLE_RATE as usize / 4000; // run the DMA at 4khz
|
||||
const MAX_BYTES_PER_SLOT: usize = MAX_FRAMES_PER_SLOT * BYTES_PER_FRAME;
|
||||
const N_SLOTS: usize = 8;
|
||||
const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2;
|
||||
// const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2;
|
||||
const USB_FRAME_RATE: u32 = 8000; // microframe rate: 8000 for HS, 1000 for FS
|
||||
|
||||
// In frames
|
||||
const QUEUE_RUNNING_UP: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 5) / 10; // 50%
|
||||
const QUEUE_RUNNING_DOWN: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 2) / 10; // 20%
|
||||
const NODATA_TIMEOUT_FRAMES: usize = SAMPLE_RATE as usize / 100; // ~100ms
|
||||
#[cfg(not(feature = "evk"))]
|
||||
const MCLK_FREQ: u32 = 24576000;
|
||||
#[cfg(feature = "evk")]
|
||||
const MCLK_FREQ: u32 = 24576000 / 2;
|
||||
// const QUEUE_RUNNING_UP: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 5) / 10; // 50%
|
||||
// const QUEUE_RUNNING_DOWN: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 2) / 10; // 20%
|
||||
// const NODATA_TIMEOUT_FRAMES: usize = SAMPLE_RATE as usize / 100; // ~100ms
|
||||
|
||||
const SAMPLE_RATE: u32 = 192000;
|
||||
const HID_INTERVAL_MS: u8 = 10;
|
||||
|
||||
struct CodecPins {
|
||||
@@ -119,9 +136,8 @@ impl PerfCounters {
|
||||
self.received_frames.store(0, Ordering::Relaxed);
|
||||
self.played_frames.store(0, Ordering::Relaxed);
|
||||
self.min_fill
|
||||
.store(N_SLOTS * BYTES_PER_SLOT, Ordering::Relaxed);
|
||||
self.avg_fill
|
||||
.store(FILL_TARGET_BYTES as usize, Ordering::Relaxed);
|
||||
.store(N_SLOTS * MAX_BYTES_PER_SLOT, Ordering::Relaxed);
|
||||
self.avg_fill.store(0 as usize, Ordering::Relaxed);
|
||||
self.queue_underflows.store(0, Ordering::Relaxed);
|
||||
self.queue_overflows.store(0, Ordering::Relaxed);
|
||||
self.audio_underflows.store(0, Ordering::Relaxed);
|
||||
@@ -166,7 +182,7 @@ static PERF: PerfCounters = PerfCounters {
|
||||
received_frames: AtomicUsize::new(0), // received from USB
|
||||
played_frames: AtomicUsize::new(0), // played audio frames
|
||||
min_fill: AtomicUsize::new(0), // not recording this for now, need to figure out how to make it meaningful, since the queue starts empty
|
||||
avg_fill: AtomicUsize::new(FILL_TARGET_BYTES as usize),
|
||||
avg_fill: AtomicUsize::new(0),
|
||||
queue_underflows: AtomicUsize::new(0), // ditto here, since we underflow at startup, but we record this one as it can be trended
|
||||
queue_overflows: AtomicUsize::new(0),
|
||||
audio_underflows: AtomicUsize::new(0),
|
||||
@@ -178,10 +194,10 @@ static PERF: PerfCounters = PerfCounters {
|
||||
|
||||
static NODATA_FLAG: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
|
||||
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, BYTES_PER_SLOT>> = None;
|
||||
static DMA_RING: StaticCell<DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>> = StaticCell::new();
|
||||
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>> = None;
|
||||
#[inline]
|
||||
fn dma_ring() -> &'static DmaRing<N_SLOTS, BYTES_PER_SLOT> {
|
||||
fn dma_ring() -> &'static DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT> {
|
||||
unsafe { DMA_RING_REF.unwrap() }
|
||||
}
|
||||
|
||||
@@ -193,6 +209,24 @@ fn cur_fill() -> usize {
|
||||
produced_bytes.wrapping_sub(consumed_bytes) as usize
|
||||
}
|
||||
|
||||
fn cur_fill_target() -> i32 {
|
||||
(dma_ring().slot_size() * N_SLOTS) as i32 / 2
|
||||
}
|
||||
|
||||
fn frames_per_slot() -> usize {
|
||||
dma_ring().slot_size() / BYTES_PER_FRAME
|
||||
}
|
||||
|
||||
// 50%
|
||||
fn queue_running_up_threshold() -> usize {
|
||||
(frames_per_slot() * N_SLOTS) / 2
|
||||
}
|
||||
|
||||
// 20%
|
||||
fn queue_running_down_threshold() -> usize {
|
||||
(frames_per_slot() * N_SLOTS) / 5
|
||||
}
|
||||
|
||||
#[interrupt]
|
||||
fn DMA0() {
|
||||
defmt::debug!("dma0");
|
||||
@@ -224,9 +258,9 @@ fn DMA0() {
|
||||
} else {
|
||||
led1().toggle();
|
||||
PERF.played_frames
|
||||
.fetch_add(FRAMES_PER_SLOT, Ordering::Relaxed);
|
||||
.fetch_add(frames_per_slot(), Ordering::Relaxed);
|
||||
}
|
||||
if cur_fill() <= BYTES_PER_SLOT {
|
||||
if cur_fill() <= dma_ring().slot_size() {
|
||||
led2().on();
|
||||
NODATA_FLAG.store(true, Ordering::Release);
|
||||
}
|
||||
@@ -255,7 +289,7 @@ impl FeedbackState {
|
||||
self.correction_enabled.store(false, Ordering::Relaxed);
|
||||
self.integrator.store(0, Ordering::Relaxed);
|
||||
self.filtered_fill
|
||||
.store(FILL_TARGET_BYTES, Ordering::Relaxed);
|
||||
.store(cur_fill_target(), Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
impl Default for FeedbackState {
|
||||
@@ -263,7 +297,7 @@ impl Default for FeedbackState {
|
||||
Self {
|
||||
correction_enabled: AtomicBool::new(false),
|
||||
integrator: AtomicI32::new(0),
|
||||
filtered_fill: AtomicI32::new(FILL_TARGET_BYTES),
|
||||
filtered_fill: AtomicI32::new(cur_fill_target()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +307,7 @@ struct Audio<'a, D: Dac<I>, I> {
|
||||
alt_setting: u8,
|
||||
i2s: I2sTx,
|
||||
dac: D,
|
||||
dma: &'a DmaRing<N_SLOTS, BYTES_PER_SLOT>,
|
||||
dma: &'a DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>,
|
||||
fb: FeedbackState,
|
||||
nodata_timeout_frame: AtomicUsize,
|
||||
cur_rate: u32,
|
||||
@@ -281,7 +315,7 @@ struct Audio<'a, D: Dac<I>, I> {
|
||||
_marker: core::marker::PhantomData<I>,
|
||||
}
|
||||
impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
|
||||
const RATES: &'static [RangeEntry<u32>] = &SAMPLE_RATES;
|
||||
/// Perform a state transition to `state`
|
||||
fn transition(&mut self, state: AudioState) {
|
||||
defmt::info!(
|
||||
@@ -303,9 +337,8 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
let regs = &self.i2s.i2s;
|
||||
// Enable TX FIFO only
|
||||
regs.fifocfg.modify(|_, w| {
|
||||
self.i2s.i2s.fifocfg.modify(|_, w| {
|
||||
w.enabletx()
|
||||
.enabled()
|
||||
.enablerx()
|
||||
@@ -317,17 +350,17 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
});
|
||||
|
||||
// Flush
|
||||
regs.fifocfg.modify(|_, w| w.emptytx().set_bit());
|
||||
self.i2s.i2s.fifocfg.modify(|_, w| w.emptytx().set_bit());
|
||||
|
||||
regs.cfg2
|
||||
self.i2s
|
||||
.i2s
|
||||
.cfg2
|
||||
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
|
||||
|
||||
let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16;
|
||||
regs.div
|
||||
.modify(|_, w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz
|
||||
self.update_bclk();
|
||||
|
||||
// Config
|
||||
regs.cfg1.modify(|_, w| unsafe {
|
||||
self.i2s.i2s.cfg1.modify(|_, w| unsafe {
|
||||
w.mstslvcfg()
|
||||
.normal_master()
|
||||
.onechannel()
|
||||
@@ -362,8 +395,8 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
// reset performance counters
|
||||
PERF.reset();
|
||||
// Stop the clocks
|
||||
self.clock_pins.sel_22m.set_low().ok();
|
||||
self.clock_pins.sel_24m.set_low().ok();
|
||||
// self.clock_pins.sel_22m.set_low().ok();
|
||||
// self.clock_pins.sel_24m.set_low().ok();
|
||||
}
|
||||
///Transition -> Armed
|
||||
/// Start I2S peripheral and MCLK. Since we assume we have interrupts disabled at
|
||||
@@ -404,17 +437,31 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
///Transition->NoData
|
||||
///store framecount at transition so we can time out recovery
|
||||
fn nodata(&mut self) {
|
||||
self.nodata_timeout_frame.store(
|
||||
PERF.queue_underflows.load(Ordering::Relaxed) + NODATA_TIMEOUT_FRAMES, // we underflow every frame, use it as a timeout counter
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
// TODO: Actually handle this
|
||||
// self.nodata_timeout_frame.store(
|
||||
// PERF.queue_underflows.load(Ordering::Relaxed) + NODATA_TIMEOUT_FRAMES, // we underflow every frame, use it as a timeout counter
|
||||
// Ordering::Relaxed,
|
||||
// );
|
||||
}
|
||||
/// Transition -> Stopping
|
||||
/// just a marker that upcoming nodata is expected, do nothing
|
||||
fn stopping(&mut self) {}
|
||||
|
||||
fn update_bclk(&mut self) {
|
||||
let mclk_freq = if 24_576_000u32.is_multiple_of(self.cur_rate) {
|
||||
24576000
|
||||
} else {
|
||||
22579200
|
||||
};
|
||||
let bclk_div = (mclk_freq / self.cur_rate / 64) as u16;
|
||||
self.i2s
|
||||
.i2s
|
||||
.div
|
||||
.modify(|_, w| unsafe { w.div().bits(bclk_div - 1) });
|
||||
}
|
||||
}
|
||||
impl<D: Dac<I>, I> ClockSource for Audio<'_, D, I> {
|
||||
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
|
||||
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalProgrammable;
|
||||
const SOF_SYNC: bool = false;
|
||||
|
||||
fn sample_rate(&self) -> u32 {
|
||||
@@ -424,25 +471,34 @@ impl<D: Dac<I>, I> ClockSource for Audio<'_, D, I> {
|
||||
&mut self,
|
||||
sample_rate: u32,
|
||||
) -> core::result::Result<(), usbd_uac2::UsbAudioClassError> {
|
||||
defmt::info!("[clock] changing rate to {}", sample_rate);
|
||||
if self.state.load(Ordering::SeqCst) != AudioState::Stopped {
|
||||
defmt::warn!("[clock] changing rate when not stopped, stopping first");
|
||||
self.stop();
|
||||
}
|
||||
let slot_bytes = (self.cur_rate as usize / DMA_RATE) * BYTES_PER_FRAME;
|
||||
dma_ring().set_slot_size(slot_bytes);
|
||||
self.cur_rate = sample_rate;
|
||||
if 24_576_000u32.is_multiple_of(sample_rate) {
|
||||
defmt::info!("[clock] 24M clock selected");
|
||||
defmt::info!("[clock] 24M osc selected");
|
||||
self.clock_pins.sel_22m.set_low().ok();
|
||||
// hal::wait_at_least(1);
|
||||
self.clock_pins.sel_24m.set_high().ok();
|
||||
} else {
|
||||
defmt::info!("[clock] 22M clock selected");
|
||||
defmt::info!("[clock] 22M osc selected");
|
||||
self.clock_pins.sel_24m.set_low().ok();
|
||||
// hal::wait_at_least(1);
|
||||
self.clock_pins.sel_22m.set_high().ok();
|
||||
};
|
||||
self.dac.change_rate(sample_rate);
|
||||
self.cur_rate = sample_rate;
|
||||
self.update_bclk();
|
||||
Ok(())
|
||||
}
|
||||
fn sample_rates(
|
||||
&self,
|
||||
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
|
||||
Ok(&Self::RATES)
|
||||
defmt::debug!("[clock] sample_rates will return {:?}", &Self::RATES.len());
|
||||
Ok(Self::RATES)
|
||||
}
|
||||
fn clock_validity(&self) -> Result<bool, UsbAudioClassError> {
|
||||
Ok(true)
|
||||
@@ -470,7 +526,8 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
|
||||
) {
|
||||
let state = self.state.load(Ordering::Relaxed);
|
||||
let mut buf = [0; (SAMPLE_RATE.div_ceil(USB_FRAME_RATE) + 1) as usize * BYTES_PER_FRAME];
|
||||
let mut buf =
|
||||
[0; (MAX_SAMPLE_RATE.div_ceil(USB_FRAME_RATE) + 1) as usize * BYTES_PER_FRAME];
|
||||
let len = match ep.read(&mut buf) {
|
||||
Ok(len) => len,
|
||||
Err(_) => {
|
||||
@@ -483,7 +540,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
|
||||
if res.dropped != 0 {
|
||||
// Overflow: some or all bytes couldn't be queued.
|
||||
defmt::error!(
|
||||
defmt::warn!(
|
||||
"overflowed dma ring, asked {}, wrote {}, dropped {}",
|
||||
buf.len(),
|
||||
res.written,
|
||||
@@ -505,7 +562,9 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
AudioState::Armed => self.transition(AudioState::Prefill),
|
||||
// When prefilling, if we have received frames over the up threshold, move to running
|
||||
AudioState::Prefill => {
|
||||
if PERF.received_frames.load(Ordering::Relaxed) >= QUEUE_RUNNING_UP {
|
||||
if PERF.received_frames.load(Ordering::Relaxed) >= queue_running_up_threshold()
|
||||
// 50%
|
||||
{
|
||||
self.transition(AudioState::Running);
|
||||
}
|
||||
}
|
||||
@@ -515,7 +574,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
AudioState::LowData => {
|
||||
let fill = cur_fill() as usize;
|
||||
// Do we check alt setting here? We shouldn't be receiving data at all if we are not in altSetting 1
|
||||
if fill >= QUEUE_RUNNING_UP {
|
||||
if fill >= queue_running_up_threshold() {
|
||||
self.transition(AudioState::Running);
|
||||
} else if fill == 0 && self.alt_setting == 0 {
|
||||
self.transition(AudioState::Stopped);
|
||||
@@ -551,7 +610,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
})
|
||||
.ok();
|
||||
|
||||
let raw_error = current_bytes - FILL_TARGET_BYTES;
|
||||
let raw_error = current_bytes - cur_fill_target();
|
||||
let i_error = if raw_error.abs() <= 4 { 0 } else { raw_error }; // deadband
|
||||
let current_i = self.fb.integrator.load(Ordering::Relaxed);
|
||||
let leak = current_i >> 7;
|
||||
@@ -567,7 +626,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
|
||||
// 3. SEPARATE GAINS FOR P AND I
|
||||
// For P: Keep your working math (converting raw error to a permille equivalent scale)
|
||||
let error_permille = (raw_error * 1000) / FILL_TARGET_BYTES;
|
||||
let error_permille = (raw_error * 1000) / cur_fill_target();
|
||||
let p_term = (-((error_permille as i64) * (nominal_v as i64)) / (10 * 256000)) as i32;
|
||||
let i_term = (-((new_i as i64) * (nominal_v as i64)) / (256000 * 1000)) as i32;
|
||||
let i_term = 0;
|
||||
@@ -596,6 +655,14 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
|
||||
syscon.reset(&mut fc7);
|
||||
syscon.enable_clock(&mut fc7);
|
||||
|
||||
unsafe {
|
||||
pac::SYSCON::ptr()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.fcclksel7()
|
||||
.modify(|_, w| w.sel().enum_0x5()); // MCLK
|
||||
}
|
||||
#[cfg(not(feature = "evk"))]
|
||||
unsafe {
|
||||
pac::IOCON::ptr().as_ref().unwrap().pio0_23.modify(|_, w| {
|
||||
w.func()
|
||||
@@ -611,23 +678,29 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
|
||||
.od()
|
||||
.normal()
|
||||
});
|
||||
pac::SYSCON::ptr()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.fcclksel7()
|
||||
.modify(|_, w| w.sel().enum_0x5()); // MCLK
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "evk"))]
|
||||
unsafe {
|
||||
pac::SYSCON::ptr()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.mclkio
|
||||
.modify(|_, w| w.mclkio().input());
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "evk")]
|
||||
unsafe {
|
||||
pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| {
|
||||
w.func()
|
||||
.alt1()
|
||||
.mode()
|
||||
.inactive()
|
||||
.slew()
|
||||
.fast()
|
||||
.invert()
|
||||
.disabled()
|
||||
.digimode()
|
||||
.digital()
|
||||
.od()
|
||||
.normal()
|
||||
});
|
||||
pac::SYSCON::ptr()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
@@ -750,9 +823,13 @@ fn main() -> ! {
|
||||
|
||||
defmt::info!("dma init");
|
||||
let i2s_dma_addr = &i2s_peripheral.i2s.fifowr as *const _ as *mut u32;
|
||||
let dma =
|
||||
DmaRing::<N_SLOTS, BYTES_PER_SLOT>::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4)
|
||||
.unwrap();
|
||||
let dma = DmaRing::<N_SLOTS, MAX_BYTES_PER_SLOT>::new(
|
||||
hal.dma.release(),
|
||||
&mut syscon,
|
||||
i2s_dma_addr,
|
||||
4,
|
||||
)
|
||||
.unwrap();
|
||||
let dma_ref = DMA_RING.init(dma);
|
||||
unsafe { DMA_RING_REF = Some(dma_ref) };
|
||||
|
||||
@@ -765,7 +842,7 @@ fn main() -> ! {
|
||||
fb: FeedbackState::default(),
|
||||
alt_setting: 0,
|
||||
nodata_timeout_frame: AtomicUsize::new(0),
|
||||
cur_rate: SAMPLE_RATE,
|
||||
cur_rate: SAMPLE_RATES[0].min,
|
||||
clock_pins: clock_sel_pins,
|
||||
_marker: core::marker::PhantomData,
|
||||
};
|
||||
@@ -825,8 +902,16 @@ fn main() -> ! {
|
||||
}
|
||||
};
|
||||
#[cfg(not(feature = "hid"))]
|
||||
let poll_all = || {
|
||||
usb_dev.poll(&mut [&mut uac2]);
|
||||
let mut poll_all = {
|
||||
move || {
|
||||
usb_dev.poll(&mut [&mut uac2]);
|
||||
if NODATA_FLAG.swap(false, Ordering::Acquire) {
|
||||
match uac2.handler().state.load(Ordering::Acquire) {
|
||||
AudioState::Stopping => uac2.handler().transition(AudioState::Stopped),
|
||||
_ => uac2.handler().transition(AudioState::Stopped),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
defmt::info!("main loop");
|
||||
|
||||
Reference in New Issue
Block a user