refactor feedback, update telemetry report
This commit is contained in:
@@ -294,6 +294,8 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
|
||||
}
|
||||
|
||||
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) });
|
||||
}
|
||||
|
||||
|
||||
+200
-156
@@ -8,6 +8,8 @@ fn panic() -> ! {
|
||||
}
|
||||
|
||||
use atomic::Atomic;
|
||||
use core::ptr;
|
||||
use core::sync::atomic::AtomicPtr;
|
||||
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
|
||||
use cortex_m_rt::entry;
|
||||
use defmt;
|
||||
@@ -117,47 +119,29 @@ struct ClockSelPins {
|
||||
|
||||
#[derive(Default)]
|
||||
struct PerfCounters {
|
||||
state: Atomic<AudioState>,
|
||||
// state: Atomic<AudioState>,
|
||||
received_frames: AtomicUsize,
|
||||
played_frames: AtomicUsize,
|
||||
min_fill: AtomicUsize,
|
||||
avg_fill: AtomicUsize,
|
||||
// min_fill: AtomicUsize,
|
||||
// avg_fill: AtomicUsize,
|
||||
queue_underflows: AtomicUsize,
|
||||
queue_overflows: AtomicUsize,
|
||||
audio_underflows: AtomicUsize,
|
||||
integrator: AtomicI32,
|
||||
p: AtomicI32,
|
||||
i: AtomicI32,
|
||||
fb: AtomicI32,
|
||||
// p: AtomicI32,
|
||||
// fb: AtomicI32,
|
||||
}
|
||||
|
||||
impl PerfCounters {
|
||||
fn reset(&self) {
|
||||
self.received_frames.store(0, Ordering::Relaxed);
|
||||
self.played_frames.store(0, Ordering::Relaxed);
|
||||
self.min_fill
|
||||
.store(N_SLOTS * MAX_BYTES_PER_SLOT, Ordering::Relaxed);
|
||||
self.avg_fill.store(0 as usize, Ordering::Relaxed);
|
||||
// self.min_fill
|
||||
// .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);
|
||||
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),
|
||||
}
|
||||
// self.p.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,11 +149,11 @@ impl defmt::Format for PerfCounters {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"frames: {}/{} min_fill: {} avg fill: {} a_underflows: {} q_underflows: {} q_overflows: {}",
|
||||
"frames: {}/{} a_underflows: {} q_underflows: {} q_overflows: {}",
|
||||
self.played_frames.load(Ordering::Relaxed),
|
||||
self.received_frames.load(Ordering::Relaxed),
|
||||
self.min_fill.load(Ordering::Relaxed),
|
||||
self.avg_fill.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),
|
||||
self.queue_overflows.load(Ordering::Relaxed)
|
||||
@@ -178,27 +162,141 @@ impl defmt::Format for PerfCounters {
|
||||
}
|
||||
|
||||
static PERF: PerfCounters = PerfCounters {
|
||||
state: Atomic::new(AudioState::Stopped),
|
||||
// state: Atomic::new(AudioState::Stopped),
|
||||
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(0),
|
||||
// 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),
|
||||
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),
|
||||
integrator: AtomicI32::new(0),
|
||||
p: AtomicI32::new(0),
|
||||
i: AtomicI32::new(0),
|
||||
fb: AtomicI32::new(0),
|
||||
// p: 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 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;
|
||||
static DMA_RING_PTR: AtomicPtr<DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>> =
|
||||
AtomicPtr::new(ptr::null_mut());
|
||||
#[inline]
|
||||
fn dma_ring() -> &'static DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT> {
|
||||
unsafe { DMA_RING_REF.unwrap() }
|
||||
let ptr = DMA_RING_PTR.load(Ordering::Acquire);
|
||||
unsafe { &*ptr }
|
||||
}
|
||||
|
||||
fn cur_fill() -> usize {
|
||||
@@ -209,14 +307,21 @@ fn cur_fill() -> usize {
|
||||
produced_bytes.wrapping_sub(consumed_bytes) as usize
|
||||
}
|
||||
|
||||
/// current fill target (based on current slot size)
|
||||
fn cur_fill_target() -> i32 {
|
||||
(dma_ring().slot_size() * N_SLOTS) as i32 / 2
|
||||
}
|
||||
|
||||
/// frames per slot (based on current slot size)
|
||||
fn frames_per_slot() -> usize {
|
||||
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%
|
||||
fn queue_running_up_threshold() -> usize {
|
||||
(frames_per_slot() * N_SLOTS) / 2
|
||||
@@ -276,39 +381,13 @@ fn FLEXCOMM7() {
|
||||
.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> {
|
||||
state: Atomic<AudioState>,
|
||||
alt_setting: u8,
|
||||
i2s: I2sTx,
|
||||
dac: D,
|
||||
dma: &'a DmaRing<N_SLOTS, MAX_BYTES_PER_SLOT>,
|
||||
fb: FeedbackState,
|
||||
fb: FeedbackLoop,
|
||||
nodata_timeout_frame: AtomicUsize,
|
||||
cur_rate: u32,
|
||||
clock_pins: ClockSelPins,
|
||||
@@ -332,8 +411,8 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
AudioState::NoData => self.nodata(),
|
||||
AudioState::Stopping => self.stopping(),
|
||||
}
|
||||
self.state.store(state, Ordering::SeqCst);
|
||||
PERF.state.store(state, Ordering::Relaxed);
|
||||
self.state.store(state, Ordering::Release);
|
||||
// PERF.state.store(state, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
@@ -376,6 +455,7 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
});
|
||||
|
||||
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
|
||||
|
||||
self.dac.init();
|
||||
}
|
||||
///Transition -> Stopped:
|
||||
@@ -415,7 +495,7 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
///Transition -> Running
|
||||
///Unmask I2S ISR, start feedback
|
||||
fn run(&mut self) {
|
||||
self.fb.start();
|
||||
self.i2s.i2s.fifostat.write(|w| w.txerr().set_bit());
|
||||
// FIFO threshold trigger enable
|
||||
self.i2s
|
||||
.i2s
|
||||
@@ -425,14 +505,14 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
.i2s
|
||||
.fifocfg
|
||||
.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 {
|
||||
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
|
||||
///store framecount at transition so we can time out recovery
|
||||
@@ -472,13 +552,15 @@ impl<D: Dac<I>, I> ClockSource for Audio<'_, D, I> {
|
||||
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 {
|
||||
if self.state.load(Ordering::Acquire) != 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;
|
||||
let slot_bytes = bytes_per_slot(self.cur_rate);
|
||||
dma_ring().set_slot_size(slot_bytes);
|
||||
self.fb
|
||||
.update_config(FeedbackConfig::for_rate(self.cur_rate));
|
||||
if 24_576_000u32.is_multiple_of(sample_rate) {
|
||||
defmt::info!("[clock] 24M osc selected");
|
||||
self.clock_pins.sel_22m.set_low().ok();
|
||||
@@ -506,7 +588,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> {
|
||||
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) {
|
||||
let state = self.state.load(Ordering::Relaxed);
|
||||
let state = self.state.load(Ordering::Acquire);
|
||||
match (alt_setting, state) {
|
||||
(0, AudioState::Armed | AudioState::Prefill | AudioState::NoData) => {
|
||||
self.transition(AudioState::Stopped)
|
||||
@@ -525,7 +607,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
&mut self,
|
||||
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
|
||||
) {
|
||||
let state = self.state.load(Ordering::Relaxed);
|
||||
let state = self.state.load(Ordering::Acquire);
|
||||
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) {
|
||||
@@ -584,7 +666,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?
|
||||
AudioState::NoData => self.transition(AudioState::LowData),
|
||||
AudioState::NoData => self.transition(AudioState::Prefill),
|
||||
}
|
||||
}
|
||||
fn audio_data_tx(
|
||||
@@ -593,11 +675,7 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
) {
|
||||
}
|
||||
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
|
||||
if !self.fb.correction_enabled.load(Ordering::Relaxed) {
|
||||
return Some(nominal_rate);
|
||||
}
|
||||
|
||||
let current_bytes = cur_fill() as i32;
|
||||
let current_bytes = cur_fill();
|
||||
|
||||
if current_bytes == 0 {
|
||||
defmt::error!("[fb] dma underrun detected!");
|
||||
@@ -605,44 +683,11 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
return Some(nominal_rate);
|
||||
}
|
||||
|
||||
PERF.avg_fill
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| {
|
||||
Some(((v << 6) - v + current_bytes as usize) >> 6)
|
||||
})
|
||||
.ok();
|
||||
let v = self.fb.compute(nominal_rate, current_bytes);
|
||||
// PERF.avg_fill.store(self.fb.avg_fill, Ordering::Relaxed);
|
||||
// PERF.fb.store(v as i32, Ordering::Relaxed);
|
||||
|
||||
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))
|
||||
Some(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,15 +877,17 @@ fn main() -> ! {
|
||||
)
|
||||
.unwrap();
|
||||
let dma_ref = DMA_RING.init(dma);
|
||||
unsafe { DMA_RING_REF = Some(dma_ref) };
|
||||
DMA_RING_PTR.store(dma_ref as *const _ as *mut _, Ordering::Release);
|
||||
// unsafe { DMA_RING_PTR = Some(dma_ref) };
|
||||
|
||||
defmt::info!("audio init");
|
||||
|
||||
let mut audio = Audio {
|
||||
state: Atomic::new(AudioState::Stopped),
|
||||
i2s: i2s_peripheral,
|
||||
dac: dac_impl,
|
||||
dma: dma_ring(),
|
||||
fb: FeedbackState::default(),
|
||||
fb: FeedbackLoop::new(FeedbackConfig::for_rate(SAMPLE_RATES[0].min)),
|
||||
alt_setting: 0,
|
||||
nodata_timeout_frame: AtomicUsize::new(0),
|
||||
cur_rate: SAMPLE_RATES[0].min,
|
||||
@@ -871,47 +918,45 @@ fn main() -> ! {
|
||||
.unwrap()
|
||||
.build();
|
||||
|
||||
// 1. Initialize the HID timer conditionally outside the closure
|
||||
#[cfg(feature = "hid")]
|
||||
let mut poll_all = {
|
||||
let mut hid_update_timer = Timer::new(
|
||||
let mut hid_update_timer = {
|
||||
let mut timer = Timer::new(
|
||||
hal.ctimer
|
||||
.1
|
||||
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
|
||||
);
|
||||
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
||||
timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
||||
timer
|
||||
};
|
||||
|
||||
move || {
|
||||
usb_dev.poll(&mut [&mut uac2, &mut hid]);
|
||||
// DMA ring is empty; if altsetting = 0 then -> stopped else NoData
|
||||
// TODO: handle NoData state
|
||||
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),
|
||||
}
|
||||
}
|
||||
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));
|
||||
// 2. Single consolidated poll_all closure
|
||||
let mut poll_all = move || {
|
||||
// Poll active USB classes
|
||||
#[cfg(feature = "hid")]
|
||||
usb_dev.poll(&mut [&mut uac2, &mut hid]);
|
||||
|
||||
#[cfg(not(feature = "hid"))]
|
||||
usb_dev.poll(&mut [&mut uac2]);
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
};
|
||||
#[cfg(not(feature = "hid"))]
|
||||
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),
|
||||
|
||||
#[cfg(feature = "hid")]
|
||||
if hid_update_timer.wait().is_ok() {
|
||||
let report = build_telemetry_report(&PERF, uac2.handler());
|
||||
if let Err(e) = hid.push_input(&report) {
|
||||
if e != UsbError::WouldBlock {
|
||||
defmt::error!("Failed to send HID report: {:?}", e);
|
||||
}
|
||||
}
|
||||
// LPC55 CTIMER is not periodic; restart manually
|
||||
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -919,6 +964,5 @@ fn main() -> ! {
|
||||
|
||||
loop {
|
||||
poll_all();
|
||||
// usb_dev.poll(&mut [&mut uac2]);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-15
@@ -99,9 +99,8 @@ pub struct AudioTelemetrySnapshot {
|
||||
pub dac_underflow_count: u16,
|
||||
pub usb_underflow_count: u16,
|
||||
pub dac_overflow_count: u16,
|
||||
pub p: i32,
|
||||
pub i: i32,
|
||||
pub fb: i32,
|
||||
pub cur_rate: u32,
|
||||
pub fb_rate_estimate: f32,
|
||||
}
|
||||
|
||||
pub mod hid {
|
||||
@@ -123,10 +122,8 @@ pub mod hid {
|
||||
dac_underflow_count=input;
|
||||
usb_underflow_count=input;
|
||||
dac_overflow_count=input;
|
||||
p=input;
|
||||
i=input;
|
||||
fb=input;
|
||||
integrator=input;
|
||||
cur_rate=input;
|
||||
fb_rate_estimate=input;
|
||||
}
|
||||
)]
|
||||
#[repr(C, packed)]
|
||||
@@ -138,10 +135,8 @@ pub mod hid {
|
||||
pub dac_underflow_count: u16,
|
||||
pub usb_underflow_count: u16,
|
||||
pub dac_overflow_count: u16,
|
||||
pub p: i32,
|
||||
pub i: i32,
|
||||
pub fb: i32,
|
||||
pub integrator: i32,
|
||||
pub cur_rate: i32,
|
||||
pub fb_rate_estimate: i32,
|
||||
}
|
||||
|
||||
impl From<AudioTelemetryReport> for AudioTelemetrySnapshot {
|
||||
@@ -149,13 +144,12 @@ pub mod hid {
|
||||
AudioTelemetrySnapshot {
|
||||
state: AudioState::try_from(value.state).expect("Invalid AudioState"),
|
||||
average_buffer_fill: value.average_buffer_fill,
|
||||
frame_count: i32::cast_unsigned(value.frame_count), // on firmware side is usize == u32
|
||||
frame_count: value.frame_count.cast_unsigned(), // on firmware side is usize == u32
|
||||
dac_underflow_count: value.dac_underflow_count,
|
||||
usb_underflow_count: value.usb_underflow_count,
|
||||
dac_overflow_count: value.dac_overflow_count,
|
||||
p: value.p,
|
||||
i: value.i,
|
||||
fb: value.fb,
|
||||
cur_rate: value.cur_rate.cast_unsigned(),
|
||||
fb_rate_estimate: (value.fb_rate_estimate as f32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user