diff --git a/Cargo.lock b/Cargo.lock index a76ac1e..8d3ee58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 6fb2601..c7254c6 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -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" diff --git a/firmware/src/dac/wm8904.rs b/firmware/src/dac/wm8904.rs index 1c11a6b..c1cf7de 100644 --- a/firmware/src/dac/wm8904.rs +++ b/firmware/src/dac/wm8904.rs @@ -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, diff --git a/firmware/src/dma.rs b/firmware/src/dma.rs index e62f21d..191ef5a 100644 --- a/firmware/src/dma.rs +++ b/firmware/src/dma.rs @@ -70,29 +70,22 @@ impl core::fmt::Display for DmaError { /// Slot-based DMA ring pub struct DmaRing { 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, - // SAFETY: only written by USB task (on start) pub(crate) desc: UnsafeCell>, 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, write_off: UnsafeCell, 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 DmaRing { }; 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 DmaRing { 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 DmaRing { 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 DmaRing { 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 DmaRing { 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 DmaRing { 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 DmaRing { 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 DmaRing { 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 DmaRing { 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 DmaRing { 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(); } } diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 8e3d22b..f4b3e76 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -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; 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; 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> = StaticCell::new(); -static mut DMA_RING_REF: Option<&'static DmaRing> = None; +static DMA_RING: StaticCell> = StaticCell::new(); +static mut DMA_RING_REF: Option<&'static DmaRing> = None; #[inline] -fn dma_ring() -> &'static DmaRing { +fn dma_ring() -> &'static DmaRing { 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> { alt_setting: u8, i2s: I2sTx, dac: D, - dma: &'a DmaRing, + dma: &'a DmaRing, fb: FeedbackState, nodata_timeout_frame: AtomicUsize, cur_rate: u32, @@ -281,7 +315,7 @@ struct Audio<'a, D: Dac, I> { _marker: core::marker::PhantomData, } impl, I> Audio<'_, D, I> { - const RATES: [RangeEntry; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)]; + const RATES: &'static [RangeEntry] = &SAMPLE_RATES; /// Perform a state transition to `state` fn transition(&mut self, state: AudioState) { defmt::info!( @@ -303,9 +337,8 @@ impl, 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, 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, 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, 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, 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, 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], usbd_uac2::UsbAudioClassError> { - Ok(&Self::RATES) + defmt::debug!("[clock] sample_rates will return {:?}", &Self::RATES.len()); + Ok(Self::RATES) } fn clock_validity(&self) -> Result { Ok(true) @@ -470,7 +526,8 @@ impl, 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, 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, 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, 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, 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, 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::::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4) - .unwrap(); + let dma = DmaRing::::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");