telemetry improvements / cli CSV export
This commit is contained in:
Generated
+247
@@ -23,6 +23,56 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-hid"
|
||||
version = "0.5.1"
|
||||
@@ -233,17 +283,87 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-hid",
|
||||
"clap",
|
||||
"colog",
|
||||
"csv",
|
||||
"deku",
|
||||
"futures-lite",
|
||||
"log",
|
||||
"pollster",
|
||||
"shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colog"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df62599ba6adc9c6c04a54278c8209125343dc4775f57b9d76c9a4287e58f2bd"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"env_logger",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@@ -333,6 +453,27 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.21.3"
|
||||
@@ -491,6 +632,29 @@ dependencies = [
|
||||
"num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -672,6 +836,12 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
@@ -703,6 +873,42 @@ dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -1020,6 +1226,12 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "panic-halt"
|
||||
version = "1.0.0"
|
||||
@@ -1108,6 +1320,15 @@ version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.5.0"
|
||||
@@ -1169,6 +1390,18 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
@@ -1223,6 +1456,12 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@@ -1263,6 +1502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1299,6 +1539,7 @@ name = "shared"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"deku",
|
||||
"serde",
|
||||
"usbd-hid",
|
||||
]
|
||||
|
||||
@@ -1565,6 +1806,12 @@ dependencies = [
|
||||
"usb-device",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
|
||||
+5
-1
@@ -5,7 +5,11 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
async-hid = "0.5.1"
|
||||
clap = { version = "4.6.1", features = ["derive"] }
|
||||
colog = "1.4.0"
|
||||
csv = "1.4.0"
|
||||
deku = "0.20.3"
|
||||
futures-lite = "2.6.1"
|
||||
log = { version = "0.4.29", features = ["std"] }
|
||||
pollster = { version = "0.4.0", features = ["macro"] }
|
||||
shared = { path = "../shared" }
|
||||
shared = { path = "../shared", features = ["serde"] }
|
||||
|
||||
+63
-1
@@ -1,10 +1,67 @@
|
||||
use std::io;
|
||||
|
||||
use async_hid::{AsyncHidRead, HidBackend, HidResult};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use deku::DekuContainerRead;
|
||||
use futures_lite::StreamExt;
|
||||
use shared::hid::AudioTelemetryReport;
|
||||
|
||||
#[derive(Clone, Copy, Debug, ValueEnum)]
|
||||
enum Format {
|
||||
Debug,
|
||||
Csv,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about)]
|
||||
struct Args {
|
||||
#[arg(short, long, default_value = "debug")]
|
||||
format: Format,
|
||||
}
|
||||
|
||||
trait StateEmitter<W: io::Write> {
|
||||
fn from_writer(writer: W) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn emit(&mut self, r: &AudioTelemetryReport);
|
||||
}
|
||||
|
||||
struct DebugEmitter<T: io::Write> {
|
||||
writer: T,
|
||||
}
|
||||
|
||||
impl<W: io::Write> StateEmitter<W> for DebugEmitter<W> {
|
||||
fn from_writer(writer: W) -> Self {
|
||||
Self { writer }
|
||||
}
|
||||
fn emit(&mut self, r: &AudioTelemetryReport) {
|
||||
writeln!(self.writer, "{r:?}");
|
||||
}
|
||||
}
|
||||
|
||||
struct CsvEmitter<W: io::Write> {
|
||||
csv: csv::Writer<W>,
|
||||
}
|
||||
|
||||
impl<W: io::Write> StateEmitter<W> for CsvEmitter<W> {
|
||||
fn from_writer(writer: W) -> Self {
|
||||
Self {
|
||||
csv: csv::Writer::from_writer(writer),
|
||||
}
|
||||
}
|
||||
fn emit(&mut self, r: &AudioTelemetryReport) {
|
||||
if let Err(e) = self.csv.serialize(r) {
|
||||
eprintln!("Serialization error: {e:?}");
|
||||
} else {
|
||||
self.csv.flush().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pollster::main]
|
||||
async fn main() -> HidResult<()> {
|
||||
colog::init();
|
||||
let args = Args::parse();
|
||||
let usbhid = HidBackend::default();
|
||||
let dev = usbhid
|
||||
.enumerate()
|
||||
@@ -13,12 +70,17 @@ async fn main() -> HidResult<()> {
|
||||
.await
|
||||
.expect("GUAC device not found or not accessible (try as root?)");
|
||||
let mut reader = dev.open_readable().await?;
|
||||
let mut writer: Box<dyn StateEmitter<_>> = match args.format {
|
||||
Format::Debug => Box::new(DebugEmitter::from_writer(io::stdout())),
|
||||
Format::Csv => Box::new(CsvEmitter::from_writer(io::stdout())),
|
||||
};
|
||||
|
||||
let mut buf = [0u8; core::mem::size_of::<AudioTelemetryReport>()];
|
||||
while let Ok(r) = reader.read_input_report(&mut buf).await {
|
||||
log::debug!("read {}: {:?}", r, &buf[..r]);
|
||||
let buf = &buf[..r];
|
||||
match AudioTelemetryReport::from_bytes((buf, 0)) {
|
||||
Ok((_, r)) => println!("{:?}", r),
|
||||
Ok((_, r)) => writer.emit(&r),
|
||||
Err(e) => eprintln!("Unable to parse report: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ default = ["nodac", "hid"]
|
||||
ak4490 = []
|
||||
cs4398 = []
|
||||
nodac = []
|
||||
wm8904 = []
|
||||
hid = [ "dep:usbd-hid", "dep:shared" ]
|
||||
evk = [ "wm8904" ]
|
||||
|
||||
[dependencies]
|
||||
shared = { path="../shared", optional = true }
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
use usbd_hid::descriptor::generator_prelude::*;
|
||||
|
||||
#[gen_hid_descriptor(
|
||||
(collection = APPLICATION, usage_page = VENDOR_DEFINED_START, usage = 0x01, ) = {
|
||||
average_buffer_fill=input;
|
||||
frame_count=input;
|
||||
dac_underflow_count=input;
|
||||
usb_underflow_count=input;
|
||||
dac_overflow_count=input;
|
||||
}
|
||||
)]
|
||||
#[repr(C)]
|
||||
// Note these are all actually u32
|
||||
pub struct AudioTelemetryReport {
|
||||
pub average_buffer_fill: i32,
|
||||
pub frame_count: i32,
|
||||
pub dac_underflow_count: i32,
|
||||
pub usb_underflow_count: i32,
|
||||
pub dac_overflow_count: i32,
|
||||
}
|
||||
+56
-18
@@ -9,6 +9,7 @@ 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;
|
||||
@@ -64,21 +65,19 @@ pub mod dac {
|
||||
}
|
||||
|
||||
mod dma;
|
||||
#[cfg(feature = "hid")]
|
||||
mod hid;
|
||||
mod hw;
|
||||
mod traits;
|
||||
|
||||
const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples
|
||||
const BYTES_PER_FRAME: usize = BYTES_PER_SAMPLE * 2; // 2 channels
|
||||
const FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 2000; // run the DMA at 2khz
|
||||
const FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 4000; // run the DMA at 4khz
|
||||
const BYTES_PER_SLOT: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME;
|
||||
const N_SLOTS: usize = 8;
|
||||
const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2;
|
||||
const USB_FRAME_RATE: u32 = 8000; // microframe rate: 8000 for HS, 1000 for FS
|
||||
|
||||
// In frames
|
||||
const QUEUE_RUNNING_UP: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 4) / 10; // 40%
|
||||
const QUEUE_RUNNING_UP: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 5) / 10; // 50%
|
||||
const QUEUE_RUNNING_DOWN: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 2) / 10; // 20%
|
||||
const NODATA_TIMEOUT_FRAMES: usize = SAMPLE_RATE as usize / 100; // ~100ms
|
||||
#[cfg(not(feature = "evk"))]
|
||||
@@ -87,7 +86,7 @@ const MCLK_FREQ: u32 = 24576000;
|
||||
const MCLK_FREQ: u32 = 24576000 / 2;
|
||||
|
||||
const SAMPLE_RATE: u32 = 192000;
|
||||
const HID_INTERVAL_MS: u8 = 100;
|
||||
const HID_INTERVAL_MS: u8 = 10;
|
||||
|
||||
struct CodecPins {
|
||||
reset: Pin<pins::Pio0_3, Gpio<Output>>,
|
||||
@@ -100,6 +99,7 @@ struct ClockSelPins {
|
||||
|
||||
#[derive(Default)]
|
||||
struct PerfCounters {
|
||||
state: Atomic<AudioState>,
|
||||
received_frames: AtomicUsize,
|
||||
played_frames: AtomicUsize,
|
||||
min_fill: AtomicUsize,
|
||||
@@ -107,6 +107,10 @@ struct PerfCounters {
|
||||
queue_underflows: AtomicUsize,
|
||||
queue_overflows: AtomicUsize,
|
||||
audio_underflows: AtomicUsize,
|
||||
integrator: AtomicI32,
|
||||
p: AtomicI32,
|
||||
i: AtomicI32,
|
||||
fb: AtomicI32,
|
||||
}
|
||||
|
||||
impl PerfCounters {
|
||||
@@ -120,14 +124,22 @@ impl PerfCounters {
|
||||
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 {
|
||||
average_buffer_fill: self.avg_fill.load(Ordering::Relaxed) as i32,
|
||||
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 i32,
|
||||
usb_underflow_count: self.queue_underflows.load(Ordering::Relaxed) as i32,
|
||||
dac_overflow_count: self.queue_overflows.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: self.fb.load(Ordering::Relaxed) as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,6 +161,7 @@ impl defmt::Format for PerfCounters {
|
||||
}
|
||||
|
||||
static PERF: PerfCounters = PerfCounters {
|
||||
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
|
||||
@@ -156,6 +169,10 @@ static PERF: PerfCounters = PerfCounters {
|
||||
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),
|
||||
};
|
||||
|
||||
static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
|
||||
@@ -255,6 +272,11 @@ enum AudioState {
|
||||
/// 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!(
|
||||
@@ -328,6 +350,7 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
||||
AudioState::NoData => self.nodata(),
|
||||
}
|
||||
self.state.store(state, Ordering::SeqCst);
|
||||
PERF.state.store(state, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
@@ -568,28 +591,43 @@ impl<D: Dac<I>, I, B: bus::UsbBus> AudioHandler<'_, B> for Audio<'_, D, I> {
|
||||
PERF.queue_underflows.fetch_add(1, Ordering::Relaxed);
|
||||
return Some(nominal_rate);
|
||||
}
|
||||
|
||||
PERF.avg_fill
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| {
|
||||
Some(((v << 6) - v + current_bytes as usize) >> 6)
|
||||
})
|
||||
.ok();
|
||||
|
||||
// normalize error wrt. frame size etc.
|
||||
let error_permille = ((current_bytes - FILL_TARGET_BYTES) * 1000) / FILL_TARGET_BYTES;
|
||||
let raw_error = current_bytes - FILL_TARGET_BYTES;
|
||||
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%
|
||||
|
||||
// 0.2% which is a huge clock error
|
||||
let max_allowed_deviation = nominal_v / 500;
|
||||
// 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) / FILL_TARGET_BYTES;
|
||||
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;
|
||||
|
||||
let p_term = -(error_permille * nominal_v) / 256000; // this works reasonably well to keep the buffer
|
||||
let i_term = 0; // placeholder
|
||||
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))
|
||||
}
|
||||
@@ -820,15 +858,15 @@ fn main() -> ! {
|
||||
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
||||
|
||||
move || {
|
||||
let active = usb_dev.poll(&mut [&mut uac2, &mut hid]);
|
||||
if active && hid_update_timer.wait().is_ok() {
|
||||
usb_dev.poll(&mut [&mut uac2, &mut hid]);
|
||||
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 timer is not Periodic, so restart it
|
||||
// lpc55 ctimer is not Periodic, so restart it
|
||||
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
name = "shared"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
deku = { version = "0.20.3", default-features = false }
|
||||
serde = { version = "1.0.228", optional = true, features = ["derive"] }
|
||||
usbd-hid = { version = "0.10.0" }
|
||||
|
||||
+19
-6
@@ -2,26 +2,39 @@
|
||||
|
||||
pub mod hid {
|
||||
use deku::DekuRead;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use usbd_hid::descriptor::generator_prelude::*;
|
||||
|
||||
#[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;
|
||||
average_buffer_fill=input;
|
||||
frame_count=input;
|
||||
dac_underflow_count=input;
|
||||
usb_underflow_count=input;
|
||||
dac_overflow_count=input;
|
||||
p=input;
|
||||
i=input;
|
||||
fb=input;
|
||||
integrator=input;
|
||||
}
|
||||
)]
|
||||
#[repr(C)]
|
||||
// Note these are all actually u32
|
||||
#[repr(C, packed)]
|
||||
// TODO: Fix that most of these values are actually u32, but usbd_hid macro doesn't work properly on u32
|
||||
pub struct AudioTelemetryReport {
|
||||
pub average_buffer_fill: i32,
|
||||
pub state: u8,
|
||||
pub average_buffer_fill: u16,
|
||||
pub frame_count: i32,
|
||||
pub dac_underflow_count: i32,
|
||||
pub usb_underflow_count: i32,
|
||||
pub dac_overflow_count: i32,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user