some refactoring, improved perf counting

This commit is contained in:
2026-05-06 18:54:25 -07:00
parent e57a029b93
commit 648b4344a8
2 changed files with 100 additions and 149 deletions
+56
View File
@@ -0,0 +1,56 @@
pub(crate) struct PllConstants {
pub m: u16, // 1-65535
pub n: u8, // 1-255
pub p: u8, // 1-31
pub selp: u8, // 5 bits
pub seli: u8, // 6 bits
}
impl PllConstants {
pub(crate) const fn new(n: u8, m: u16, p: u8) -> Self {
assert!(n != 0, "1 <= N <= 255");
assert!(m != 0, "1 <= M <= 65535");
assert!(p != 0 && p <= 31, "1 <= P <= 31");
// Following ripped from lpc55-hal and made const
// UM 4.6.6.3.2
let selp = {
let v = (m >> 2) + 1;
if v < 31 { v } else { 31 }
} as u8;
let seli = {
let v = match m {
m if m >= 8000 => 1,
m if m >= 122 => 8000 / m,
_ => 2 * (m >> 2) + 3,
};
if v < 63 { v } else { 63 }
} as u8;
// let seli = min(2*(m >> 2) + 3, 63);
Self {
n,
m,
p,
selp,
seli,
}
}
}
impl defmt::Format for PllConstants {
fn format(&self, fmt: defmt::Formatter) {
let factor = f32::from(self.m) / (f32::from(self.n) * 2.0 * f32::from(self.p));
defmt::write!(
fmt,
"m: {} n: {} p: {} selp: {} seli: {} fout: fin * {}",
self.m,
self.n,
self.p,
self.selp,
self.seli,
factor
);
}
}
+44 -149
View File
@@ -35,113 +35,21 @@ use usbd_uac2::{
descriptors::{ChannelConfig, ClockType, FormatType1, LockDelay},
};
mod hw;
mod wm8904;
struct PllConstants {
m: u16, // 1-65535
n: u8, // 1-255
p: u8, // 1-31
selp: u8, // 5 bits
seli: u8, // 6 bits
}
impl PllConstants {
const fn new(n: u8, m: u16, p: u8) -> Self {
assert!(n != 0, "1 <= N <= 255");
assert!(m != 0, "1 <= M <= 65535");
assert!(p != 0 && p <= 31, "1 <= P <= 31");
// Following ripped from lpc55-hal and made const
// UM 4.6.6.3.2
let selp = {
let v = (m >> 2) + 1;
if v < 31 { v } else { 31 }
} as u8;
let seli = {
let v = match m {
m if m >= 8000 => 1,
m if m >= 122 => 8000 / m,
_ => 2 * (m >> 2) + 3,
};
if v < 63 { v } else { 63 }
} as u8;
// let seli = min(2*(m >> 2) + 3, 63);
Self {
n,
m,
p,
selp,
seli,
}
}
}
impl defmt::Format for PllConstants {
fn format(&self, fmt: defmt::Formatter) {
let factor = f32::from(self.m) / (f32::from(self.n) * 2.0 * f32::from(self.p));
defmt::write!(
fmt,
"m: {} n: {} p: {} selp: {} seli: {} fout: fin * {}",
self.m,
self.n,
self.p,
self.selp,
self.seli,
factor
);
}
}
const CODEC_I2C_ADDR: u8 = 0b0011010;
// Fo = M/(N*2*P) * Fin
// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz
const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8);
const AUDIO_PLL: hw::PllConstants = hw::PllConstants::new(125, 3072, 8);
const FIFO_LENGTH: usize = 2048; // frames
const MCLK_FREQ: u32 = 24576000;
const MCLK_FREQ: u32 = 12288000;
const SAMPLE_RATE: u32 = 48000;
type SampleType = (i32, i32);
// const SINE_LUT: [i32; 32] = [
// 0, 1636536, 3210180, 4660460, 5931640, 6974871, 7750062, 8227422, 8388607, 8227422, 7750062,
// 6974871, 5931640, 4660460, 3210180, 1636536, 0, -1636536, -3210180, -4660460, -5931640,
// -6974871, -7750062, -8227422, -8388607, -8227422, -7750062, -6974871, -5931640, -4660460,
// -3210180, -1636536,
// ];
// pub fn i2s_sine_test(i2s: &pac::I2S7) -> ! {
// let mut idx = 0;
// let mut count = 0usize;
// defmt::debug!("starting sine test");
// loop {
// if i2s.fifostat.read().txnotfull().bit_is_set() {
// let sample = SINE_LUT[idx] * 32;
// // ✅ Left channel
// i2s.fifowr.write(|w| unsafe { w.bits(sample as u32) });
// // wait for space if needed
// while !i2s.fifostat.read().txnotfull().bit_is_set() {}
// // ✅ Right channel
// i2s.fifowr.write(|w| unsafe { w.bits(sample as u32) });
// idx = (idx + 1) & (SINE_LUT.len() - 1);
// count += 1;
// if count.is_multiple_of(48000) {
// defmt::debug!("frames sent: {}", count)
// }
// }
// }
// }
struct Clock {}
impl Clock {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(48000)];
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
}
impl UsbAudioClockImpl for Clock {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
@@ -162,18 +70,31 @@ impl UsbAudioClockImpl for Clock {
#[derive(Default)]
struct PerfCounters {
frames: AtomicUsize,
avg_fill: AtomicI32, // i32 to simplify averaging
min_fill: AtomicUsize,
avg_fill: AtomicUsize,
queue_underflows: AtomicUsize,
queue_overflows: AtomicUsize,
audio_underflows: AtomicUsize,
}
impl PerfCounters {
fn reset(&self) {
self.frames.store(0, Ordering::Relaxed);
self.min_fill.store(FIFO_LENGTH, Ordering::Relaxed);
self.avg_fill.store(FIFO_LENGTH / 2, Ordering::Relaxed);
self.queue_underflows.store(0, Ordering::Relaxed);
self.queue_overflows.store(0, Ordering::Relaxed);
self.audio_underflows.store(0, Ordering::Relaxed);
}
}
impl defmt::Format for PerfCounters {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"frames: {} avg fill: {} a_underflows: {} q_underflows: {} q_overflows: {}",
"frames: {} min_fill: {} avg fill: {} a_underflows: {} q_underflows: {} q_overflows: {}",
self.frames.load(Ordering::Relaxed),
self.min_fill.load(Ordering::Relaxed),
self.avg_fill.load(Ordering::Relaxed),
self.audio_underflows.load(Ordering::Relaxed),
self.queue_underflows.load(Ordering::Relaxed),
@@ -185,10 +106,23 @@ impl defmt::Format for PerfCounters {
static FIFO_CONSUMER_STORE: StaticCell<Consumer<SampleType>> = StaticCell::new();
static mut FIFO_CONSUMER: *mut Consumer<SampleType> = null_mut();
static PERF: PerfCounters = PerfCounters {
frames: AtomicUsize::new(0),
min_fill: AtomicUsize::new(FIFO_LENGTH),
avg_fill: AtomicUsize::new(FIFO_LENGTH / 2),
queue_underflows: AtomicUsize::new(0),
queue_overflows: AtomicUsize::new(0),
audio_underflows: AtomicUsize::new(0),
};
#[interrupt]
fn FLEXCOMM7() {
let i2s = unsafe { &*pac::I2S7::ptr() };
if i2s.fifostat.read().txlvl().bits() == 0 {
PERF.audio_underflows.fetch_add(1, Ordering::Relaxed);
}
// refil the buffer to 4 frames / 8 samples
while i2s.fifostat.read().txlvl().bits() <= 6 {
let fifo = unsafe { &mut *FIFO_CONSUMER };
@@ -196,6 +130,8 @@ fn FLEXCOMM7() {
i2s.fifowr.write(|w| unsafe { w.bits(l as u32) });
i2s.fifowr.write(|w| unsafe { w.bits(r as u32) });
} else {
PERF.queue_underflows.fetch_add(1, Ordering::Relaxed);
PERF.min_fill.fetch_add(0, Ordering::Relaxed);
i2s.fifowr.write(|w| unsafe { w.bits(0) });
i2s.fifowr.write(|w| unsafe { w.bits(0) });
}
@@ -206,45 +142,9 @@ struct Audio<'a> {
running: AtomicBool,
i2s: I2sTx,
producer: UnsafeCell<heapless::spsc::Producer<'a, SampleType>>,
perf: PerfCounters,
integrator: AtomicI32,
}
impl<'a> Audio<'a> {
// fn poll(&self) {
// if !self.running.load(Ordering::Relaxed) {
// return;
// }
// if self.i2s.i2s.fifostat.read().txerr().bit_is_set() {
// self.i2s.i2s.fifostat.modify(|_, w| w.txerr().set_bit());
// // defmt::error!("fifo tx error, txlvl: {}", stat.txlvl().bits());
// }
// if self.i2s.i2s.fifostat.read().txlvl().bits() == 0 {
// self.perf.audio_underflows.fetch_add(1, Ordering::Relaxed);
// }
// while self.i2s.i2s.fifostat.read().txlvl().bits() <= 6 {
// // fifo is 8 deep at 32 bit samples
// let fifo = self.consumer.get();
// if let Some(sample) = unsafe { (*fifo).dequeue() } {
// self.i2s
// .i2s
// .fifowr
// .write(|w| unsafe { w.bits(sample.0 as u32) });
// self.i2s
// .i2s
// .fifowr
// .write(|w| unsafe { w.bits(sample.1 as u32) });
// } else {
// if self.running.load(Ordering::Relaxed) {
// self.perf.queue_underflows.fetch_add(1, Ordering::Relaxed);
// }
// break;
// // self.i2s.i2s.fifowr.write(|w| unsafe { w.bits(0 as u32) });
// // self.i2s.i2s.fifowr.write(|w| unsafe { w.bits(0 as u32) });
// }
// self.perf.frames.fetch_add(1, Ordering::Relaxed);
// }
// }
fn start(&self) {
self.running.store(true, Ordering::Relaxed);
defmt::info!("playback starting, enabling interrupts");
@@ -260,7 +160,8 @@ impl<'a> Audio<'a> {
}
fn stop(&self) {
self.running.store(true, Ordering::Relaxed);
defmt::info!("playback stopped: {}", self.perf);
defmt::info!("playback stopped: {}", PERF);
PERF.reset();
pac::NVIC::mask(pac::Interrupt::FLEXCOMM7);
}
}
@@ -294,7 +195,7 @@ impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio<'_> {
)
}) {
if let Err(e) = unsafe { (*self.producer.get()).enqueue(sample) } {
self.perf.queue_overflows.fetch_add(1, Ordering::Relaxed);
PERF.queue_overflows.fetch_add(1, Ordering::Relaxed);
// defmt::error!("overflowed fifo, len: {}", unsafe {
// (*self.producer.get()).len()
// });
@@ -305,8 +206,8 @@ impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio<'_> {
const TARGET: i32 = FIFO_LENGTH as i32 / 2 - 64;
const NOMINAL: i32 = 48 << 16;
let queuelen = unsafe { (*self.producer.get()).len() as i32 };
let error = (queuelen - TARGET).clamp(-32, 32);
let queuelen = unsafe { (*self.producer.get()).len() };
let error = (queuelen as i32 - TARGET).clamp(-32, 32);
// --- integrator ---
let scaled_error = error / 64;
@@ -332,9 +233,9 @@ impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio<'_> {
let v = NOMINAL + (correction << 10);
// EMA (unchanged, already correct)
let ema = self.perf.avg_fill.load(Ordering::Relaxed);
let ema = PERF.avg_fill.load(Ordering::Relaxed);
let new = ((ema * 1023) + queuelen + 512) >> 10;
self.perf.avg_fill.store(new, Ordering::Relaxed);
PERF.avg_fill.store(new, Ordering::Relaxed);
defmt::debug!(
"q:{} p:{} i:{} err:{} fb:{}+{}",
@@ -457,7 +358,7 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
.as_ref()
.unwrap()
.mclkdiv
.modify(|_, w| w.div().bits(0).halt().run().reset().released()); // div by 1 = PLL0 fout
.modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz
pac::SYSCON::ptr()
.as_ref()
.unwrap()
@@ -488,7 +389,7 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
regs.cfg2
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) });
regs.div.modify(|_, w| unsafe { w.div().bits(7) }); // Clock source is MCLK (24MHz on FRO96) / 8 = 3MHz
regs.div.modify(|_, w| unsafe { w.div().bits(3) }); // Clock source is MCLK (24MHz on FRO96) / 4 = 3MHz
// Config
regs.cfg1.modify(|_, w| unsafe {
@@ -545,10 +446,7 @@ fn main() -> ! {
// iocon.disabled(&mut syscon).release(); // save the environment :)
debug!("clocks");
// TODO: figure out how to configure the PLL for a more suitable audio clock.
let clocks = hal::ClockRequirements::default()
// .system_frequency(24.mhz())
// .system_frequency(72.mhz())
.system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon)
.unwrap();
@@ -557,8 +455,6 @@ fn main() -> ! {
.0
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
);
debug!("pll0");
init_audio_pll();
debug!("peripherals");
@@ -598,7 +494,7 @@ fn main() -> ! {
.unwrap();
let (producer, consumer) = queue.split();
let consumer_ref = unsafe { FIFO_CONSUMER_STORE.init(consumer) };
let consumer_ref = FIFO_CONSUMER_STORE.init(consumer);
unsafe { FIFO_CONSUMER = consumer_ref as *mut _ };
// i2s_sine_test(&i2s_peripheral.i2s);
@@ -606,7 +502,6 @@ fn main() -> ! {
i2s: i2s_peripheral,
producer: UnsafeCell::new(producer),
running: AtomicBool::new(false),
perf: PerfCounters::default(),
integrator: AtomicI32::new(0),
};