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]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.25.0"
|
version = "1.25.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck_derive",
|
"bytemuck_derive",
|
||||||
]
|
]
|
||||||
@@ -515,14 +515,14 @@ version = "0.3.100"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
|
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"defmt 1.1.0",
|
"defmt 1.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "defmt"
|
name = "defmt"
|
||||||
version = "1.1.0"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f"
|
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"defmt-macros",
|
"defmt-macros",
|
||||||
@@ -530,12 +530,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "defmt-macros"
|
name = "defmt-macros"
|
||||||
version = "1.1.0"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b"
|
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"defmt-parser",
|
"defmt-parser",
|
||||||
"proc-macro-error2",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -557,7 +556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e"
|
checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"critical-section",
|
"critical-section",
|
||||||
"defmt 1.1.0",
|
"defmt 1.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -764,7 +763,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
"defmt 1.1.0",
|
"defmt 1.1.1",
|
||||||
"defmt-rtt",
|
"defmt-rtt",
|
||||||
"embedded-hal",
|
"embedded-hal",
|
||||||
"embedded-io",
|
"embedded-io",
|
||||||
@@ -1178,6 +1177,27 @@ dependencies = [
|
|||||||
"autocfg",
|
"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]]
|
[[package]]
|
||||||
name = "objc2"
|
name = "objc2"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
@@ -1245,7 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
|
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"defmt 1.1.0",
|
"defmt 1.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1338,28 +1358,6 @@ dependencies = [
|
|||||||
"toml_edit",
|
"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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -1538,7 +1536,10 @@ dependencies = [
|
|||||||
name = "shared"
|
name = "shared"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"defmt 1.1.1",
|
||||||
"deku",
|
"deku",
|
||||||
|
"num_enum",
|
||||||
"serde",
|
"serde",
|
||||||
"usbd-hid",
|
"usbd-hid",
|
||||||
]
|
]
|
||||||
@@ -1794,12 +1795,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "usbd-uac2"
|
name = "usbd-uac2"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "117c289dcd316caa7aca9c0909117d8cf9d35f3ed2e7a5739067f0bcedc93e35"
|
checksum = "88b77e75c12ae8e2be3b2d8a111117a0025ea17ad6de7e8f941ba500adfa8a7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder-embedded-io",
|
"byteorder-embedded-io",
|
||||||
"defmt 1.1.0",
|
"defmt 1.1.1",
|
||||||
"embedded-io",
|
"embedded-io",
|
||||||
"modular-bitfield",
|
"modular-bitfield",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
|||||||
+5
-5
@@ -4,7 +4,7 @@ use async_hid::{AsyncHidRead, HidBackend, HidResult};
|
|||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
use deku::DekuContainerRead;
|
use deku::DekuContainerRead;
|
||||||
use futures_lite::StreamExt;
|
use futures_lite::StreamExt;
|
||||||
use shared::hid::AudioTelemetryReport;
|
use shared::{AudioTelemetrySnapshot, hid::AudioTelemetryReport};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, ValueEnum)]
|
#[derive(Clone, Copy, Debug, ValueEnum)]
|
||||||
enum Format {
|
enum Format {
|
||||||
@@ -23,7 +23,7 @@ trait StateEmitter<W: io::Write> {
|
|||||||
fn from_writer(writer: W) -> Self
|
fn from_writer(writer: W) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
fn emit(&mut self, r: &AudioTelemetryReport);
|
fn emit(&mut self, r: &AudioTelemetrySnapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DebugEmitter<T: io::Write> {
|
struct DebugEmitter<T: io::Write> {
|
||||||
@@ -34,7 +34,7 @@ impl<W: io::Write> StateEmitter<W> for DebugEmitter<W> {
|
|||||||
fn from_writer(writer: W) -> Self {
|
fn from_writer(writer: W) -> Self {
|
||||||
Self { writer }
|
Self { writer }
|
||||||
}
|
}
|
||||||
fn emit(&mut self, r: &AudioTelemetryReport) {
|
fn emit(&mut self, r: &AudioTelemetrySnapshot) {
|
||||||
writeln!(self.writer, "{r:?}");
|
writeln!(self.writer, "{r:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ impl<W: io::Write> StateEmitter<W> for CsvEmitter<W> {
|
|||||||
csv: csv::Writer::from_writer(writer),
|
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) {
|
if let Err(e) = self.csv.serialize(r) {
|
||||||
eprintln!("Serialization error: {e:?}");
|
eprintln!("Serialization error: {e:?}");
|
||||||
} else {
|
} else {
|
||||||
@@ -80,7 +80,7 @@ async fn main() -> HidResult<()> {
|
|||||||
log::debug!("read {}: {:?}", r, &buf[..r]);
|
log::debug!("read {}: {:?}", r, &buf[..r]);
|
||||||
let buf = &buf[..r];
|
let buf = &buf[..r];
|
||||||
match AudioTelemetryReport::from_bytes((buf, 0)) {
|
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),
|
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"
|
static_cell = "2.1.1"
|
||||||
usb-device = { version = "0.3", features = ["control-buffer-256"] }
|
usb-device = { version = "0.3", features = ["control-buffer-256"] }
|
||||||
usbd-hid = { version = "0.10.0", optional = true }
|
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]
|
[profile.release]
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
|
use core::cell::UnsafeCell;
|
||||||
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
|
use crate::hal;
|
||||||
use crate::pac;
|
use crate::pac;
|
||||||
|
|
||||||
use defmt::{debug, info};
|
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(crate) struct PllConstants {
|
||||||
pub m: u16, // 1-65535
|
pub m: u16, // 1-65535
|
||||||
@@ -184,3 +195,62 @@ pub(crate) fn init_audio_pll() {
|
|||||||
}
|
}
|
||||||
info!("pll0 locked after {} loops", i);
|
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 atomic::Atomic;
|
||||||
use bytemuck::NoUninit;
|
|
||||||
use core::error;
|
|
||||||
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
|
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering};
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use defmt;
|
use defmt;
|
||||||
@@ -40,7 +38,10 @@ use usbd_uac2::{
|
|||||||
|
|
||||||
use crate::dac::DacImpl;
|
use crate::dac::DacImpl;
|
||||||
use crate::dma::DmaRing;
|
use crate::dma::DmaRing;
|
||||||
|
use crate::hw::led1;
|
||||||
|
use crate::hw::led2;
|
||||||
use crate::traits::Dac;
|
use crate::traits::Dac;
|
||||||
|
use shared::AudioState;
|
||||||
use shared::hid::AudioTelemetryReport;
|
use shared::hid::AudioTelemetryReport;
|
||||||
|
|
||||||
#[cfg(feature = "ak4490")]
|
#[cfg(feature = "ak4490")]
|
||||||
@@ -139,7 +140,7 @@ impl PerfCounters {
|
|||||||
integrator: self.integrator.load(Ordering::Relaxed),
|
integrator: self.integrator.load(Ordering::Relaxed),
|
||||||
p: self.p.load(Ordering::Relaxed),
|
p: self.p.load(Ordering::Relaxed),
|
||||||
i: self.i.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),
|
fb: AtomicI32::new(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static NODATA_FLAG: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
|
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;
|
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, BYTES_PER_SLOT>> = None;
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -211,18 +214,22 @@ fn DMA0() {
|
|||||||
err,
|
err,
|
||||||
mem
|
mem
|
||||||
);
|
);
|
||||||
// red_led().on();
|
|
||||||
dma.errint0.write(|w| unsafe { w.bits(1 << 19) });
|
dma.errint0.write(|w| unsafe { w.bits(1 << 19) });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inta & (1 << 19)) != 0 {
|
if (inta & (1 << 19)) != 0 {
|
||||||
dma.inta0.write(|w| unsafe { w.bits(1 << 19) });
|
dma.inta0.write(|w| unsafe { w.bits(1 << 19) });
|
||||||
if dma_ring().advance_consumed(1).is_err() {
|
if dma_ring().advance_consumed(1).is_err() {
|
||||||
// red_led().on();
|
PERF.audio_underflows.fetch_add(1, Ordering::Relaxed);
|
||||||
} else {
|
} else {
|
||||||
|
led1().toggle();
|
||||||
PERF.played_frames
|
PERF.played_frames
|
||||||
.fetch_add(FRAMES_PER_SLOT, Ordering::Relaxed);
|
.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())
|
.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 {
|
struct FeedbackState {
|
||||||
correction_enabled: AtomicBool,
|
correction_enabled: AtomicBool,
|
||||||
integrator: AtomicI32,
|
integrator: AtomicI32,
|
||||||
@@ -348,6 +296,7 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
|||||||
AudioState::Running => self.run(),
|
AudioState::Running => self.run(),
|
||||||
AudioState::LowData => {}
|
AudioState::LowData => {}
|
||||||
AudioState::NoData => self.nodata(),
|
AudioState::NoData => self.nodata(),
|
||||||
|
AudioState::Stopping => self.stopping(),
|
||||||
}
|
}
|
||||||
self.state.store(state, Ordering::SeqCst);
|
self.state.store(state, Ordering::SeqCst);
|
||||||
PERF.state.store(state, Ordering::Relaxed);
|
PERF.state.store(state, Ordering::Relaxed);
|
||||||
@@ -460,6 +409,9 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
|||||||
Ordering::Relaxed,
|
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> {
|
impl<D: Dac<I>, I> ClockSource for Audio<'_, D, I> {
|
||||||
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
|
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) => {
|
(0, AudioState::Armed | AudioState::Prefill | AudioState::NoData) => {
|
||||||
self.transition(AudioState::Stopped)
|
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, AudioState::Stopped) => self.transition(AudioState::Armed),
|
||||||
(1, _) => {} // altSetting 1 in any other state is a no-op
|
(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
|
// Valid states here are Armed, Prefill, Running, Draining and NoData
|
||||||
match state {
|
match state {
|
||||||
AudioState::Stopped => {
|
AudioState::Stopped | AudioState::Stopping => {
|
||||||
defmt::error!("Received audio data when stopped")
|
defmt::error!("Received audio data when stopped/stopping")
|
||||||
}
|
}
|
||||||
// When armed, data rx goes to prefill
|
// When armed, data rx goes to prefill
|
||||||
AudioState::Armed => self.transition(AudioState::Prefill),
|
AudioState::Armed => self.transition(AudioState::Prefill),
|
||||||
@@ -748,16 +701,8 @@ fn main() -> ! {
|
|||||||
.into_gpio_pin(&mut iocon, &mut gpio)
|
.into_gpio_pin(&mut iocon, &mut gpio)
|
||||||
.into_output_low(),
|
.into_output_low(),
|
||||||
};
|
};
|
||||||
let leds = (
|
|
||||||
pins::Pio0_13::take()
|
hw::init_leds(&mut iocon, &mut gpio);
|
||||||
.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(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// iocon.disabled(&mut syscon).release(); // save the environment :)
|
// iocon.disabled(&mut syscon).release(); // save the environment :)
|
||||||
|
|
||||||
@@ -859,6 +804,14 @@ fn main() -> ! {
|
|||||||
|
|
||||||
move || {
|
move || {
|
||||||
usb_dev.poll(&mut [&mut uac2, &mut hid]);
|
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() {
|
if hid_update_timer.wait().is_ok() {
|
||||||
let report = PERF.build_report();
|
let report = PERF.build_report();
|
||||||
match hid.push_input(&report) {
|
match hid.push_input(&report) {
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ name = "shared"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
std = []
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||||
|
defmt = "1.1.1"
|
||||||
deku = { version = "0.20.3", default-features = false }
|
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"] }
|
serde = { version = "1.0.228", optional = true, features = ["derive"] }
|
||||||
usbd-hid = { version = "0.10.0" }
|
usbd-hid = { version = "0.10.0" }
|
||||||
|
|||||||
+124
-2
@@ -1,14 +1,120 @@
|
|||||||
#![no_std]
|
#![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 {
|
pub mod hid {
|
||||||
use deku::DekuRead;
|
use deku::DekuRead;
|
||||||
|
use usbd_hid::descriptor::generator_prelude::*;
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use usbd_hid::descriptor::generator_prelude::*;
|
|
||||||
|
use crate::{AudioState, AudioTelemetrySnapshot};
|
||||||
|
|
||||||
#[derive(DekuRead)]
|
#[derive(DekuRead)]
|
||||||
#[deku(endian = "little")]
|
#[deku(endian = "little")]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[gen_hid_descriptor(
|
#[gen_hid_descriptor(
|
||||||
(collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01, ) = {
|
(collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01, ) = {
|
||||||
state=input;
|
state=input;
|
||||||
@@ -37,4 +143,20 @@ pub mod hid {
|
|||||||
pub fb: i32,
|
pub fb: i32,
|
||||||
pub integrator: 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