factor into workspace, improve features & deps

This commit is contained in:
2026-05-15 18:06:30 -07:00
parent 08b1d10a92
commit 24e0083090
15 changed files with 1032 additions and 220 deletions
+48
View File
@@ -0,0 +1,48 @@
# usbd-uac2 Examples
This repository contains example implementations of a USB Audio Class 2 (UAC2) device.
Two example backends are provided, both based on the LPCXpresso55S28 demo board, using the onboard WM8904 DAC:
- **Interrupt-driven example** (`lpc55s28-evk`)
- **DMA-based example** (`lpc55s28-evk-dma`)
## Examples Overview
### Interrupt-driven (`lpc55s28-evk`)
Running at 32bit/48khz. It can't keep up at 96khz. Works on USBFS and USBHS.
This is a minimal implementation intended to demonstrate the fundamental
structure of the class driver. It fills a `bbqueue` as data comes in from USB,
and drains it into the I2S FIFO in the I2S interrupt. This requires a lot of
time-critical CPU work managing buffers. Particularly, the USB peripheral driver
uses a lot of interrupt-free critical sections which can cause late interrupts
and underruns.
This is intended primarily as a learning/reference implementation
### DMA-based (`lpc55s28-evk-dma`)
Running at 32bit/96khz. Works on USBFS and USBHS.
A more realistic and robust implementation using DMA. It fills a static ring
buffer as data comes in from USB, while the DMA chases it around the ring,
draining into the TX FIFO. This is efficient and decouples interrupt latency
from data delivery. It uses the DMA interrupt to track consumed slots, but as
long as the USB doesn't catch up to the read slot before this happens, there is
a lot of slack for other things to be happening.
This is a more useful demonstration, but is still lacking correct handling of
edge cases, error conditions and so on you would want in a fully fleshed out
implementation. Particularly, it behaves poorly in underrun, since the DMA will
keep emitting from the ring regardless of whether the data is valid, which
sounds terrible.
## Running the Examples
You can flash and run either example using `cargo embed`:
```sh
cargo embed --release --example lpc55s28-evk --features usbfs
+1 -7
View File
@@ -313,6 +313,7 @@ dependencies = [
[[package]]
name = "lpc55-hal"
version = "0.5.0"
source = "git+https://github.com/ktims/lpc55-hal?branch=main#8dfefd62aff4abd2de535f23107812dda68437be"
dependencies = [
"block-buffer",
"cipher",
@@ -353,7 +354,6 @@ dependencies = [
"log-to-defmt",
"lpc55-hal",
"nb 1.1.0",
"panic-halt",
"panic-probe",
"static_cell",
"usb-device",
@@ -458,12 +458,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "panic-halt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
[[package]]
name = "panic-probe"
version = "1.0.0"
+5 -4
View File
@@ -2,6 +2,7 @@
name = "lpc55s28-evk-dma"
version = "0.1.0"
edition = "2024"
publish = false
[features]
default = ["usbhs"]
@@ -16,13 +17,13 @@ defmt-rtt = "1.1.0"
embedded-hal = "1.0.0"
embedded-io = "0.7.1"
log-to-defmt = "0.1.0"
lpc55-hal = { version = "0.5.0", path = "../lpc55-hal" }
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 = "0.3"
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]}
# 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" }
usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] }
[profile.release]
opt-level = "z"
+9
View File
@@ -0,0 +1,9 @@
[default.general]
chip = "LPC55S28JBD100"
[default.rtt]
enabled = true
[default.gdb]
enabled = true
[debug.rtt]
enabled = false
+5
View File
@@ -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);
}
+28 -4
View File
@@ -233,13 +233,37 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
pub fn produced(&self) -> usize {
self.produced.load(Ordering::Acquire)
}
pub fn produced_bytes(&self) -> usize {
self.produced_bytes.load(Ordering::Acquire)
}
pub fn consumed(&self) -> usize {
self.consumed.load(Ordering::Acquire)
}
pub fn consumed_bytes(&self) -> usize {
self.consumed.load(Ordering::Acquire) * self.slot_bytes
+ (self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff)
loop {
let consumed_start = self.consumed.load(Ordering::Acquire);
let reg_1 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
let reg_2 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
let consumed_end = self.consumed.load(Ordering::Acquire);
if consumed_start == consumed_end && reg_1 == reg_2 {
// 1. Map the hardware remaining countdown into a clean byte count
let remaining_bytes = if reg_1 == 0x3ff {
0 // 0x3FF means all transfers completed, 0 bytes remaining
} else {
// Formula from NXP manual: (XFERCOUNT + 1) * Data Width
(reg_1 + 1) * self.word_bytes
};
// 2. Total bytes consumed in this specific active slot
let active_slot_consumed = self.slot_bytes - remaining_bytes;
// 3. Combine with your software index history accumulator
return consumed_start * self.slot_bytes + active_slot_consumed;
}
}
}
pub fn fill_slots(&self) -> usize {
@@ -319,7 +343,7 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
let slots = unsafe { &mut *self.slots.get() };
let desc = unsafe { &mut *self.desc.get() };
let chan_desc = unsafe { &mut *self.channel_desc.get() };
defmt::info!("slots base: &{:x}", self.slots.get());
defmt::debug!("slots base: &{:x}", self.slots.get());
// Pre-fill with silence so underrun replays silence.
for i in 0..N {
+9 -68
View File
@@ -1,22 +1,19 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::hal;
use core::cell::{OnceCell, UnsafeCell};
use core::mem::MaybeUninit;
use core::ptr::null_mut;
use crate::Syscon;
use crate::hal;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use defmt::debug;
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use defmt::{debug, info};
use hal::{
Iocon, Pin,
Enabled, Iocon, Pin,
drivers::pins,
prelude::*,
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio},
};
use lpc55_hal::Enabled;
use static_cell::StaticCell;
pub(crate) struct PllConstants {
pub m: u16, // 1-65535
pub n: u8, // 1-255
@@ -138,67 +135,12 @@ pub(crate) fn init_audio_pll() {
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
debug!("pll0 wait for lock");
info!("pll0 wait for lock");
let mut i = 0usize;
while syscon.pll0stat.read().lock().bit_is_clear() {
i += 1;
}
debug!("pll0 locked after {} tries", i);
}
const SYS_PLL: PllConstants = PllConstants::new(4, 75, 1); // 150MHz
pub(crate) fn init_sys_pll1() {
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 pll1: {}", SYS_PLL);
pmc.pdruncfg0.modify(|_, w| w.pden_pll1().poweredoff());
syscon.pll1clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll1ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(SYS_PLL.seli)
.selp()
.bits(SYS_PLL.selp)
});
syscon
.pll1ndec
.write(|w| unsafe { w.ndiv().bits(SYS_PLL.n) });
syscon.pll1ndec.write(|w| unsafe {
w.ndiv().bits(SYS_PLL.n).nreq().set_bit() // latch
});
syscon
.pll1mdec
.write(|w| unsafe { w.mdiv().bits(SYS_PLL.m) });
syscon
.pll1pdec
.write(|w| unsafe { w.pdiv().bits(SYS_PLL.p) });
syscon.pll1pdec.write(|w| unsafe {
w.pdiv().bits(SYS_PLL.p).preq().set_bit() // latch
});
pmc.pdruncfg0.modify(|_, w| w.pden_pll1().poweredon());
debug!("pll1 wait for lock");
let mut i = 0usize;
while syscon.pll1stat.read().lock().bit_is_clear() {
i += 1;
}
debug!("pll1 locked after {} tries", i);
// switch system clock to pll1
syscon.fmccr.modify(|_, w| w.flashtim().flashtim11());
syscon.mainclkselb.modify(|_, w| w.sel().enum_0x2()); // pll1
info!("pll0 locked after {} loops", i);
}
pub struct I2sTx {
@@ -207,7 +149,6 @@ pub struct I2sTx {
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx {
defmt::debug!("init i2s");
// Enable BOTH
syscon.reset(&mut fc7);
syscon.enable_clock(&mut fc7);
+98 -97
View File
@@ -1,10 +1,15 @@
//! Interrupt driven example for the LPCXpresso55S28 demo board
//! DMA based audio output example for the LPCXpresso55S28 demo board
//!
//! Uses the onboard WM8904 DAC at 48KHz. Clock is generated by PLL0. Simple PI feedback
//! is implemented.
//! Uses the onboard WM8904 DAC at 96KHz. Clock is generated by PLL0. Simple proportional feedback is implemented.
//!
//! Packets from USB are placed a `heapless::spsc::Queue`. They are consumed
//! by the I2S FIFO in the FLEXCOMM7 interrupt.
//! USB walks around a static ring of slots, filling them as data comes in from
//! the host. DMA chases it, filling the I2S FIFO as it drains to the DAC.
//! Feedback ensures that the host doesn't overrun or underrun the ring.
//!
//! This implementation is more suitable for real use than the interrupt-based
//! example, but it is still missing many niceties and behaves worse in
//! anomalous situations since the DMA just keeps trucking over the ring
//! regardless of the data validity.
#![no_main]
#![no_std]
@@ -34,13 +39,11 @@ use static_cell::StaticCell;
use usb_device::{
bus::{self},
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid},
endpoint::IsochronousSynchronizationType,
};
use usbd_uac2::UsbIsochronousFeedback;
use usbd_uac2::TerminalConfig;
use usbd_uac2::{
self, AudioClassConfig, RangeEntry, TerminalConfig, UsbAudioClass, UsbAudioClockImpl, UsbSpeed,
constants::{FunctionCode, TerminalType},
descriptors::{ChannelConfig, ClockType, FormatType1, LockDelay},
self, AudioHandler, ClockSource, RangeEntry, UsbAudioClassConfig, UsbIsochronousFeedback,
UsbSpeed, constants::FunctionCode, descriptors::ClockType,
};
use crate::dma::DmaRing;
@@ -52,41 +55,27 @@ mod wm8904;
const CODEC_I2C_ADDR: u8 = 0b0011010;
const MCLK_FREQ: u32 = 12288000;
const SAMPLE_RATE: u32 = 96000;
const USB_FRAME_RATE: u32 = if cfg!(feature = "usbhs") { 8000 } else { 1000 };
//latency ≈ (current_fill × FRAMES_PER_SLOT)
// + FRAMES_PER_SLOT/2 - average DMA transfer position
// + 8 - FIFO depth @ 32-bit samples
// with example values, ~2.3ms
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 SLOT_SIZE_BYTES: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME; // run the DMA at 2khz
const N_SLOTS: usize = 32;
const FILL_TARGET: i32 = (FRAMES_PER_SLOT * N_SLOTS) as i32 / 2;
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;
struct Clock {}
impl Clock {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
}
impl UsbAudioClockImpl for Clock {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false;
fn get_sample_rate(&self) -> u32 {
Clock::RATES[0].min
}
fn get_rates(
&self,
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
Ok(&Clock::RATES)
}
fn get_clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true)
}
}
const LOG_PERIOD: u32 = 1000;
static DMA_RING: StaticCell<DmaRing<N_SLOTS, SLOT_SIZE_BYTES>> = StaticCell::new();
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, SLOT_SIZE_BYTES>> = None;
static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, BYTES_PER_SLOT>> = None;
#[inline]
fn dma_ring() -> &'static DmaRing<N_SLOTS, SLOT_SIZE_BYTES> {
fn dma_ring() -> &'static DmaRing<N_SLOTS, BYTES_PER_SLOT> {
unsafe { DMA_RING_REF.unwrap() }
}
@@ -125,13 +114,15 @@ struct Audio<'a, const N: usize, const MAX_SLOT_BYTES: usize> {
running: AtomicBool,
i2s: I2sTx,
dma: &'a DmaRing<N, MAX_SLOT_BYTES>,
log_counter: u32,
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> {
fn start(&self) {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
fn start(&mut self) {
red_led().off(); // clear any dma error
self.running.store(false, Ordering::Relaxed);
defmt::info!("playback starting (DMA)");
defmt::info!("playback armed (DMA)");
let i2s = &self.i2s.i2s;
i2s.fifotrig
@@ -150,15 +141,15 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> {
// If we don't disable interrupts while stopped, we will underflow constantly and continuously refill the fifo with 0s
// We could actually stop the I2S here, but sometimes that makes the DAC misbehave. The peripheral is configured to send
// 0s when the FIFO is empty, so this is fine.
pac::NVIC::mask(pac::Interrupt::DMA0);
self.running.store(false, Ordering::Relaxed);
dma_ring().stop();
defmt::info!("playback stopped");
pac::NVIC::mask(pac::Interrupt::DMA0);
green_led().off();
blue_led().off();
}
}
impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<'_, B>
impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> AudioHandler<'_, B>
for Audio<'_, N, MAX_SLOT_BYTES>
{
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) {
@@ -174,7 +165,8 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
) {
// Buffer must fit 125us of audio data (based on how `usbd_uac2` sets up the descriptors).
let mut buf = [0; SAMPLE_RATE.div_ceil(8000) as usize * BYTES_PER_FRAME];
// Buffer must have room for one additional frame in case the host clock runs slower than the device.
let mut buf = [0; (SAMPLE_RATE.div_ceil(USB_FRAME_RATE) + 1) as usize * BYTES_PER_FRAME];
let len = match ep.read(&mut buf) {
Ok(len) => len,
@@ -199,8 +191,8 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<
}
// If we're not running yet, wait until we reach 50% full then enable DMA requests
if !self.running.load(Ordering::Relaxed) && self.dma.fill_slots() >= (N_SLOTS / 2) {
defmt::debug!(
"buffer has {} slots, start dma transfers",
defmt::info!(
"buffer warmed ({} slots) starting playback",
self.dma.fill_slots()
);
self.dma.run();
@@ -208,52 +200,76 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<
}
}
/// Provide rate feedback to the host, so that it doesn't over- or underflow
/// the buffer. Proportional-only control is stable with normal hosts,
/// adding an I term with proper tuning (quite weak) would stabilize the
/// rate reported to the host but is not necessary for basic playback.
/// Provide rate feedback to the host. P-only is stable and works fine, with
/// most hosts. The host can either filter it internally or treat it
/// instantaneously and send more data specifically when the error gets
/// large; we will absorb reasonable clock drifts with our ring buffer.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
const MAX_CORRECTION: i32 = 1 << 10; // ~1.6%
// Don't want to signal an absurd rate when not consuming; let the
// buffer fill before starting feedback.
if !self.running.load(Ordering::Acquire) {
return Some(nominal_rate);
}
let produced_bytes = self.dma.produced_bytes.load(Ordering::Acquire);
let produced_bytes = self.dma.produced_bytes();
let consumed_bytes = self.dma.consumed_bytes();
let valid = produced_bytes >= consumed_bytes; // else we are in underrun condition
if produced_bytes < consumed_bytes || produced_bytes == 0 {
defmt::error!("[fb] dma underrun detected!");
red_led().on();
return Some(nominal_rate);
}
let fill_frames = if valid {
(produced_bytes - consumed_bytes) as i32 / BYTES_PER_FRAME as i32
} else {
// we will emit a canonical error in the DMA ISR
defmt::debug!("[fb] dma underrun detected");
0
};
let mut error = fill_frames - FILL_TARGET;
error = error.clamp(-32, 32); // avoid huge spikes
let p = error * 256;
let i = 0; // placeholder
let correction = -(p + i);
let current_bytes = (produced_bytes - consumed_bytes) as i32;
// normalize error wrt. frame size etc.
let error_permille = ((current_bytes - FILL_TARGET_BYTES) * 1000) / FILL_TARGET_BYTES;
let nominal_v = nominal_rate.to_u32_12_13() as i32;
let mut v = nominal_v + correction;
// 0.2% which is a huge clock error
let max_allowed_deviation = nominal_v / 500;
v = v.clamp(nominal_v - MAX_CORRECTION, nominal_v + MAX_CORRECTION);
let p_term = -(error_permille * nominal_v) / 256000; // this works reasonably well to keep the buffer
let i_term = 0; // placeholder
defmt::debug!(
"valid:{} fill:{} err:{} fb:{=u32:x}",
valid,
fill_frames,
error,
v as u32
let mut v = nominal_v + p_term + i_term;
v = v.clamp(
nominal_v - max_allowed_deviation,
nominal_v + max_allowed_deviation,
);
self.log_counter += 1;
if self.log_counter.is_multiple_of(LOG_PERIOD) {
defmt::info!(
"fill:{}% err_pm:{} p:{} i:{} fb_delta:{} fb:{=u32:x}",
(current_bytes * 100) / (N_SLOTS * BYTES_PER_SLOT) as i32,
error_permille,
p_term,
i_term,
v - nominal_v,
v as u32
);
}
Some(UsbIsochronousFeedback::new(v as u32))
}
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> ClockSource for Audio<'_, N, MAX_SLOT_BYTES> {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false;
fn sample_rate(&self) -> u32 {
Self::RATES[0].min
}
fn sample_rates(
&self,
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
Ok(&Self::RATES)
}
fn clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true)
}
}
#[entry]
fn main() -> ! {
let hal = hal::new();
@@ -277,7 +293,7 @@ fn main() -> ! {
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
pins::Pio1_21::take().unwrap().into_i2c4_sda_pin(&mut iocon),
);
// We can initialize and iocon these, but there is no peripheral, so they do not get used
// We can initialize and iocon these, but there is no peripheral driver, so they do not get used
let _codec_i2s_pins = (
pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon),
pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon),
@@ -286,18 +302,16 @@ fn main() -> ! {
);
debug!("clocks");
// Run the system clock at 96MHz. The lpc55-hal will run it from the FRO. But we won't actually use these clocks, we just need the guards...
let clocks = hal::ClockRequirements::default()
.system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon)
.unwrap();
let mut _delay_timer = Timer::new(
let mut usb_delay_timer = Timer::new(
hal.ctimer
.0
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
);
// Start PLL1 at 150MHz as main system clock
hw::init_sys_pll1();
// Start PLL0 at 24.576MHz as the audio clock. The FRO cannot evenly divide
// any common audio frequencies and is not particularly stable anyway.
hw::init_audio_pll();
@@ -326,7 +340,7 @@ fn main() -> ! {
&mut anactrl,
&mut pmc,
&mut syscon,
&mut _delay_timer,
&mut usb_delay_timer,
clocks.support_usbhs_token().unwrap(),
),
);
@@ -348,34 +362,21 @@ fn main() -> ! {
defmt::debug!("dma init");
let i2s_dma_addr = &i2s_peripheral.i2s.fifowr as *const _ as *mut u32;
let dma = DmaRing::<32, SLOT_SIZE_BYTES>::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4)
.unwrap();
let dma =
DmaRing::<N_SLOTS, BYTES_PER_SLOT>::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4)
.unwrap();
let dma_ref = DMA_RING.init(dma);
unsafe { DMA_RING_REF = Some(dma_ref) };
let mut clock = Clock {};
let mut audio = Audio {
i2s: i2s_peripheral,
dma: dma_ring(),
running: AtomicBool::new(false),
log_counter: 0,
};
defmt::debug!("usb init");
let config = AudioClassConfig::new(usb_speed, FunctionCode::Other, &mut clock, &mut audio)
.with_output_config(TerminalConfig::new(
4,
1,
2,
FormatType1 {
bit_resolution: 32,
bytes_per_sample: 4,
},
TerminalType::ExtLineConnector,
ChannelConfig::default_chans(2),
IsochronousSynchronizationType::Asynchronous,
LockDelay::Milliseconds(10),
None,
));
let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
.with_output_config(TerminalConfig::builder().base_id(2).build());
let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d))
+1 -18
View File
@@ -411,6 +411,7 @@ dependencies = [
[[package]]
name = "lpc55-hal"
version = "0.5.0"
source = "git+https://github.com/ktims/lpc55-hal?branch=main#8dfefd62aff4abd2de535f23107812dda68437be"
dependencies = [
"block-buffer",
"cipher",
@@ -451,10 +452,7 @@ dependencies = [
"embedded-io",
"log-to-defmt",
"lpc55-hal",
"nb 1.1.0",
"panic-halt",
"panic-probe",
"static_cell",
"usb-device",
"usbd-uac2",
]
@@ -615,12 +613,6 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "panic-halt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
[[package]]
name = "panic-probe"
version = "1.0.0"
@@ -825,15 +817,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "static_cell"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
dependencies = [
"portable-atomic",
]
[[package]]
name = "syn"
version = "2.0.117"
+5 -6
View File
@@ -2,6 +2,7 @@
name = "lpc55s28-evk"
version = "0.1.0"
edition = "2024"
publish = false
[features]
default = ["usbhs"]
@@ -17,13 +18,11 @@ defmt-rtt = "1.1.0"
embedded-hal = "1.0.0"
embedded-io = "0.7.1"
log-to-defmt = "0.1.0"
lpc55-hal = { version = "0.5.0", path = "../lpc55-hal" }
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 = "0.3"
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]}
# 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" }
usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] }
[profile.release]
opt-level = "z"
+5
View File
@@ -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);
}
+1 -1
View File
@@ -224,7 +224,7 @@ impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> {
v = v.clamp(nominal_v - (1 << 14), nominal_v + (1 << 14));
// Note: this log will cause continuous underflows
defmt::debug!("fill:{} err:{} fb:{=u32:x}", fill, error, v);
defmt::debug!("fill:{} err:{} fb:{=u32:x}", fill, error, v as u32);
Some(UsbIsochronousFeedback::new(v as u32))
}