add stopping state, handle dma drain, improve cli readability (state) by moving state to shared
This commit is contained in:
+30
-77
@@ -8,8 +8,6 @@ fn panic() -> ! {
|
||||
}
|
||||
|
||||
use atomic::Atomic;
|
||||
use bytemuck::NoUninit;
|
||||
use core::error;
|
||||
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
|
||||
use cortex_m_rt::entry;
|
||||
use defmt;
|
||||
@@ -40,7 +38,10 @@ use usbd_uac2::{
|
||||
|
||||
use crate::dac::DacImpl;
|
||||
use crate::dma::DmaRing;
|
||||
use crate::hw::led1;
|
||||
use crate::hw::led2;
|
||||
use crate::traits::Dac;
|
||||
use shared::AudioState;
|
||||
use shared::hid::AudioTelemetryReport;
|
||||
|
||||
#[cfg(feature = "ak4490")]
|
||||
@@ -139,7 +140,7 @@ impl PerfCounters {
|
||||
integrator: self.integrator.load(Ordering::Relaxed),
|
||||
p: self.p.load(Ordering::Relaxed),
|
||||
i: self.i.load(Ordering::Relaxed),
|
||||
fb: self.fb.load(Ordering::Relaxed) as i32,
|
||||
fb: u32::cast_signed(self.fb.load(Ordering::Relaxed) as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,6 +176,8 @@ static PERF: PerfCounters = PerfCounters {
|
||||
fb: AtomicI32::new(0),
|
||||
};
|
||||
|
||||
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;
|
||||
#[inline]
|
||||
@@ -211,18 +214,22 @@ fn DMA0() {
|
||||
err,
|
||||
mem
|
||||
);
|
||||
// red_led().on();
|
||||
dma.errint0.write(|w| unsafe { w.bits(1 << 19) });
|
||||
}
|
||||
|
||||
if (inta & (1 << 19)) != 0 {
|
||||
dma.inta0.write(|w| unsafe { w.bits(1 << 19) });
|
||||
if dma_ring().advance_consumed(1).is_err() {
|
||||
// red_led().on();
|
||||
PERF.audio_underflows.fetch_add(1, Ordering::Relaxed);
|
||||
} else {
|
||||
led1().toggle();
|
||||
PERF.played_frames
|
||||
.fetch_add(FRAMES_PER_SLOT, Ordering::Relaxed);
|
||||
}
|
||||
if cur_fill() <= BYTES_PER_SLOT {
|
||||
led2().on();
|
||||
NODATA_FLAG.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,65 +242,6 @@ fn FLEXCOMM7() {
|
||||
.modify(|_, w| w.txerr().set_bit())
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, NoUninit, Eq, PartialEq)]
|
||||
enum AudioState {
|
||||
/// Knowingly stopped, ie. AltSetting=0. DAC muted, I2S disabled.
|
||||
///
|
||||
/// AltSetting = 1 -> ARMED
|
||||
Stopped,
|
||||
/// Waiting for data. DAC muted, I2S running sending 0s (FIFO not serviced).
|
||||
///
|
||||
/// USB OUT data packet -> ARMED
|
||||
/// AltSetting = 0 -> STOPPED
|
||||
Armed,
|
||||
/// Filling the buffer before playback starts. Feedback does not run,
|
||||
/// playout does not start draining the queue. Gets us better feedback
|
||||
/// behaviour and a full buffer without a feedback rate spike at startup.
|
||||
///
|
||||
/// queue reaches <QUEUE_RUNNING_UP> -> RUNNING
|
||||
/// AltSetting = 0 -> STOPPED
|
||||
///
|
||||
Prefill,
|
||||
/// Normal running state. Start servicing FIFO and begin playing out from the buffer.
|
||||
///
|
||||
/// queue reaches <QUEUE_RUNNING_DOWN> -> DRAINING
|
||||
/// AltSetting = 0 -> DRAINING
|
||||
Running,
|
||||
/// The queue is low. We will continue playout.
|
||||
///
|
||||
/// queue is empty && altSetting 1 -> NODATA
|
||||
/// queue is empty && altSetting 0 -> STOPPED
|
||||
/// queue reaches <QUEUE_RUNNING_UP> && altSetting 1 -> RUNNING
|
||||
LowData,
|
||||
/// There is no data in the queue. We will count underflows for a while, send 0s, and hope the host comes back, but maybe playback is done, which we should notice and shut down.
|
||||
///
|
||||
/// countdown reaches DATA_TIMEOUT -> STOPPED
|
||||
/// AltSetting = 0 -> STOPPED
|
||||
NoData,
|
||||
}
|
||||
impl Default for AudioState {
|
||||
fn default() -> Self {
|
||||
AudioState::Stopped
|
||||
}
|
||||
}
|
||||
impl defmt::Format for AudioState {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Stopped => "Stopped",
|
||||
Self::Armed => "Armed",
|
||||
Self::Prefill => "Prefill",
|
||||
Self::Running => "Running",
|
||||
Self::LowData => "Draining",
|
||||
Self::NoData => "NoData",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedbackState {
|
||||
correction_enabled: AtomicBool,
|
||||
integrator: AtomicI32,
|
||||
@@ -348,6 +296,7 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
AudioState::Running => self.run(),
|
||||
AudioState::LowData => {}
|
||||
AudioState::NoData => self.nodata(),
|
||||
AudioState::Stopping => self.stopping(),
|
||||
}
|
||||
self.state.store(state, Ordering::SeqCst);
|
||||
PERF.state.store(state, Ordering::Relaxed);
|
||||
@@ -460,6 +409,9 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
/// Transition -> Stopping
|
||||
/// just a marker that upcoming nodata is expected, do nothing
|
||||
fn stopping(&mut self) {}
|
||||
}
|
||||
impl<D: Dac<I>, I> ClockSource for Audio<'_, D, I> {
|
||||
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
|
||||
@@ -503,7 +455,8 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
(0, AudioState::Armed | AudioState::Prefill | AudioState::NoData) => {
|
||||
self.transition(AudioState::Stopped)
|
||||
}
|
||||
(0, AudioState::Running | AudioState::Stopped) => {} // noop, we naturally transition through LowData to Stopped
|
||||
(0, AudioState::Running) => self.transition(AudioState::Stopping),
|
||||
(0, AudioState::Stopped | AudioState::Stopping) => {} // already stopped/ing
|
||||
(1, AudioState::Stopped) => self.transition(AudioState::Armed),
|
||||
(1, _) => {} // altSetting 1 in any other state is a no-op
|
||||
(_, _) => {
|
||||
@@ -545,8 +498,8 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
|
||||
// Valid states here are Armed, Prefill, Running, Draining and NoData
|
||||
match state {
|
||||
AudioState::Stopped => {
|
||||
defmt::error!("Received audio data when stopped")
|
||||
AudioState::Stopped | AudioState::Stopping => {
|
||||
defmt::error!("Received audio data when stopped/stopping")
|
||||
}
|
||||
// When armed, data rx goes to prefill
|
||||
AudioState::Armed => self.transition(AudioState::Prefill),
|
||||
@@ -748,16 +701,8 @@ fn main() -> ! {
|
||||
.into_gpio_pin(&mut iocon, &mut gpio)
|
||||
.into_output_low(),
|
||||
};
|
||||
let leds = (
|
||||
pins::Pio0_13::take()
|
||||
.unwrap()
|
||||
.into_gpio_pin(&mut iocon, &mut gpio)
|
||||
.into_output_low(),
|
||||
pins::Pio0_14::take()
|
||||
.unwrap()
|
||||
.into_gpio_pin(&mut iocon, &mut gpio)
|
||||
.into_output_low(),
|
||||
);
|
||||
|
||||
hw::init_leds(&mut iocon, &mut gpio);
|
||||
|
||||
// iocon.disabled(&mut syscon).release(); // save the environment :)
|
||||
|
||||
@@ -859,6 +804,14 @@ fn main() -> ! {
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user