add stopping state, handle dma drain, improve cli readability (state) by moving state to shared
This commit is contained in:
Generated
+37
-36
@@ -217,9 +217,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
@@ -515,14 +515,14 @@ version = "0.3.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
|
||||
dependencies = [
|
||||
"defmt 1.1.0",
|
||||
"defmt 1.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f"
|
||||
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"defmt-macros",
|
||||
@@ -530,12 +530,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b"
|
||||
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -557,7 +556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"defmt 1.1.0",
|
||||
"defmt 1.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -764,7 +763,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"cortex-m",
|
||||
"cortex-m-rt",
|
||||
"defmt 1.1.0",
|
||||
"defmt 1.1.1",
|
||||
"defmt-rtt",
|
||||
"embedded-hal",
|
||||
"embedded-io",
|
||||
@@ -1178,6 +1177,27 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.4"
|
||||
@@ -1245,7 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"defmt 1.1.0",
|
||||
"defmt 1.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1338,28 +1358,6 @@ dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
@@ -1538,7 +1536,10 @@ dependencies = [
|
||||
name = "shared"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"defmt 1.1.1",
|
||||
"deku",
|
||||
"num_enum",
|
||||
"serde",
|
||||
"usbd-hid",
|
||||
]
|
||||
@@ -1794,12 +1795,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "usbd-uac2"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117c289dcd316caa7aca9c0909117d8cf9d35f3ed2e7a5739067f0bcedc93e35"
|
||||
checksum = "88b77e75c12ae8e2be3b2d8a111117a0025ea17ad6de7e8f941ba500adfa8a7a"
|
||||
dependencies = [
|
||||
"byteorder-embedded-io",
|
||||
"defmt 1.1.0",
|
||||
"defmt 1.1.1",
|
||||
"embedded-io",
|
||||
"modular-bitfield",
|
||||
"num-traits",
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ use async_hid::{AsyncHidRead, HidBackend, HidResult};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use deku::DekuContainerRead;
|
||||
use futures_lite::StreamExt;
|
||||
use shared::hid::AudioTelemetryReport;
|
||||
use shared::{AudioTelemetrySnapshot, hid::AudioTelemetryReport};
|
||||
|
||||
#[derive(Clone, Copy, Debug, ValueEnum)]
|
||||
enum Format {
|
||||
@@ -23,7 +23,7 @@ trait StateEmitter<W: io::Write> {
|
||||
fn from_writer(writer: W) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn emit(&mut self, r: &AudioTelemetryReport);
|
||||
fn emit(&mut self, r: &AudioTelemetrySnapshot);
|
||||
}
|
||||
|
||||
struct DebugEmitter<T: io::Write> {
|
||||
@@ -34,7 +34,7 @@ impl<W: io::Write> StateEmitter<W> for DebugEmitter<W> {
|
||||
fn from_writer(writer: W) -> Self {
|
||||
Self { writer }
|
||||
}
|
||||
fn emit(&mut self, r: &AudioTelemetryReport) {
|
||||
fn emit(&mut self, r: &AudioTelemetrySnapshot) {
|
||||
writeln!(self.writer, "{r:?}");
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ impl<W: io::Write> StateEmitter<W> for CsvEmitter<W> {
|
||||
csv: csv::Writer::from_writer(writer),
|
||||
}
|
||||
}
|
||||
fn emit(&mut self, r: &AudioTelemetryReport) {
|
||||
fn emit(&mut self, r: &AudioTelemetrySnapshot) {
|
||||
if let Err(e) = self.csv.serialize(r) {
|
||||
eprintln!("Serialization error: {e:?}");
|
||||
} else {
|
||||
@@ -80,7 +80,7 @@ async fn main() -> HidResult<()> {
|
||||
log::debug!("read {}: {:?}", r, &buf[..r]);
|
||||
let buf = &buf[..r];
|
||||
match AudioTelemetryReport::from_bytes((buf, 0)) {
|
||||
Ok((_, r)) => writer.emit(&r),
|
||||
Ok((_, r)) => writer.emit(&r.into()),
|
||||
Err(e) => eprintln!("Unable to parse report: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -3,9 +3,13 @@ name = "shared"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
std = []
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||
defmt = "1.1.1"
|
||||
deku = { version = "0.20.3", default-features = false }
|
||||
num_enum = { version = "0.7.6", default-features = false }
|
||||
serde = { version = "1.0.228", optional = true, features = ["derive"] }
|
||||
usbd-hid = { version = "0.10.0" }
|
||||
|
||||
+124
-2
@@ -1,14 +1,120 @@
|
||||
#![no_std]
|
||||
|
||||
use bytemuck::NoUninit;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, NoUninit, Eq, PartialEq, TryFromPrimitive)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[repr(u8)]
|
||||
pub enum AudioState {
|
||||
/// Knowingly stopped, ie. AltSetting=0. DAC muted, I2S disabled.
|
||||
///
|
||||
/// AltSetting = 1 -> ARMED
|
||||
Stopped = 0,
|
||||
/// Waiting for data. DAC muted, I2S running sending 0s (FIFO not serviced).
|
||||
///
|
||||
/// USB OUT data packet -> ARMED
|
||||
/// AltSetting = 0 -> STOPPED
|
||||
Armed = 1,
|
||||
/// 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 = 2,
|
||||
/// Normal running state. Start servicing FIFO and begin playing out from the buffer.
|
||||
///
|
||||
/// queue reaches <QUEUE_RUNNING_DOWN> -> DRAINING
|
||||
/// AltSetting = 0 -> DRAINING
|
||||
Running = 3,
|
||||
/// 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 = 4,
|
||||
/// There is no data in the queue. We will count underflows for a while, send 0s, and hope the host comes back.
|
||||
///
|
||||
/// countdown reaches DATA_TIMEOUT -> STOPPED
|
||||
/// AltSetting = 0 -> STOPPED
|
||||
NoData = 5,
|
||||
/// The host has asked us to stop (altSetting 0), but we need to play out the remaining buffer
|
||||
///
|
||||
/// queue is empty -> STOPPED
|
||||
Stopping = 6,
|
||||
}
|
||||
|
||||
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",
|
||||
Self::Stopping => "Stopping",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for AudioState {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Stopped => "Stopped",
|
||||
Self::Armed => "Armed",
|
||||
Self::Prefill => "Prefill",
|
||||
Self::Running => "Running",
|
||||
Self::LowData => "Draining",
|
||||
Self::NoData => "NoData",
|
||||
Self::Stopping => "Stopping",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct AudioTelemetrySnapshot {
|
||||
pub state: AudioState,
|
||||
pub average_buffer_fill: u16,
|
||||
pub frame_count: u32,
|
||||
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 mod hid {
|
||||
use deku::DekuRead;
|
||||
use usbd_hid::descriptor::generator_prelude::*;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use usbd_hid::descriptor::generator_prelude::*;
|
||||
|
||||
use crate::{AudioState, AudioTelemetrySnapshot};
|
||||
|
||||
#[derive(DekuRead)]
|
||||
#[deku(endian = "little")]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01, ) = {
|
||||
state=input;
|
||||
@@ -37,4 +143,20 @@ pub mod hid {
|
||||
pub fb: i32,
|
||||
pub integrator: i32,
|
||||
}
|
||||
|
||||
impl From<AudioTelemetryReport> for AudioTelemetrySnapshot {
|
||||
fn from(value: AudioTelemetryReport) -> Self {
|
||||
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
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user