Compare commits
8 Commits
4e3f1f52ca
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
31edf91933
|
|||
|
ab09c98084
|
|||
|
5e8d6e2a34
|
|||
| e1d7be5e76 | |||
|
74e37192fa
|
|||
|
c8d3d0409a
|
|||
|
4c2384fba5
|
|||
|
69511f4061
|
@@ -1,9 +1,15 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: cargo-check
|
- id: cargo-check-host
|
||||||
name: Cargo check
|
name: Cargo check (host)
|
||||||
entry: cargo check
|
entry: cargo check -p cli -p shared
|
||||||
|
pass_filenames: false
|
||||||
|
types: [file, rust]
|
||||||
|
language: system
|
||||||
|
- id: cargo-check-firmware
|
||||||
|
name: Cargo check (firmware)
|
||||||
|
entry: cargo check -p guac --target thumbv8m.main-none-eabihf
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
types: [file, rust]
|
types: [file, rust]
|
||||||
language: system
|
language: system
|
||||||
|
|||||||
Generated
+875
-24
File diff suppressed because it is too large
Load Diff
+8
-31
@@ -1,35 +1,12 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "guac"
|
members = [
|
||||||
version = "0.1.0"
|
"firmware",
|
||||||
edition = "2024"
|
"cli",
|
||||||
|
"shared"
|
||||||
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[features]
|
default-members = ["firmware"]
|
||||||
default = ["nodac", "hid"]
|
|
||||||
ak4490 = []
|
|
||||||
cs4398 = []
|
|
||||||
nodac = []
|
|
||||||
hid = [ "dep:usbd-hid"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
atomic = "0.6.1"
|
|
||||||
bbqueue = "0.7.0"
|
|
||||||
bytemuck = { version = "1.25.0", features = ["derive"] }
|
|
||||||
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
|
|
||||||
cortex-m-rt = "0.7.5"
|
|
||||||
defmt = "1.0.1"
|
|
||||||
defmt-rtt = "1.1.0"
|
|
||||||
embedded-hal = "0.2.7"
|
|
||||||
embedded-io = "0.7.1"
|
|
||||||
log-to-defmt = "0.1.0"
|
|
||||||
# Includes update to usb-device 0.3, fix for isochronous and smaller critical sections
|
|
||||||
lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
|
|
||||||
nb = "1.1.0"
|
|
||||||
panic-halt = "1.0.0"
|
|
||||||
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"]}
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
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", features = ["serde"] }
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
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()
|
||||||
|
.await?
|
||||||
|
.find(|d| d.product_id == 0xcc1d && d.vendor_id == 0x1209)
|
||||||
|
.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)) => writer.emit(&r),
|
||||||
|
Err(e) => eprintln!("Unable to parse report: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Generated
+1168
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
|||||||
|
[package]
|
||||||
|
name = "guac"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["nodac", "hid"]
|
||||||
|
ak4490 = []
|
||||||
|
cs4398 = []
|
||||||
|
nodac = []
|
||||||
|
wm8904 = []
|
||||||
|
hid = [ "dep:usbd-hid", "dep:shared" ]
|
||||||
|
evk = [ "wm8904" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
shared = { path="../shared", optional = true }
|
||||||
|
atomic = "0.6.1"
|
||||||
|
bbqueue = "0.7.0"
|
||||||
|
bytemuck = { version = "1.25.0", features = ["derive"] }
|
||||||
|
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
|
||||||
|
cortex-m-rt = "0.7.5"
|
||||||
|
defmt = "1.0.1"
|
||||||
|
defmt-rtt = "1.1.0"
|
||||||
|
embedded-hal = "0.2.7"
|
||||||
|
embedded-io = "0.7.1"
|
||||||
|
log-to-defmt = "0.1.0"
|
||||||
|
# Includes update to usb-device 0.3, fix for isochronous and smaller critical sections
|
||||||
|
lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
|
||||||
|
nb = "1.1.0"
|
||||||
|
panic-halt = "1.0.0"
|
||||||
|
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"]}
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "z"
|
||||||
|
lto = true
|
||||||
|
debug = true
|
||||||
|
codegen-units = 1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Find the actual path of memory.x and add it to link search, required for building in workspace
|
||||||
|
fn main() {
|
||||||
|
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
println!("cargo:rustc-link-search={}", manifest_dir);
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ enum RegisterAddress {
|
|||||||
pub struct Ak4490Dac<T> {
|
pub struct Ak4490Dac<T> {
|
||||||
i2c: T,
|
i2c: T,
|
||||||
pins: CodecPins, // this dependency is unfortunate, but non trivial to generalize
|
pins: CodecPins, // this dependency is unfortunate, but non trivial to generalize
|
||||||
|
volume: (u8, u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Ak4490Dac<T>
|
impl<T> Ak4490Dac<T>
|
||||||
@@ -43,6 +44,10 @@ where
|
|||||||
_ => 5,
|
_ => 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn set_volume_impl(&mut self, left: u8, right: u8) {
|
||||||
|
self.write_reg(RegisterAddress::LeftAtt, left);
|
||||||
|
self.write_reg(RegisterAddress::RightAtt, right);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Dac<T> for Ak4490Dac<T>
|
impl<T> Dac<T> for Ak4490Dac<T>
|
||||||
@@ -50,7 +55,11 @@ where
|
|||||||
T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write,
|
T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write,
|
||||||
{
|
{
|
||||||
fn new(i2c: T, pins: CodecPins) -> Self {
|
fn new(i2c: T, pins: CodecPins) -> Self {
|
||||||
Self { i2c, pins }
|
Self {
|
||||||
|
i2c,
|
||||||
|
pins,
|
||||||
|
volume: (0xff, 0xff),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn init(&mut self) {
|
fn init(&mut self) {
|
||||||
// bring out of reset
|
// bring out of reset
|
||||||
@@ -66,7 +75,13 @@ where
|
|||||||
self.write_reg(RegisterAddress::Control4, (dfs & 0x4) >> 1);
|
self.write_reg(RegisterAddress::Control4, (dfs & 0x4) >> 1);
|
||||||
}
|
}
|
||||||
fn set_volume(&mut self, left: u8, right: u8) {
|
fn set_volume(&mut self, left: u8, right: u8) {
|
||||||
self.write_reg(RegisterAddress::LeftAtt, left);
|
self.set_volume_impl(left, right);
|
||||||
self.write_reg(RegisterAddress::RightAtt, right);
|
self.volume = (left, right);
|
||||||
|
}
|
||||||
|
fn mute(&mut self) {
|
||||||
|
self.set_volume_impl(0, 0);
|
||||||
|
}
|
||||||
|
fn unmute(&mut self) {
|
||||||
|
self.set_volume_impl(self.volume.0, self.volume.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
use crate::{CodecPins, traits::Dac};
|
||||||
|
use embedded_hal::prelude::{
|
||||||
|
_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead,
|
||||||
|
};
|
||||||
|
const WM8904_I2C_ADDRESS: u8 = 0b0011010;
|
||||||
|
const MCLK: u32 = 24576000 / 2; // assume EVK TODO: better
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
enum RegisterAddress {
|
||||||
|
SwResetId = 0x00,
|
||||||
|
BiasControl0 = 0x04,
|
||||||
|
VmidControl = 0x05,
|
||||||
|
MicBiasControl0 = 0x06,
|
||||||
|
MicBiasControl1 = 0x07,
|
||||||
|
AnaAdc0 = 0x0a,
|
||||||
|
PowerMgmt0 = 0x0c,
|
||||||
|
PowerMgmt2 = 0x0e,
|
||||||
|
PowerMgmt3 = 0x0f,
|
||||||
|
PowerMgmt6 = 0x12,
|
||||||
|
ClockRates0 = 0x14,
|
||||||
|
ClockRates1 = 0x15,
|
||||||
|
ClockRates2 = 0x16,
|
||||||
|
AudioInterface0 = 0x18,
|
||||||
|
AudioInterface1 = 0x19,
|
||||||
|
AudioInterface2 = 0x1a,
|
||||||
|
AudioInterface3 = 0x1b,
|
||||||
|
DacDigiVolLeft = 0x1e,
|
||||||
|
DacDigiVolRight = 0x1f,
|
||||||
|
DacDigital0 = 0x20,
|
||||||
|
DacDigi1 = 0x21,
|
||||||
|
AdcDigiVolLeft = 0x24,
|
||||||
|
AdcDigiVolRight = 0x25,
|
||||||
|
AdcDigital0 = 0x26,
|
||||||
|
DigiMic0 = 0x27,
|
||||||
|
Drc0 = 0x28,
|
||||||
|
Drc1 = 0x29,
|
||||||
|
Drc2 = 0x2a,
|
||||||
|
Drc3 = 0x2b,
|
||||||
|
AnaLeftIn0 = 0x2c,
|
||||||
|
AnaRightIn0 = 0x2d,
|
||||||
|
AnaLeftIn1 = 0x2e,
|
||||||
|
AnaRightIn1 = 0x2f,
|
||||||
|
AnaOut1Left = 0x39,
|
||||||
|
AnaOut1Right = 0x3a,
|
||||||
|
AnaOut2Left = 0x3b,
|
||||||
|
AnaOut2Right = 0x3c,
|
||||||
|
AnaOut12Zc = 0x3d,
|
||||||
|
DcServo0 = 0x43,
|
||||||
|
DcServo1 = 0x44,
|
||||||
|
DcServo2 = 0x45,
|
||||||
|
DcServo4 = 0x47,
|
||||||
|
DcServo5 = 0x48,
|
||||||
|
DcServo6 = 0x49,
|
||||||
|
DcServo7 = 0x4a,
|
||||||
|
DcServo8 = 0x4b,
|
||||||
|
DcServo9 = 0x4c,
|
||||||
|
DcServoRb0 = 0x4d,
|
||||||
|
AnaHp0 = 0x5a,
|
||||||
|
AnaLineOut0 = 0x5e,
|
||||||
|
ChargePump0 = 0x62,
|
||||||
|
ClassW = 0x68,
|
||||||
|
WriteSeq0 = 0x6c,
|
||||||
|
WriteSeq1 = 0x6d,
|
||||||
|
WriteSeq2 = 0x6e,
|
||||||
|
WriteSeq3 = 0x6f,
|
||||||
|
WriteSeq4 = 0x70,
|
||||||
|
FllControl1 = 0x74,
|
||||||
|
FllControl2 = 0x75,
|
||||||
|
FllControl3 = 0x76,
|
||||||
|
FllControl4 = 0x77,
|
||||||
|
FllControl5 = 0x78,
|
||||||
|
GpioControl1 = 0x79,
|
||||||
|
GpioControl2 = 0x7a,
|
||||||
|
GpioControl3 = 0x7b,
|
||||||
|
GpioControl4 = 0x7c,
|
||||||
|
DigiPulls = 0x7e,
|
||||||
|
IntStatus = 0x7f,
|
||||||
|
IntStatusMask = 0x80,
|
||||||
|
IntPriority = 0x81,
|
||||||
|
IntDebounce = 0x82,
|
||||||
|
Eq1 = 0x86,
|
||||||
|
Eq2 = 0x87,
|
||||||
|
Eq3 = 0x88,
|
||||||
|
Eq4 = 0x89,
|
||||||
|
Eq5 = 0x8a,
|
||||||
|
Eq6 = 0x8b,
|
||||||
|
Eq7 = 0x8c,
|
||||||
|
Eq8 = 0x8d,
|
||||||
|
Eq9 = 0x8e,
|
||||||
|
Eq10 = 0x8f,
|
||||||
|
Eq11 = 0x90,
|
||||||
|
Eq12 = 0x91,
|
||||||
|
Eq13 = 0x92,
|
||||||
|
Eq14 = 0x93,
|
||||||
|
Eq15 = 0x94,
|
||||||
|
Eq16 = 0x95,
|
||||||
|
Eq17 = 0x96,
|
||||||
|
Eq18 = 0x97,
|
||||||
|
Eq19 = 0x98,
|
||||||
|
Eq20 = 0x99,
|
||||||
|
Eq21 = 0x9a,
|
||||||
|
Eq22 = 0x9b,
|
||||||
|
Eq23 = 0x9c,
|
||||||
|
Eq24 = 0x9d,
|
||||||
|
AdcTest0 = 0xc6,
|
||||||
|
FllNcoTest0 = 0xf7,
|
||||||
|
FllNcoTest1 = 0xf8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Wm8904Dac<T> {
|
||||||
|
i2c: T,
|
||||||
|
pins: CodecPins,
|
||||||
|
mclk: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Wm8904Dac<T>
|
||||||
|
where
|
||||||
|
T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn write_reg(&mut self, reg: RegisterAddress, val: u16) {
|
||||||
|
let b = val.to_be_bytes();
|
||||||
|
defmt::info!("i2c w [{:?}]", &[reg as u8, b[0], b[1]]);
|
||||||
|
self.i2c
|
||||||
|
.write(WM8904_I2C_ADDRESS, &[reg as u8, b[0], b[1]])
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
fn cr1_for_rate(&self, rate: u32) -> u16 {
|
||||||
|
let fs_ratio = self.mclk / rate;
|
||||||
|
if !self.mclk.is_multiple_of(rate) {
|
||||||
|
defmt::warn!("sample rate should be a multiple of mclk");
|
||||||
|
}
|
||||||
|
let clk_sys_rate: u16 = match fs_ratio {
|
||||||
|
64 => 0,
|
||||||
|
128 => 1,
|
||||||
|
192 => 2,
|
||||||
|
256 => 3,
|
||||||
|
384 => 4,
|
||||||
|
512 => 5,
|
||||||
|
768 => 6,
|
||||||
|
1024 => 7,
|
||||||
|
1408 => 8,
|
||||||
|
1536 => 9,
|
||||||
|
_ => {
|
||||||
|
defmt::warn!("unsupport ratio {}", fs_ratio);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let sample_rate: u16 = match rate {
|
||||||
|
r if r < 11025 => 0, // 0-11024
|
||||||
|
r if r < 16000 => 1, // 11025 - 15999
|
||||||
|
r if r < 22050 => 2, // 16000 - 22049
|
||||||
|
r if r < 32000 => 3, // 22050 - 31999
|
||||||
|
r if r < 44100 => 4, // 32000 - 44099
|
||||||
|
_ => 5, // 44100+
|
||||||
|
};
|
||||||
|
(clk_sys_rate << 10) | sample_rate
|
||||||
|
}
|
||||||
|
fn blck_div_for_rate(&self, rate: u32) -> u16 {
|
||||||
|
let bits_per_frame = 64;
|
||||||
|
let bits_per_second = bits_per_frame * rate;
|
||||||
|
(self.mclk / bits_per_second) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Dac<T> for Wm8904Dac<T>
|
||||||
|
where
|
||||||
|
T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write,
|
||||||
|
{
|
||||||
|
fn new(i2c: T, pins: CodecPins) -> Self {
|
||||||
|
Self {
|
||||||
|
i2c,
|
||||||
|
pins,
|
||||||
|
mclk: MCLK,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn init(&mut self) {
|
||||||
|
let mut buf = [0u8; 2];
|
||||||
|
match self.i2c.write_read(WM8904_I2C_ADDRESS, &[0], &mut buf) {
|
||||||
|
Ok(_) => {
|
||||||
|
let chip_id = ((buf[0] as u16) << 8) | buf[1] as u16;
|
||||||
|
defmt::info!("Read chip ID: {:x}", chip_id)
|
||||||
|
}
|
||||||
|
Err(_) => defmt::error!("Error reading I2C"),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_reg(RegisterAddress::ClockRates2, 0x000f); // OPCLK_ENA | CLK_SYS_ENA | CLK_DSP_ENA | TOCLK_ENA
|
||||||
|
self.write_reg(RegisterAddress::WriteSeq0, 0x0100); // write sequencer 0 ENA
|
||||||
|
self.write_reg(RegisterAddress::WriteSeq3, 0x0100); // write sequencer 3 START, INDEX=0
|
||||||
|
// wait on write sequencer
|
||||||
|
defmt::info!("[codec] waiting on write seq");
|
||||||
|
loop {
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
self.i2c
|
||||||
|
.write_read(WM8904_I2C_ADDRESS, &[0x70], &mut buf)
|
||||||
|
.ok();
|
||||||
|
if buf[1] & 1 == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defmt::debug!("[codec] write seq done");
|
||||||
|
self.write_reg(RegisterAddress::ClockRates0, 0);
|
||||||
|
self.write_reg(RegisterAddress::PowerMgmt0, 0); // IN PGAs disabled
|
||||||
|
self.write_reg(RegisterAddress::PowerMgmt2, 0x0003); // HPL_PGA_ENA | HPR_PGA_ENA
|
||||||
|
self.write_reg(RegisterAddress::PowerMgmt3, 0); //line outs disabled
|
||||||
|
|
||||||
|
self.write_reg(RegisterAddress::PowerMgmt6, 0x000c); // power management 6 = DACL_ENA | DACR_ENA
|
||||||
|
self.write_reg(RegisterAddress::AudioInterface0, 0x0050); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC
|
||||||
|
self.write_reg(RegisterAddress::DacDigi1, 0x0040); // dac digital 1 = DAC_OSR128
|
||||||
|
self.write_reg(RegisterAddress::AnaLeftIn0, 0x0005);
|
||||||
|
self.write_reg(RegisterAddress::AnaRightIn0, 0x0005);
|
||||||
|
self.write_reg(RegisterAddress::AnaOut1Left, 0x0039); // analog out1 left = vol=0dB
|
||||||
|
self.write_reg(RegisterAddress::AnaOut1Right, 0x0039); // analog out1 right = vol=0dB
|
||||||
|
self.write_reg(RegisterAddress::AnaOut2Left, 0x0039); // analog out2 left = vol=0dB
|
||||||
|
self.write_reg(RegisterAddress::AnaOut2Right, 0x0039); // analog out2 right = vol=0dB
|
||||||
|
self.write_reg(RegisterAddress::DcServo0, 0x0003); // dc servo 0 = HPOUTL_ENA | HPOUTR_ENA
|
||||||
|
self.write_reg(RegisterAddress::AnaHp0, 0x00ff); // analog hp 0 = remove all shorts etc
|
||||||
|
self.write_reg(RegisterAddress::AnaLineOut0, 0x00ff); // analog lineout 0 = remove all shorts etc
|
||||||
|
self.write_reg(RegisterAddress::ClassW, 0x0001); // enable class w charge pump
|
||||||
|
self.write_reg(RegisterAddress::ChargePump0, 0x0001); // enable charge pump
|
||||||
|
self.write_reg(RegisterAddress::AudioInterface1, (3 << 2) | 2); // audio if 1 = i2s, 32 bit per sample
|
||||||
|
|
||||||
|
self.write_reg(RegisterAddress::ClockRates1, self.cr1_for_rate(96000)); // Set up for 48k, impl will change if needed
|
||||||
|
self.write_reg(RegisterAddress::ClockRates2, 0x000f); // clock rates 2 = CLK_SYS_ENA
|
||||||
|
|
||||||
|
self.write_reg(
|
||||||
|
RegisterAddress::AudioInterface2,
|
||||||
|
self.blck_div_for_rate(96000),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.write_reg(RegisterAddress::AudioInterface3, 0); // audio interface 3 = input lrclock
|
||||||
|
self.write_reg(RegisterAddress::AnaOut12Zc, 0); // analog out12 zc = play source = dac
|
||||||
|
self.write_reg(RegisterAddress::DacDigiVolLeft, 0x01ff); // dac vol left = update left/right = 0dB
|
||||||
|
}
|
||||||
|
fn change_rate(&mut self, new_rate: u32) {
|
||||||
|
// TODO: mute, stop clocks etc.
|
||||||
|
defmt::info!("dac rate -> {}", new_rate);
|
||||||
|
self.write_reg(RegisterAddress::ClockRates1, self.cr1_for_rate(new_rate));
|
||||||
|
self.write_reg(
|
||||||
|
RegisterAddress::AudioInterface2,
|
||||||
|
self.blck_div_for_rate(new_rate),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn mute(&mut self) {
|
||||||
|
// self.write_reg(RegisterAddress::DacDigiVolLeft, 0x0100);
|
||||||
|
}
|
||||||
|
fn unmute(&mut self) {
|
||||||
|
// TODO: restore previous volume
|
||||||
|
// self.write_reg(RegisterAddress::DacDigiVolLeft, 0x01ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::pac;
|
use crate::pac;
|
||||||
use defmt::debug;
|
use defmt::{debug, info};
|
||||||
|
|
||||||
pub(crate) struct PllConstants {
|
pub(crate) struct PllConstants {
|
||||||
pub m: u16, // 1-65535
|
pub m: u16, // 1-65535
|
||||||
@@ -112,3 +112,75 @@ pub(crate) fn init_sys_pll1() {
|
|||||||
syscon.fmccr.modify(|_, w| w.flashtim().flashtim11());
|
syscon.fmccr.modify(|_, w| w.flashtim().flashtim11());
|
||||||
syscon.mainclkselb.modify(|_, w| w.sel().enum_0x2()); // pll1
|
syscon.mainclkselb.modify(|_, w| w.sel().enum_0x2()); // pll1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fo = M/(N*2*P) * Fin
|
||||||
|
// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz
|
||||||
|
const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8);
|
||||||
|
|
||||||
|
// Set PLL0 to 24.576MHz, start, and wait for lock
|
||||||
|
// This is not exposed by lpc55-hal, unfortunately. Copy their implementation here.
|
||||||
|
pub(crate) fn init_audio_pll() {
|
||||||
|
let syscon = unsafe { &*pac::SYSCON::ptr() };
|
||||||
|
let pmc = unsafe { &*pac::PMC::ptr() };
|
||||||
|
let anactrl = unsafe { &*pac::ANACTRL::ptr() };
|
||||||
|
|
||||||
|
debug!("start clk_in");
|
||||||
|
pmc.pdruncfg0
|
||||||
|
.modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon());
|
||||||
|
syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable());
|
||||||
|
anactrl
|
||||||
|
.xo32m_ctrl
|
||||||
|
.modify(|_, w| w.enable_system_clk_out().enable());
|
||||||
|
|
||||||
|
debug!("init pll0: {}", AUDIO_PLL);
|
||||||
|
pmc.pdruncfg0
|
||||||
|
.modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff());
|
||||||
|
syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in
|
||||||
|
syscon.pll0ctrl.write(|w| unsafe {
|
||||||
|
w.clken()
|
||||||
|
.enable()
|
||||||
|
.seli()
|
||||||
|
.bits(AUDIO_PLL.seli)
|
||||||
|
.selp()
|
||||||
|
.bits(AUDIO_PLL.selp)
|
||||||
|
});
|
||||||
|
|
||||||
|
syscon
|
||||||
|
.pll0ndec
|
||||||
|
.write(|w| unsafe { w.ndiv().bits(AUDIO_PLL.n) });
|
||||||
|
syscon.pll0ndec.write(|w| unsafe {
|
||||||
|
w.ndiv().bits(AUDIO_PLL.n).nreq().set_bit() // latch
|
||||||
|
});
|
||||||
|
|
||||||
|
syscon
|
||||||
|
.pll0pdec
|
||||||
|
.write(|w| unsafe { w.pdiv().bits(AUDIO_PLL.p) });
|
||||||
|
syscon.pll0pdec.write(|w| unsafe {
|
||||||
|
w.pdiv().bits(AUDIO_PLL.p).preq().set_bit() // latch
|
||||||
|
});
|
||||||
|
|
||||||
|
syscon.pll0sscg0.write(|w| unsafe { w.md_lbs().bits(0) });
|
||||||
|
|
||||||
|
syscon
|
||||||
|
.pll0sscg1
|
||||||
|
.write(|w| unsafe { w.mdiv_ext().bits(AUDIO_PLL.m).sel_ext().set_bit() });
|
||||||
|
syscon.pll0sscg1.write(|w| unsafe {
|
||||||
|
w.mdiv_ext()
|
||||||
|
.bits(AUDIO_PLL.m)
|
||||||
|
.sel_ext()
|
||||||
|
.set_bit()
|
||||||
|
.mreq()
|
||||||
|
.set_bit() // latch
|
||||||
|
.md_req()
|
||||||
|
.set_bit() // latch
|
||||||
|
});
|
||||||
|
|
||||||
|
pmc.pdruncfg0
|
||||||
|
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
|
||||||
|
info!("pll0 wait for lock");
|
||||||
|
let mut i = 0usize;
|
||||||
|
while syscon.pll0stat.read().lock().bit_is_clear() {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
info!("pll0 locked after {} loops", i);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ fn panic() -> ! {
|
|||||||
|
|
||||||
use atomic::Atomic;
|
use atomic::Atomic;
|
||||||
use bytemuck::NoUninit;
|
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;
|
||||||
@@ -39,8 +40,8 @@ use usbd_uac2::{
|
|||||||
|
|
||||||
use crate::dac::DacImpl;
|
use crate::dac::DacImpl;
|
||||||
use crate::dma::DmaRing;
|
use crate::dma::DmaRing;
|
||||||
use crate::hid::AudioTelemetryReport;
|
|
||||||
use crate::traits::Dac;
|
use crate::traits::Dac;
|
||||||
|
use shared::hid::AudioTelemetryReport;
|
||||||
|
|
||||||
#[cfg(feature = "ak4490")]
|
#[cfg(feature = "ak4490")]
|
||||||
pub mod dac {
|
pub mod dac {
|
||||||
@@ -57,28 +58,35 @@ pub mod dac {
|
|||||||
mod noop;
|
mod noop;
|
||||||
pub use self::noop::NoopDac as DacImpl;
|
pub use self::noop::NoopDac as DacImpl;
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "wm8904")]
|
||||||
|
pub mod dac {
|
||||||
|
mod wm8904;
|
||||||
|
pub use self::wm8904::Wm8904Dac as DacImpl;
|
||||||
|
}
|
||||||
|
|
||||||
mod dma;
|
mod dma;
|
||||||
#[cfg(feature = "hid")]
|
|
||||||
mod hid;
|
|
||||||
mod hw;
|
mod hw;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
|
||||||
const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples
|
const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples
|
||||||
const BYTES_PER_FRAME: usize = BYTES_PER_SAMPLE * 2; // 2 channels
|
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 BYTES_PER_SLOT: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME;
|
||||||
const N_SLOTS: usize = 8;
|
const N_SLOTS: usize = 8;
|
||||||
const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2;
|
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
|
const USB_FRAME_RATE: u32 = 8000; // microframe rate: 8000 for HS, 1000 for FS
|
||||||
|
|
||||||
// In frames
|
// 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 QUEUE_RUNNING_DOWN: usize = ((FRAMES_PER_SLOT * N_SLOTS) * 2) / 10; // 20%
|
||||||
const NODATA_TIMEOUT_FRAMES: usize = SAMPLE_RATE as usize / 100; // ~100ms
|
const NODATA_TIMEOUT_FRAMES: usize = SAMPLE_RATE as usize / 100; // ~100ms
|
||||||
|
#[cfg(not(feature = "evk"))]
|
||||||
const MCLK_FREQ: u32 = 24576000;
|
const MCLK_FREQ: u32 = 24576000;
|
||||||
|
#[cfg(feature = "evk")]
|
||||||
|
const MCLK_FREQ: u32 = 24576000 / 2;
|
||||||
|
|
||||||
const SAMPLE_RATE: u32 = 192000;
|
const SAMPLE_RATE: u32 = 192000;
|
||||||
const HID_INTERVAL_MS: u8 = 100;
|
const HID_INTERVAL_MS: u8 = 10;
|
||||||
|
|
||||||
struct CodecPins {
|
struct CodecPins {
|
||||||
reset: Pin<pins::Pio0_3, Gpio<Output>>,
|
reset: Pin<pins::Pio0_3, Gpio<Output>>,
|
||||||
@@ -91,6 +99,7 @@ struct ClockSelPins {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct PerfCounters {
|
struct PerfCounters {
|
||||||
|
state: Atomic<AudioState>,
|
||||||
received_frames: AtomicUsize,
|
received_frames: AtomicUsize,
|
||||||
played_frames: AtomicUsize,
|
played_frames: AtomicUsize,
|
||||||
min_fill: AtomicUsize,
|
min_fill: AtomicUsize,
|
||||||
@@ -98,6 +107,10 @@ struct PerfCounters {
|
|||||||
queue_underflows: AtomicUsize,
|
queue_underflows: AtomicUsize,
|
||||||
queue_overflows: AtomicUsize,
|
queue_overflows: AtomicUsize,
|
||||||
audio_underflows: AtomicUsize,
|
audio_underflows: AtomicUsize,
|
||||||
|
integrator: AtomicI32,
|
||||||
|
p: AtomicI32,
|
||||||
|
i: AtomicI32,
|
||||||
|
fb: AtomicI32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PerfCounters {
|
impl PerfCounters {
|
||||||
@@ -111,14 +124,22 @@ impl PerfCounters {
|
|||||||
self.queue_underflows.store(0, Ordering::Relaxed);
|
self.queue_underflows.store(0, Ordering::Relaxed);
|
||||||
self.queue_overflows.store(0, Ordering::Relaxed);
|
self.queue_overflows.store(0, Ordering::Relaxed);
|
||||||
self.audio_underflows.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 {
|
fn build_report(&self) -> AudioTelemetryReport {
|
||||||
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,
|
frame_count: self.played_frames.load(Ordering::Relaxed) as i32,
|
||||||
dac_underflow_count: self.audio_underflows.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 i32,
|
usb_underflow_count: self.queue_underflows.load(Ordering::Relaxed) as u16,
|
||||||
dac_overflow_count: self.queue_overflows.load(Ordering::Relaxed) as i32,
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,6 +161,7 @@ impl defmt::Format for PerfCounters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static PERF: PerfCounters = PerfCounters {
|
static PERF: PerfCounters = PerfCounters {
|
||||||
|
state: Atomic::new(AudioState::Stopped),
|
||||||
received_frames: AtomicUsize::new(0), // received from USB
|
received_frames: AtomicUsize::new(0), // received from USB
|
||||||
played_frames: AtomicUsize::new(0), // played audio frames
|
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
|
min_fill: AtomicUsize::new(0), // not recording this for now, need to figure out how to make it meaningful, since the queue starts empty
|
||||||
@@ -147,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_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),
|
queue_overflows: AtomicUsize::new(0),
|
||||||
audio_underflows: 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();
|
static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
|
||||||
@@ -200,6 +226,15 @@ fn DMA0() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[interrupt]
|
||||||
|
fn FLEXCOMM7() {
|
||||||
|
// Count I2S TX FIFO error (should be underrun, assuming we set up our DMA trigger correctly)
|
||||||
|
PERF.audio_underflows.fetch_add(1, Ordering::Relaxed);
|
||||||
|
unsafe { &*pac::I2S7::ptr() }
|
||||||
|
.fifostat
|
||||||
|
.modify(|_, w| w.txerr().set_bit())
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Copy, NoUninit, Eq, PartialEq)]
|
#[derive(Clone, Copy, NoUninit, Eq, PartialEq)]
|
||||||
enum AudioState {
|
enum AudioState {
|
||||||
@@ -237,6 +272,11 @@ enum AudioState {
|
|||||||
/// AltSetting = 0 -> STOPPED
|
/// AltSetting = 0 -> STOPPED
|
||||||
NoData,
|
NoData,
|
||||||
}
|
}
|
||||||
|
impl Default for AudioState {
|
||||||
|
fn default() -> Self {
|
||||||
|
AudioState::Stopped
|
||||||
|
}
|
||||||
|
}
|
||||||
impl defmt::Format for AudioState {
|
impl defmt::Format for AudioState {
|
||||||
fn format(&self, fmt: defmt::Formatter) {
|
fn format(&self, fmt: defmt::Formatter) {
|
||||||
defmt::write!(
|
defmt::write!(
|
||||||
@@ -310,6 +350,7 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
|||||||
AudioState::NoData => self.nodata(),
|
AudioState::NoData => self.nodata(),
|
||||||
}
|
}
|
||||||
self.state.store(state, Ordering::SeqCst);
|
self.state.store(state, Ordering::SeqCst);
|
||||||
|
PERF.state.store(state, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self) {
|
fn init(&mut self) {
|
||||||
@@ -351,11 +392,15 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
|||||||
.datapause()
|
.datapause()
|
||||||
.normal()
|
.normal()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
|
||||||
self.dac.init();
|
self.dac.init();
|
||||||
}
|
}
|
||||||
///Transition -> Stopped:
|
///Transition -> Stopped:
|
||||||
///clear queue, mute DAC, mask I2S ISR, stop I2S peripheral, disable & reset feedback and performance queues
|
///clear queue, mute DAC, mask I2S ISR, stop I2S peripheral, disable & reset feedback and performance queues
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
|
// Disable FIFO error interrupt
|
||||||
|
self.i2s.i2s.fifointenclr.write(|w| w.txerr().set_bit());
|
||||||
dma_ring().stop();
|
dma_ring().stop();
|
||||||
pac::NVIC::mask(pac::Interrupt::DMA0);
|
pac::NVIC::mask(pac::Interrupt::DMA0);
|
||||||
self.dac.mute();
|
self.dac.mute();
|
||||||
@@ -399,6 +444,10 @@ impl<D: Dac<I>, I> Audio<'_, D, I> {
|
|||||||
.fifocfg
|
.fifocfg
|
||||||
.modify(|_, w| w.enabletx().enabled().dmatx().enabled());
|
.modify(|_, w| w.enabletx().enabled().dmatx().enabled());
|
||||||
dma_ring().run();
|
dma_ring().run();
|
||||||
|
// clear tx error status
|
||||||
|
self.i2s.i2s.fifostat.write(|w| w.txerr().set_bit());
|
||||||
|
// enable tx error interrupt
|
||||||
|
self.i2s.i2s.fifointenset.write(|w| w.txerr().enabled());
|
||||||
unsafe {
|
unsafe {
|
||||||
pac::NVIC::unmask(pac::Interrupt::DMA0);
|
pac::NVIC::unmask(pac::Interrupt::DMA0);
|
||||||
}
|
}
|
||||||
@@ -542,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);
|
PERF.queue_underflows.fetch_add(1, Ordering::Relaxed);
|
||||||
return Some(nominal_rate);
|
return Some(nominal_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
PERF.avg_fill
|
PERF.avg_fill
|
||||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| {
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |v| {
|
||||||
Some(((v << 6) - v + current_bytes as usize) >> 6)
|
Some(((v << 6) - v + current_bytes as usize) >> 6)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// normalize error wrt. frame size etc.
|
let raw_error = current_bytes - FILL_TARGET_BYTES;
|
||||||
let error_permille = ((current_bytes - FILL_TARGET_BYTES) * 1000) / 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 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
|
// 3. SEPARATE GAINS FOR P AND I
|
||||||
let max_allowed_deviation = nominal_v / 500;
|
// 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
|
PERF.p.store(p_term, Ordering::Relaxed);
|
||||||
let i_term = 0; // placeholder
|
PERF.i.store(i_term, Ordering::Relaxed);
|
||||||
|
|
||||||
let mut v = nominal_v + p_term + i_term;
|
let mut v = nominal_v + p_term + i_term;
|
||||||
v = v.clamp(
|
v = v.clamp(
|
||||||
nominal_v - max_allowed_deviation,
|
nominal_v - max_allowed_deviation,
|
||||||
nominal_v + max_allowed_deviation,
|
nominal_v + max_allowed_deviation,
|
||||||
);
|
);
|
||||||
|
PERF.fb.store(v, Ordering::Relaxed);
|
||||||
|
|
||||||
Some(UsbIsochronousFeedback::new(v as u32))
|
Some(UsbIsochronousFeedback::new(v as u32))
|
||||||
}
|
}
|
||||||
@@ -594,11 +658,6 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
|
|||||||
.od()
|
.od()
|
||||||
.normal()
|
.normal()
|
||||||
});
|
});
|
||||||
pac::SYSCON::ptr()
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.mclkio
|
|
||||||
.modify(|_, w| w.mclkio().input());
|
|
||||||
pac::SYSCON::ptr()
|
pac::SYSCON::ptr()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -606,6 +665,33 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
|
|||||||
.modify(|_, w| w.sel().enum_0x5()); // MCLK
|
.modify(|_, w| w.sel().enum_0x5()); // MCLK
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "evk"))]
|
||||||
|
unsafe {
|
||||||
|
pac::SYSCON::ptr()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.mclkio
|
||||||
|
.modify(|_, w| w.mclkio().input());
|
||||||
|
}
|
||||||
|
#[cfg(feature = "evk")]
|
||||||
|
unsafe {
|
||||||
|
pac::SYSCON::ptr()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.mclkclksel
|
||||||
|
.modify(|_, w| w.sel().enum_0x1()); // PLL0
|
||||||
|
pac::SYSCON::ptr()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.mclkdiv
|
||||||
|
.modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k
|
||||||
|
pac::SYSCON::ptr()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.mclkio
|
||||||
|
.modify(|_, w| w.mclkio().output());
|
||||||
|
}
|
||||||
|
|
||||||
// Select I2S TX function
|
// Select I2S TX function
|
||||||
fc7.pselid.write(|w| w.persel().i2s_transmit());
|
fc7.pselid.write(|w| w.persel().i2s_transmit());
|
||||||
|
|
||||||
@@ -629,10 +715,17 @@ fn main() -> ! {
|
|||||||
let usb0_vbus_pin = pins::Pio0_22::take()
|
let usb0_vbus_pin = pins::Pio0_22::take()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_usb0_vbus_pin(&mut iocon);
|
.into_usb0_vbus_pin(&mut iocon);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "evk"))]
|
||||||
let codec_i2c_pins = (
|
let codec_i2c_pins = (
|
||||||
pins::Pio0_16::take().unwrap().into_i2c4_scl_pin(&mut iocon),
|
pins::Pio0_16::take().unwrap().into_i2c4_scl_pin(&mut iocon),
|
||||||
pins::Pio0_5::take().unwrap().into_i2c4_sda_pin(&mut iocon),
|
pins::Pio0_5::take().unwrap().into_i2c4_sda_pin(&mut iocon),
|
||||||
);
|
);
|
||||||
|
#[cfg(feature = "evk")]
|
||||||
|
let codec_i2c_pins = (
|
||||||
|
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
|
||||||
|
pins::Pio1_21::take().unwrap().into_i2c4_sda_pin(&mut iocon),
|
||||||
|
);
|
||||||
let codec_i2s_pins = (
|
let codec_i2s_pins = (
|
||||||
pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon),
|
pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon),
|
||||||
pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon),
|
pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon),
|
||||||
@@ -674,6 +767,9 @@ fn main() -> ! {
|
|||||||
.configure(&mut anactrl, &mut pmc, &mut syscon)
|
.configure(&mut anactrl, &mut pmc, &mut syscon)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
hw::init_sys_pll1();
|
hw::init_sys_pll1();
|
||||||
|
#[cfg(feature = "evk")]
|
||||||
|
hw::init_audio_pll();
|
||||||
|
|
||||||
let mut delay_timer = Timer::new(
|
let mut delay_timer = Timer::new(
|
||||||
hal.ctimer
|
hal.ctimer
|
||||||
.0
|
.0
|
||||||
@@ -762,15 +858,15 @@ fn main() -> ! {
|
|||||||
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
||||||
|
|
||||||
move || {
|
move || {
|
||||||
let active = usb_dev.poll(&mut [&mut uac2, &mut hid]);
|
usb_dev.poll(&mut [&mut uac2, &mut hid]);
|
||||||
if active && 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) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(UsbError::WouldBlock) => {}
|
Err(UsbError::WouldBlock) => {}
|
||||||
Err(e) => defmt::error!("Failed to send HID report: {:?}", e),
|
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));
|
hid_update_timer.start(Microseconds::new(HID_INTERVAL_MS as u32 * 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
import struct
|
|
||||||
from time import sleep
|
|
||||||
from typing import Self
|
|
||||||
import hid
|
|
||||||
|
|
||||||
VID = 0x1209
|
|
||||||
PID = 0xCC1D
|
|
||||||
INTERVAL = 0.1
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AudioTelemetry:
|
|
||||||
STRUCT = "<LLLLL"
|
|
||||||
LEN = struct.calcsize(STRUCT)
|
|
||||||
|
|
||||||
average_buffer_fill: int
|
|
||||||
frame_count: int
|
|
||||||
dac_underflow_count: int
|
|
||||||
usb_underflow_count: int
|
|
||||||
dac_overflow_count: int
|
|
||||||
|
|
||||||
def from_bytes(b: bytes) -> Self:
|
|
||||||
if len(b) != AudioTelemetry.LEN:
|
|
||||||
raise ValueError(f"wrong size report ({len(b)} != {AudioTelemetry.LEN})")
|
|
||||||
fields = struct.unpack(AudioTelemetry.STRUCT, b)
|
|
||||||
return AudioTelemetry(*fields)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
with hid.Device(VID, PID) as h:
|
|
||||||
while True:
|
|
||||||
report = AudioTelemetry.from_bytes(h.read(AudioTelemetry.LEN))
|
|
||||||
print(f"{report}")
|
|
||||||
sleep(INTERVAL)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "guac-scripts"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Scripts to work with GUAC devices"
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = ">=3.12"
|
|
||||||
dependencies = [
|
|
||||||
"hid>=1.0.9",
|
|
||||||
"rich-click>=1.9.7",
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
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" }
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#![no_std]
|
||||||
|
|
||||||
|
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, 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 state: u8,
|
||||||
|
pub average_buffer_fill: u16,
|
||||||
|
pub frame_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,
|
||||||
|
}
|
||||||
|
}
|
||||||
-20
@@ -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,
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user