add stopping state, handle dma drain, improve cli readability (state) by moving state to shared

This commit is contained in:
2026-08-25 12:51:52 -07:00
parent 31edf91933
commit bdd946ad11
7 changed files with 271 additions and 121 deletions
+1 -1
View File
@@ -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.0", features = ["defmt"]}
usbd-uac2 = { version = "0.1.1", features = ["defmt"]}
[profile.release]
opt-level = "z"
+70
View File
@@ -1,5 +1,16 @@
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use crate::hal;
use crate::pac;
use defmt::{debug, info};
use hal::{
Enabled, Iocon, Pin,
drivers::pins,
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio},
};
pub(crate) struct PllConstants {
pub m: u16, // 1-65535
@@ -184,3 +195,62 @@ pub(crate) fn init_audio_pll() {
}
info!("pll0 locked after {} loops", i);
}
pub struct SharedLed<T: OutputPin> {
inner: UnsafeCell<T>,
}
unsafe impl<T: OutputPin> Sync for SharedLed<T> {}
impl<T: OutputPin> SharedLed<T> {
pub fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
}
}
pub fn on(&self) {
unsafe {
(*self.inner.get()).set_low().ok();
}
}
pub fn off(&self) {
unsafe {
(*self.inner.get()).set_high().ok();
}
}
}
impl<T: OutputPin + ToggleableOutputPin> SharedLed<T> {
pub fn toggle(&self) {
unsafe {
(*self.inner.get()).toggle().ok();
}
}
}
type Led1 = Pin<pins::Pio0_13, Gpio<Output>>;
type Led2 = Pin<pins::Pio0_14, Gpio<Output>>;
pub static LED1: MaybeUninit<SharedLed<Led1>> = MaybeUninit::uninit();
pub static LED2: MaybeUninit<SharedLed<Led2>> = MaybeUninit::uninit();
pub fn init_leds(iocon: &mut Iocon<Enabled>, gpio: &mut hal::Gpio<Enabled>) {
let led1 = SharedLed::new(
pins::Pio0_13::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let led2 = SharedLed::new(
pins::Pio0_14::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
unsafe {
core::ptr::write(LED1.as_ptr() as *mut SharedLed<Led1>, led1);
core::ptr::write(LED2.as_ptr() as *mut SharedLed<Led2>, led2);
}
}
pub fn led1() -> &'static SharedLed<Led1> {
unsafe { &*LED1.as_ptr() }
}
pub fn led2() -> &'static SharedLed<Led2> {
unsafe { &*LED2.as_ptr() }
}
+30 -77
View File
@@ -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) {