1 Commits
Author SHA1 Message Date
ktims 1e87561b37 add gitea workflow
Build and Release Firmware / build-and-release (push) Successful in 1m35s
2026-08-26 15:20:09 -07:00
4 changed files with 176 additions and 213 deletions
-2
View File
@@ -294,8 +294,6 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
} }
pub fn run(&self) { pub fn run(&self) {
self.dma.inta0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.errint0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.enableset0.write(|w| unsafe { w.bits(1 << 19) }); self.dma.enableset0.write(|w| unsafe { w.bits(1 << 19) });
} }
+157 -202
View File
@@ -8,8 +8,6 @@ fn panic() -> ! {
} }
use atomic::Atomic; use atomic::Atomic;
use core::ptr;
use core::sync::atomic::AtomicPtr;
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering}; use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
use cortex_m_rt::entry; use cortex_m_rt::entry;
use defmt; use defmt;
@@ -119,29 +117,47 @@ struct ClockSelPins {
#[derive(Default)] #[derive(Default)]
struct PerfCounters { struct PerfCounters {
// state: Atomic<AudioState>, state: Atomic<AudioState>,
received_frames: AtomicUsize, received_frames: AtomicUsize,
played_frames: AtomicUsize, played_frames: AtomicUsize,
// min_fill: AtomicUsize, min_fill: AtomicUsize,
// avg_fill: AtomicUsize, avg_fill: AtomicUsize,
queue_underflows: AtomicUsize, queue_underflows: AtomicUsize,
queue_overflows: AtomicUsize, queue_overflows: AtomicUsize,
audio_underflows: AtomicUsize, audio_underflows: AtomicUsize,
// p: AtomicI32, integrator: AtomicI32,
// fb: AtomicI32, p: AtomicI32,
i: AtomicI32,
fb: AtomicI32,
} }
impl PerfCounters { impl PerfCounters {
fn reset(&self) { fn reset(&self) {
self.received_frames.store(0, Ordering::Relaxed); self.received_frames.store(0, Ordering::Relaxed);
self.played_frames.store(0, Ordering::Relaxed); self.played_frames.store(0, Ordering::Relaxed);
// self.min_fill self.min_fill
// .store(N_SLOTS * MAX_BYTES_PER_SLOT, Ordering::Relaxed); .store(N_SLOTS * MAX_BYTES_PER_SLOT, Ordering::Relaxed);
// self.avg_fill.store(0 as usize, Ordering::Relaxed); self.avg_fill.store(0 as usize, Ordering::Relaxed);
self.queue_underflows.store(0, Ordering::Relaxed); self.queue_underflows.store(0, Ordering::Relaxed);
self.queue_overflows.store(0, Ordering::Relaxed); self.queue_overflows.store(0, Ordering::Relaxed);
self.audio_underflows.store(0, Ordering::Relaxed); self.audio_underflows.store(0, Ordering::Relaxed);
// self.p.store(0, Ordering::Relaxed); self.p.store(0, Ordering::Relaxed);
self.i.store(0, Ordering::Relaxed);
// FB loop will have to take care of the fb value
}
fn build_report(&self) -> AudioTelemetryReport {
AudioTelemetryReport {
state: self.state.load(Ordering::Relaxed) as u8,
average_buffer_fill: self.avg_fill.load(Ordering::Relaxed) as u16,
frame_count: self.played_frames.load(Ordering::Relaxed) as i32,
dac_underflow_count: self.audio_underflows.load(Ordering::Relaxed) as u16,
usb_underflow_count: self.queue_underflows.load(Ordering::Relaxed) as u16,
dac_overflow_count: self.queue_overflows.load(Ordering::Relaxed) as u16,
integrator: self.integrator.load(Ordering::Relaxed),
p: self.p.load(Ordering::Relaxed),
i: self.i.load(Ordering::Relaxed),
fb: u32::cast_signed(self.fb.load(Ordering::Relaxed) as u32),
}
} }
} }
@@ -149,11 +165,11 @@ impl defmt::Format for PerfCounters {
fn format(&self, fmt: defmt::Formatter) { fn format(&self, fmt: defmt::Formatter) {
defmt::write!( defmt::write!(
fmt, fmt,
"frames: {}/{} a_underflows: {} q_underflows: {} q_overflows: {}", "frames: {}/{} min_fill: {} avg fill: {} a_underflows: {} q_underflows: {} q_overflows: {}",
self.played_frames.load(Ordering::Relaxed), self.played_frames.load(Ordering::Relaxed),
self.received_frames.load(Ordering::Relaxed), self.received_frames.load(Ordering::Relaxed),
// self.min_fill.load(Ordering::Relaxed), self.min_fill.load(Ordering::Relaxed),
// self.avg_fill.load(Ordering::Relaxed), self.avg_fill.load(Ordering::Relaxed),
self.audio_underflows.load(Ordering::Relaxed), self.audio_underflows.load(Ordering::Relaxed),
self.queue_underflows.load(Ordering::Relaxed), self.queue_underflows.load(Ordering::Relaxed),
self.queue_overflows.load(Ordering::Relaxed) self.queue_overflows.load(Ordering::Relaxed)
@@ -162,141 +178,27 @@ impl defmt::Format for PerfCounters {
} }
static PERF: PerfCounters = PerfCounters { static PERF: PerfCounters = PerfCounters {
// state: Atomic::new(AudioState::Stopped), state: Atomic::new(AudioState::Stopped),
received_frames: AtomicUsize::new(0), // received from USB received_frames: AtomicUsize::new(0), // received from USB
played_frames: AtomicUsize::new(0), // played audio frames 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 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(0), 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_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), queue_overflows: AtomicUsize::new(0),
audio_underflows: AtomicUsize::new(0), audio_underflows: AtomicUsize::new(0),
// p: AtomicI32::new(0), integrator: AtomicI32::new(0),
// fb: AtomicI32::new(0), p: AtomicI32::new(0),
i: AtomicI32::new(0),
fb: AtomicI32::new(0),
}; };
fn build_telemetry_report<D: Dac<I>, I>(
perf: &PerfCounters,
audio: &Audio<D, I>,
) -> AudioTelemetryReport {
AudioTelemetryReport {
state: audio.state.load(Ordering::Relaxed) as u8,
average_buffer_fill: audio.fb.avg_fill as u16,
frame_count: perf.played_frames.load(Ordering::Relaxed).cast_signed() as i32,
dac_underflow_count: perf.audio_underflows.load(Ordering::Relaxed) as u16,
usb_underflow_count: perf.queue_underflows.load(Ordering::Relaxed) as u16,
dac_overflow_count: perf.queue_overflows.load(Ordering::Relaxed) as u16,
cur_rate: audio.cur_rate.cast_signed(),
fb_rate_estimate: audio.fb.current_freq_estimate() as i32,
}
}
#[derive(Clone, Copy, Debug)]
pub struct FeedbackConfig {
/// Nominal USB rate in Q12.13 fixed-point format (or target sample rate context).
pub nominal_rate: u32,
/// Target ring buffer fill level in bytes.
pub target_fill_bytes: i32,
/// Deadband threshold in bytes (e.g., 4).
pub deadband_bytes: i32,
/// Maximum allowed feedback deviation permille denominator (e.g., 500 => 0.2%).
pub max_deviation_divider: i32,
/// Master toggle for feedback correction.
pub correction_enabled: bool,
}
impl FeedbackConfig {
pub fn new(nominal_rate: u32, target_fill_bytes: usize) -> Self {
Self {
nominal_rate: nominal_rate,
target_fill_bytes: target_fill_bytes as i32,
deadband_bytes: 4,
max_deviation_divider: 500, // 0.2%
correction_enabled: true,
}
}
pub fn for_rate(nominal_rate: u32) -> Self {
let target_fill_bytes = (bytes_per_slot(nominal_rate) * N_SLOTS) / 2;
Self::new(nominal_rate, target_fill_bytes)
}
}
pub struct FeedbackLoop {
pub config: FeedbackConfig,
pub avg_fill: usize,
last_freq: u32,
}
impl FeedbackLoop {
pub fn new(config: FeedbackConfig) -> Self {
Self {
config,
avg_fill: config.target_fill_bytes as usize,
last_freq: config.nominal_rate,
}
}
pub fn reset(&mut self) {
self.avg_fill = self.config.target_fill_bytes as usize;
self.last_freq = self.config.nominal_rate
}
pub fn update_config(&mut self, config: FeedbackConfig) {
self.config = config;
self.reset();
}
pub fn compute(
&mut self,
nominal_rate: UsbIsochronousFeedback,
current_fill_bytes: usize,
) -> UsbIsochronousFeedback {
if !self.config.correction_enabled {
return nominal_rate;
}
let current_bytes = current_fill_bytes as i32;
if current_bytes == 0 {
defmt::error!("[fb] dma underrun detected!");
PERF.queue_underflows.fetch_add(1, Ordering::Relaxed);
return nominal_rate;
}
self.avg_fill = ((self.avg_fill << 6) - self.avg_fill + current_bytes as usize) >> 6;
let target_fill = self.config.target_fill_bytes;
let raw_error = current_bytes - target_fill;
let nominal_v = nominal_rate.to_u32_12_13() as i32;
let max_allowed_deviation = nominal_v / self.config.max_deviation_divider;
let error_permille = (raw_error * 1000) / target_fill;
let p_term = (-((error_permille as i64) * (nominal_v as i64)) / (10 * 256000)) as i32;
let mut v = nominal_v + p_term;
v = v.clamp(
nominal_v - max_allowed_deviation,
nominal_v + max_allowed_deviation,
);
self.last_freq = v as u32;
UsbIsochronousFeedback::new(v as u32)
}
pub fn current_freq_estimate(&self) -> f32 {
// Divides by 8 (frame-to-microframe) and 65536.0 (Q16.16 shift)
(self.last_freq as f32 / (65536.0)) * USB_FRAME_RATE as f32
}
}
static NODATA_FLAG: AtomicBool = AtomicBool::new(false); static NODATA_FLAG: AtomicBool = AtomicBool::new(false);
static DMA_RING: StaticCell<DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>> = StaticCell::new(); static DMA_RING: StaticCell<DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>> = StaticCell::new();
static DMA_RING_PTR: AtomicPtr<DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>> = static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>> = None;
AtomicPtr::new(ptr::null_mut());
#[inline] #[inline]
fn dma_ring() -> &'static DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT> { fn dma_ring() -> &'static DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT> {
let ptr = DMA_RING_PTR.load(Ordering::Acquire); unsafe { DMA_RING_REF.unwrap() }
unsafe { &*ptr }
} }
fn cur_fill() -> usize { fn cur_fill() -> usize {
@@ -307,21 +209,14 @@ fn cur_fill() -> usize {
produced_bytes.wrapping_sub(consumed_bytes) as usize produced_bytes.wrapping_sub(consumed_bytes) as usize
} }
/// current fill target (based on current slot size)
fn cur_fill_target() -> i32 { fn cur_fill_target() -> i32 {
(dma_ring().slot_size() * N_SLOTS) as i32 / 2 (dma_ring().slot_size() * N_SLOTS) as i32 / 2
} }
/// frames per slot (based on current slot size)
fn frames_per_slot() -> usize { fn frames_per_slot() -> usize {
dma_ring().slot_size() / BYTES_PER_FRAME dma_ring().slot_size() / BYTES_PER_FRAME
} }
/// bytes per slot (based on provided rate)
fn bytes_per_slot(rate: u32) -> usize {
(rate as usize / DMA_RATE) * BYTES_PER_FRAME
}
// 50% // 50%
fn queue_running_up_threshold() -> usize { fn queue_running_up_threshold() -> usize {
(frames_per_slot() * N_SLOTS) / 2 (frames_per_slot() * N_SLOTS) / 2
@@ -381,13 +276,39 @@ fn FLEXCOMM7() {
.modify(|_, w| w.txerr().set_bit()) .modify(|_, w| w.txerr().set_bit())
} }
struct FeedbackState {
correction_enabled: AtomicBool,
integrator: AtomicI32,
filtered_fill: AtomicI32,
}
impl FeedbackState {
fn start(&mut self) {
self.correction_enabled.store(true, Ordering::Relaxed);
}
fn reset(&mut self) {
self.correction_enabled.store(false, Ordering::Relaxed);
self.integrator.store(0, Ordering::Relaxed);
self.filtered_fill
.store(cur_fill_target(), Ordering::Relaxed);
}
}
impl Default for FeedbackState {
fn default() -> Self {
Self {
correction_enabled: AtomicBool::new(false),
integrator: AtomicI32::new(0),
filtered_fill: AtomicI32::new(cur_fill_target()),
}
}
}
struct Audio<'a, D: Dac<I>, I> { struct Audio<'a, D: Dac<I>, I> {
state: Atomic<AudioState>, state: Atomic<AudioState>,
alt_setting: u8, alt_setting: u8,
i2s: I2sTx, i2s: I2sTx,
dac: D, dac: D,
dma: &'a DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>, dma: &'a DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>,
fb: FeedbackLoop, fb: FeedbackState,
nodata_timeout_frame: AtomicUsize, nodata_timeout_frame: AtomicUsize,
cur_rate: u32, cur_rate: u32,
clock_pins: ClockSelPins, clock_pins: ClockSelPins,
@@ -411,8 +332,8 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
AudioState::NoData => self.nodata(), AudioState::NoData => self.nodata(),
AudioState::Stopping => self.stopping(), AudioState::Stopping => self.stopping(),
} }
self.state.store(state, Ordering::Release); self.state.store(state, Ordering::SeqCst);
// PERF.state.store(state, Ordering::Relaxed); PERF.state.store(state, Ordering::Relaxed);
} }
fn init(&mut self) { fn init(&mut self) {
@@ -455,7 +376,6 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
}); });
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) }; unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
self.dac.init(); self.dac.init();
} }
///Transition -> Stopped: ///Transition -> Stopped:
@@ -495,7 +415,7 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
///Transition -> Running ///Transition -> Running
///Unmask I2S ISR, start feedback ///Unmask I2S ISR, start feedback
fn run(&mut self) { fn run(&mut self) {
self.i2s.i2s.fifostat.write(|w| w.txerr().set_bit()); self.fb.start();
// FIFO threshold trigger enable // FIFO threshold trigger enable
self.i2s self.i2s
.i2s .i2s
@@ -505,14 +425,14 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
.i2s .i2s
.fifocfg .fifocfg
.modify(|_, w| w.enabletx().enabled().dmatx().enabled()); .modify(|_, w| w.enabletx().enabled().dmatx().enabled());
dma_ring().run();
// clear tx error status
self.i2s.i2s.fifostat.write(|w| w.txerr().set_bit());
// enable tx error interrupt
self.i2s.i2s.fifointenset.write(|w| w.txerr().enabled());
unsafe { unsafe {
pac::NVIC::unmask(pac::Interrupt::DMA0); pac::NVIC::unmask(pac::Interrupt::DMA0);
} }
dma_ring().run();
// clear tx error status
// self.i2s.i2s.fifostat.write(|w| w.txerr().set_bit());
// enable tx error interrupt
self.i2s.i2s.fifointenset.write(|w| w.txerr().enabled());
} }
///Transition->NoData ///Transition->NoData
///store framecount at transition so we can time out recovery ///store framecount at transition so we can time out recovery
@@ -552,15 +472,13 @@ impl<D: Dac<I>, I> ClockSource for Audio<'_, D, I> {
sample_rate: u32, sample_rate: u32,
) -> core::result::Result<(), usbd_uac2::UsbAudioClassError> { ) -> core::result::Result<(), usbd_uac2::UsbAudioClassError> {
defmt::info!("[clock] changing rate to {}", sample_rate); defmt::info!("[clock] changing rate to {}", sample_rate);
if self.state.load(Ordering::Acquire) != AudioState::Stopped { if self.state.load(Ordering::SeqCst) != AudioState::Stopped {
defmt::warn!("[clock] changing rate when not stopped, stopping first"); defmt::warn!("[clock] changing rate when not stopped, stopping first");
self.stop(); self.stop();
} }
self.cur_rate = sample_rate; let slot_bytes = (self.cur_rate as usize / DMA_RATE) * BYTES_PER_FRAME;
let slot_bytes = bytes_per_slot(self.cur_rate);
dma_ring().set_slot_size(slot_bytes); dma_ring().set_slot_size(slot_bytes);
self.fb self.cur_rate = sample_rate;
.update_config(FeedbackConfig::for_rate(self.cur_rate));
if 24_576_000u32.is_multiple_of(sample_rate) { if 24_576_000u32.is_multiple_of(sample_rate) {
defmt::info!("[clock] 24M osc selected"); defmt::info!("[clock] 24M osc selected");
self.clock_pins.sel_22m.set_low().ok(); self.clock_pins.sel_22m.set_low().ok();
@@ -588,7 +506,7 @@ impl<D: Dac<I>, I> ClockSource for Audio<'_, D, I> {
} }
impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> { impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) { fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) {
let state = self.state.load(Ordering::Acquire); let state = self.state.load(Ordering::Relaxed);
match (alt_setting, state) { match (alt_setting, state) {
(0, AudioState::Armed | AudioState::Prefill | AudioState::NoData) => { (0, AudioState::Armed | AudioState::Prefill | AudioState::NoData) => {
self.transition(AudioState::Stopped) self.transition(AudioState::Stopped)
@@ -607,7 +525,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
&mut self, &mut self,
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>, ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
) { ) {
let state = self.state.load(Ordering::Acquire); let state = self.state.load(Ordering::Relaxed);
let mut buf = let mut buf =
[0; (MAX_SAMPLE_RATE.div_ceil(USB_FRAME_RATE) + 1) as usize * BYTES_PER_FRAME]; [0; (MAX_SAMPLE_RATE.div_ceil(USB_FRAME_RATE) + 1) as usize * BYTES_PER_FRAME];
let len = match ep.read(&mut buf) { let len = match ep.read(&mut buf) {
@@ -638,8 +556,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
// Valid states here are Armed, Prefill, Running, Draining and NoData // Valid states here are Armed, Prefill, Running, Draining and NoData
match state { match state {
AudioState::Stopped | AudioState::Stopping => { AudioState::Stopped | AudioState::Stopping => {
defmt::warn!("Received audio data when stopped/stopping"); defmt::error!("Received audio data when stopped/stopping")
self.transition(AudioState::Prefill);
} }
// When armed, data rx goes to prefill // When armed, data rx goes to prefill
AudioState::Armed => self.transition(AudioState::Prefill), AudioState::Armed => self.transition(AudioState::Prefill),
@@ -666,7 +583,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
} }
} }
// Any data in NoData moves us into LowData. But maybe it should be more like prefill? // Any data in NoData moves us into LowData. But maybe it should be more like prefill?
AudioState::NoData => self.transition(AudioState::Prefill), AudioState::NoData => self.transition(AudioState::LowData),
} }
} }
fn audio_data_tx( fn audio_data_tx(
@@ -675,7 +592,11 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
) { ) {
} }
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> { fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
let current_bytes = cur_fill(); if !self.fb.correction_enabled.load(Ordering::Relaxed) {
return Some(nominal_rate);
}
let current_bytes = cur_fill() as i32;
if current_bytes == 0 { if current_bytes == 0 {
defmt::error!("[fb] dma underrun detected!"); defmt::error!("[fb] dma underrun detected!");
@@ -683,11 +604,44 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
return Some(nominal_rate); return Some(nominal_rate);
} }
let v = self.fb.compute(nominal_rate, current_bytes); PERF.avg_fill
// PERF.avg_fill.store(self.fb.avg_fill, Ordering::Relaxed); .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| {
// PERF.fb.store(v as i32, Ordering::Relaxed); Some(((v << 6) - v + current_bytes as usize) >> 6)
})
.ok();
Some(v) 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;
let new_i = current_i
.saturating_sub(leak)
.saturating_add(i_error)
.clamp(-5000, 5000);
self.fb.integrator.store(new_i, Ordering::Relaxed);
PERF.integrator.store(new_i, Ordering::Relaxed);
let nominal_v = nominal_rate.to_u32_12_13() as i32;
let max_allowed_deviation = nominal_v / 500; // 0.2%
// 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) / 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;
PERF.p.store(p_term, Ordering::Relaxed);
PERF.i.store(i_term, Ordering::Relaxed);
let mut v = nominal_v + p_term + i_term;
v = v.clamp(
nominal_v - max_allowed_deviation,
nominal_v + max_allowed_deviation,
);
PERF.fb.store(v, Ordering::Relaxed);
Some(UsbIsochronousFeedback::new(v as u32))
} }
} }
@@ -877,17 +831,15 @@ fn main() -> ! {
) )
.unwrap(); .unwrap();
let dma_ref = DMA_RING.init(dma); let dma_ref = DMA_RING.init(dma);
DMA_RING_PTR.store(dma_ref as *const _ as *mut _, Ordering::Release); unsafe { DMA_RING_REF = Some(dma_ref) };
// unsafe { DMA_RING_PTR = Some(dma_ref) };
defmt::info!("audio init"); defmt::info!("audio init");
let mut audio = Audio { let mut audio = Audio {
state: Atomic::new(AudioState::Stopped), state: Atomic::new(AudioState::Stopped),
i2s: i2s_peripheral, i2s: i2s_peripheral,
dac: dac_impl, dac: dac_impl,
dma: dma_ring(), dma: dma_ring(),
fb: FeedbackLoop::new(FeedbackConfig::for_rate(SAMPLE_RATES[0].min)), fb: FeedbackState::default(),
alt_setting: 0, alt_setting: 0,
nodata_timeout_frame: AtomicUsize::new(0), nodata_timeout_frame: AtomicUsize::new(0),
cur_rate: SAMPLE_RATES[0].min, cur_rate: SAMPLE_RATES[0].min,
@@ -918,45 +870,47 @@ fn main() -> ! {
.unwrap() .unwrap()
.build(); .build();
// 1. Initialize the HID timer conditionally outside the closure
#[cfg(feature = "hid")] #[cfg(feature = "hid")]
let mut hid_update_timer = { let mut poll_all = {
let mut timer = Timer::new( let mut hid_update_timer = Timer::new(
hal.ctimer hal.ctimer
.1 .1
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()), .enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
); );
timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000)); hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
timer
};
// 2. Single consolidated poll_all closure move || {
let mut poll_all = move || { usb_dev.poll(&mut [&mut uac2, &mut hid]);
// Poll active USB classes // DMA ring is empty; if altsetting = 0 then -> stopped else NoData
#[cfg(feature = "hid")] // TODO: handle NoData state
usb_dev.poll(&mut [&mut uac2, &mut hid]); if NODATA_FLAG.swap(false, Ordering::Acquire) {
match uac2.handler().state.load(Ordering::Acquire) {
#[cfg(not(feature = "hid"))] AudioState::Stopping => uac2.handler().transition(AudioState::Stopped),
usb_dev.poll(&mut [&mut uac2]); _ => uac2.handler().transition(AudioState::Stopped),
}
// TODO: we should handle unexpected NODATA differently from 'Stopping'
if NODATA_FLAG.swap(false, Ordering::AcqRel) {
match uac2.handler().state.load(Ordering::Acquire) {
AudioState::Stopping => uac2.handler().transition(AudioState::Stopped),
_ => uac2.handler().transition(AudioState::Stopped),
} }
if hid_update_timer.wait().is_ok() {
let report = PERF.build_report();
match hid.push_input(&report) {
Ok(_) => {}
Err(UsbError::WouldBlock) => {}
Err(e) => defmt::error!("Failed to send HID report: {:?}", e),
}
// lpc55 ctimer is not Periodic, so restart it
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
}
} }
};
#[cfg(feature = "hid")] #[cfg(not(feature = "hid"))]
if hid_update_timer.wait().is_ok() { let mut poll_all = {
let report = build_telemetry_report(&PERF, uac2.handler()); move || {
if let Err(e) = hid.push_input(&report) { usb_dev.poll(&mut [&mut uac2]);
if e != UsbError::WouldBlock { if NODATA_FLAG.swap(false, Ordering::Acquire) {
defmt::error!("Failed to send HID report: {:?}", e); match uac2.handler().state.load(Ordering::Acquire) {
AudioState::Stopping => uac2.handler().transition(AudioState::Stopped),
_ => uac2.handler().transition(AudioState::Stopped),
} }
} }
// LPC55 CTIMER is not periodic; restart manually
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
} }
}; };
@@ -964,5 +918,6 @@ fn main() -> ! {
loop { loop {
poll_all(); poll_all();
// usb_dev.poll(&mut [&mut uac2]);
} }
} }
+4
View File
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.95.0"
targets = ["thumbv8m.main-none-eabihf"]
components = ["llvm-tools-preview"]
+15 -9
View File
@@ -99,8 +99,9 @@ pub struct AudioTelemetrySnapshot {
pub dac_underflow_count: u16, pub dac_underflow_count: u16,
pub usb_underflow_count: u16, pub usb_underflow_count: u16,
pub dac_overflow_count: u16, pub dac_overflow_count: u16,
pub cur_rate: u32, pub p: i32,
pub fb_rate_estimate: f32, pub i: i32,
pub fb: i32,
} }
pub mod hid { pub mod hid {
@@ -122,8 +123,10 @@ pub mod hid {
dac_underflow_count=input; dac_underflow_count=input;
usb_underflow_count=input; usb_underflow_count=input;
dac_overflow_count=input; dac_overflow_count=input;
cur_rate=input; p=input;
fb_rate_estimate=input; i=input;
fb=input;
integrator=input;
} }
)] )]
#[repr(C, packed)] #[repr(C, packed)]
@@ -135,8 +138,10 @@ pub mod hid {
pub dac_underflow_count: u16, pub dac_underflow_count: u16,
pub usb_underflow_count: u16, pub usb_underflow_count: u16,
pub dac_overflow_count: u16, pub dac_overflow_count: u16,
pub cur_rate: i32, pub p: i32,
pub fb_rate_estimate: i32, pub i: i32,
pub fb: i32,
pub integrator: i32,
} }
impl From<AudioTelemetryReport> for AudioTelemetrySnapshot { impl From<AudioTelemetryReport> for AudioTelemetrySnapshot {
@@ -144,12 +149,13 @@ pub mod hid {
AudioTelemetrySnapshot { AudioTelemetrySnapshot {
state: AudioState::try_from(value.state).expect("Invalid AudioState"), state: AudioState::try_from(value.state).expect("Invalid AudioState"),
average_buffer_fill: value.average_buffer_fill, average_buffer_fill: value.average_buffer_fill,
frame_count: value.frame_count.cast_unsigned(), // on firmware side is usize == u32 frame_count: i32::cast_unsigned(value.frame_count), // on firmware side is usize == u32
dac_underflow_count: value.dac_underflow_count, dac_underflow_count: value.dac_underflow_count,
usb_underflow_count: value.usb_underflow_count, usb_underflow_count: value.usb_underflow_count,
dac_overflow_count: value.dac_overflow_count, dac_overflow_count: value.dac_overflow_count,
cur_rate: value.cur_rate.cast_unsigned(), p: value.p,
fb_rate_estimate: (value.fb_rate_estimate as f32), i: value.i,
fb: value.fb,
} }
} }
} }