Compare commits

...

3 Commits

Author SHA1 Message Date
ktims 719d4391b8 examples: update README 2026-05-16 16:24:57 -07:00
ktims ffbdef1b6d lpc55s28-evk: add audio input 2026-05-16 16:07:45 -07:00
ktims fb050c4a40 usbd_uac2: fix clippies, add builder, support IN data 2026-05-16 16:07:05 -07:00
7 changed files with 480 additions and 225 deletions
+29 -21
View File
@@ -1,31 +1,46 @@
# usbd-uac2 Examples Two example backends are provided, both based on the LPCXpresso55S28 demo board, using the onboard WM8904 DAC.
This repository contains example implementations of a USB Audio Class 2 (UAC2) device. ## LPC55S28 (LPCXpresso55S28)
Two example backends are provided, both based on the LPCXpresso55S28 demo board, using the onboard WM8904 DAC: These examples work on the
(LPCXpresso55S28)[https://www.nxp.com/design/design-center/software/development-software/mcuxpresso-software-and-tools-/lpcxpresso-boards/lpcxpresso55s28-development-board:LPC55S28-EVK]
development board. They use the onboard WM8904 codec, so don't require any
additional hardware for audio I/O.
- **Interrupt-driven example** (`lpc55s28-evk`) They can be programmed and debugged using the CMSIS-DAP that's on the board,
- **DMA-based example** (`lpc55s28-evk-dma`) though due to the security features you may need to reset into the DFU
bootloader first.
```
cargo embed --release
```
## Examples Overview Enable logging with `DEFMT_LOG` when building, but beware that enabling defmt
logs can cause timing failures, and _must_ be drained by the host, as it is
blocking.
They work on either USBFS or USBHS, but not both at the same time. The default
is USBHS. If you would like to run the example on USBFS, disable default
features and select USBFS:
```
cargo embed --release --no-default-features --features usbfs
```
### Interrupt-driven (`lpc55s28-evk`) ### Interrupt-driven (`lpc55s28-evk`)
Running at 32bit/48khz. It can't keep up at 96khz. Works on USBFS and USBHS. Running at 32bit/48khz. Simultaneous input and output. Works on USBFS and USBHS.
This is a minimal implementation intended to demonstrate the fundamental 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, structure of the class driver. The architecture is not recommended for a production
and drains it into the I2S FIFO in the I2S interrupt. This requires a lot of device, but it does work reliably on the happy path.
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 This is intended primarily as a reference implementation to aid understanding of
the class driver.
### DMA-based (`lpc55s28-evk-dma`) ### DMA-based (`lpc55s28-evk-dma`)
Running at 32bit/96khz. Works on USBFS and USBHS. Running at 32bit/96khz. Output only. Works on USBFS and USBHS.
A more realistic and robust implementation using DMA. It fills a static ring 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, buffer as data comes in from USB, while the DMA chases it around the ring,
@@ -39,10 +54,3 @@ 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 implementation. Particularly, it behaves poorly in underrun, since the DMA will
keep emitting from the ring regardless of whether the data is valid, which keep emitting from the ring regardless of whether the data is valid, which
sounds terrible. sounds terrible.
## Running the Examples
You can flash and run either example using `cargo embed`:
```sh
cargo embed --release --example lpc55s28-evk --features usbfs
+98 -46
View File
@@ -10,6 +10,7 @@ use defmt::debug;
use hal::{ use hal::{
Enabled, Iocon, Pin, Enabled, Iocon, Pin,
drivers::pins, drivers::pins,
peripherals::syscon::{ClockControl, ResetControl},
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin}, traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio}, typestates::pin::{gpio::direction::Output, state::Gpio},
}; };
@@ -143,18 +144,35 @@ pub(crate) fn init_audio_pll() {
debug!("pll0 locked after {} tries", i); debug!("pll0 locked after {} tries", i);
} }
pub struct I2sTx { pub struct I2sHandles {
pub i2s: pac::I2S7, pub tx: pac::I2S7,
pub rx: pac::I2S6,
} }
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx { pub fn init_i2s(
fc7: pac::FLEXCOMM7,
i2s7: pac::I2S7,
fc6: pac::FLEXCOMM6,
i2s6: pac::I2S6,
syscon: &mut Syscon,
) -> I2sHandles {
defmt::debug!("init i2s"); defmt::debug!("init i2s");
// Enable BOTH // Enable BOTH
syscon.reset(&mut fc7); fc7.clear_reset(syscon);
syscon.enable_clock(&mut fc7); fc7.enable_clock(syscon);
fc6.clear_reset(syscon);
unsafe { fc6.enable_clock(syscon);
pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| { {
let sc = unsafe { pac::SYSCON::ptr().as_ref().unwrap() };
let ioc = unsafe { pac::IOCON::ptr().as_ref().unwrap() };
// MCLK source
//
sc.mclkclksel.write(|w| w.sel().enum_0x1()); // PLL0
// MCLK div
sc.mclkdiv
.write(|w| unsafe { w.div().bits(1).halt().run().reset().released() }); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k
// MCLK out config
ioc.pio1_31.modify(|_, w| {
w.func() w.func()
.alt1() .alt1()
.mode() .mode()
@@ -168,57 +186,65 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
.od() .od()
.normal() .normal()
}); });
pac::SYSCON::ptr() // FC7 clock
.as_ref() sc.fcclksel7().modify(|_, w| w.sel().enum_0x5()); // MCLK
.unwrap() // FC6 clock
.fcclksel7() sc.fcclksel6().modify(|_, w| w.sel().enum_0x5()); // MCLK
.modify(|_, w| w.sel().enum_0x5()); // MCLK // MCLK out
pac::SYSCON::ptr() sc.mclkio.modify(|_, w| w.mclkio().output());
.as_ref()
.unwrap() // Enable clock for sysctl, it's not mapped into Peripherals
.mclkclksel sc.ahbclkctrlset[2].write(|w| unsafe { w.bits(1 << 15) });
.modify(|_, w| w.sel().enum_0x1()); // PLL0 while sc.ahbclkctrl2.read().sysctl().is_disable() {}
pac::SYSCON::ptr() let sysctrl = unsafe { pac::SYSCTL::ptr().as_ref().unwrap() };
.as_ref() sysctrl.sharedctrlset[0].write(|w| w.sharedscksel().flexcomm7().sharedwssel().flexcomm7()); // FC7 drives shared SCK, WS
.unwrap() sysctrl.fcctrlsel[7].write(|w| {
.mclkdiv w.sckinsel()
.modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k .shared_set0_i2s_signals()
pac::SYSCON::ptr() .wsinsel()
.as_ref() .shared_set0_i2s_signals()
.unwrap() }); // FC7 uses shared set
.mclkio sysctrl.fcctrlsel[6].write(|w| {
.modify(|_, w| w.mclkio().output()); w.sckinsel()
}; .shared_set0_i2s_signals()
.wsinsel()
.shared_set0_i2s_signals()
});
// for _ in 0..1000 {
// cortex_m::asm::nop();
// }
}
// Select I2S TX function // Select I2S TX function
fc7.pselid.write(|w| w.persel().i2s_transmit()); fc7.pselid.write(|w| w.persel().i2s_transmit());
// Select I2S RX function
fc6.pselid.write(|w| w.persel().i2s_receive());
let regs = i2s7; let out_regs = i2s7;
let in_regs = i2s6;
// Enable TX FIFO only // Enable TX FIFO only
regs.fifocfg.modify(|_, w| { out_regs.fifocfg.write(|w| {
w.enabletx() w.enabletx()
.enabled() .enabled()
.enablerx() .txi2se0() // transmit 0s when empty - only supported option for 32b data
.disabled()
.dmatx()
.disabled()
.txi2se0()
.zero() .zero()
.emptytx() // reset the tx queue
.set_bit()
}); });
// Flush out_regs
regs.fifocfg.modify(|_, w| w.emptytx().set_bit()); .cfg2
.write(|w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
regs.cfg2
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16; let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16;
regs.div out_regs
.modify(|_, w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz .div
.write(|w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz
// Config // TX Config
regs.cfg1.modify(|_, w| unsafe { out_regs.cfg1.write(|w| unsafe {
w.mstslvcfg() w.mstslvcfg()
.normal_master() .normal_master()
.onechannel() .onechannel()
@@ -233,7 +259,33 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
.normal() .normal()
}); });
I2sTx { i2s: regs } // Enable RX FIFO only
in_regs
.fifocfg
.write(|w| w.enablerx().enabled().emptyrx().set_bit());
in_regs
.cfg2
.write(|w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
in_regs.div.write(|w| unsafe { w.div().bits(0) });
in_regs.cfg1.write(|w| unsafe {
w.mstslvcfg()
.normal_slave_mode()
.onechannel()
.dual_channel()
.datalen()
.bits(31)
.mainenable()
.enabled()
.mode()
.classic_mode()
.datapause()
.normal()
});
I2sHandles {
tx: out_regs,
rx: in_regs,
}
} }
pub struct SharedLed<T: OutputPin> { pub struct SharedLed<T: OutputPin> {
+244 -84
View File
@@ -3,12 +3,22 @@
//! This is a minimal implementation intended to demonstrate only the minimum //! This is a minimal implementation intended to demonstrate only the minimum
//! essential implementation, to clarify usage of the class driver. //! essential implementation, to clarify usage of the class driver.
//! //!
//! Uses the EVK's WM8904 DAC at 48KHz. Clock is generated by PLL0. Simple //! Uses the EVK's WM8904 CODEC at 48KHz. Clock is generated by PLL0. Simple
//! proportional feedback is implemented. //! proportional feedback is implemented.
//! //!
//! Packets from USB are placed in a `bbqueue`. They are consumed by the I2S //! Packets from USB are placed in a `bbqueue` (a lock free ring buffer). They
//! FIFO in the FLEXCOMM7 interrupt. This makes the implementation very sensitive //! are consumed by the I2S FIFO in the FLEXCOMM7 interrupt. This makes the
//! to interrupt::free critical sections (which are widely used in the USB bus driver) //! implementation very sensitive to interrupt::free critical sections (which
//! are widely used in the USB bus driver), but it works well enough at 48k.
//!
//! Audio frames from the CODEC are placed in a separate `bbqueue` from the
//! FLEXCOMM6 interrupt handler. They're drained out to the USB host in the USB
//! callback context.
//!
//! LEDs:
//! - Blue - audio under/overrun - note this toggles a bunch at playback stream startup
//! - Red - recording stream running
//! - Green - playback stream running
#![no_main] #![no_main]
#![no_std] #![no_std]
@@ -26,9 +36,9 @@ use bbqueue::{
prod_cons::stream::{StreamConsumer, StreamProducer}, prod_cons::stream::{StreamConsumer, StreamProducer},
traits::bbqhdl::BbqHandle, traits::bbqhdl::BbqHandle,
}; };
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use core::sync::atomic::{AtomicU32, Ordering};
use cortex_m_rt::entry; use cortex_m_rt::entry;
use defmt::debug; use defmt::{debug, error, info, warn};
use defmt_rtt as _; use defmt_rtt as _;
use hal::raw as pac; use hal::raw as pac;
use hal::{ use hal::{
@@ -41,18 +51,24 @@ use lpc55_hal as hal;
use pac::interrupt; use pac::interrupt;
use usb_device::{ use usb_device::{
bus::{self}, bus::{self},
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid}, device::{StringDescriptors, UsbVidPid},
}; };
use usbd_uac2::{ use usbd_uac2::{
self, AudioHandler, ClockSource, RangeEntry, TerminalConfig, UsbAudioClassConfig, self, AudioHandler, ClockSource, RangeEntry, TerminalConfig, UsbAudioClassConfig,
UsbIsochronousFeedback, UsbSpeed, constants::FunctionCode, descriptors::ClockType, UsbIsochronousFeedback, UsbSpeed, constants::FunctionCode, descriptors::ClockType,
}; };
use crate::hw::{I2sTx, blue_led, green_led, red_led}; use crate::hw::{I2sHandles, blue_led, green_led, red_led};
mod hw; mod hw;
mod wm8904; mod wm8904;
// pid.codes test IDs
const USB_VID: u16 = 0x1209;
const USB_PID: u16 = 0x0001;
const USB_MANUFACTURER: &str = "usbd_uac2";
const USB_PRODUCT: &str = "interrupt example device";
const CODEC_I2C_ADDR: u8 = 0b0011010; const CODEC_I2C_ADDR: u8 = 0b0011010;
const FIFO_LENGTH: usize = 256; // frames const FIFO_LENGTH: usize = 256; // frames
const MCLK_FREQ: u32 = 12288000; const MCLK_FREQ: u32 = 12288000;
@@ -62,18 +78,25 @@ const SAMPLE_RATE: u32 = 48000;
const USB_FRAME_RATE: usize = 8000; const USB_FRAME_RATE: usize = 8000;
#[cfg(feature = "usbfs")] #[cfg(feature = "usbfs")]
const USB_FRAME_RATE: usize = 1000; const USB_FRAME_RATE: usize = 1000;
type SampleType = (i32, i32);
const BYTES_PER_FRAME: usize = 8; const BYTES_PER_FRAME: usize = 8;
const QUEUE_BYTES: usize = FIFO_LENGTH * BYTES_PER_FRAME; const QUEUE_BYTES: usize = FIFO_LENGTH * BYTES_PER_FRAME;
// We use bbqueue here for performance in the USB driver that runs almost entirely in interrupt free critical section.
static QUEUE: Churrasco<QUEUE_BYTES> = Churrasco::new();
// Used for feedback calculation of current fifo state
static PRODUCED: AtomicU32 = AtomicU32::new(0);
static CONSUMED: AtomicU32 = AtomicU32::new(0);
static QUEUE_AUDIO_OUT: Churrasco<QUEUE_BYTES> = Churrasco::new();
// Used for feedback calculation of current fifo state
static PRODUCED_OUT: AtomicU32 = AtomicU32::new(0);
static CONSUMED_OUT: AtomicU32 = AtomicU32::new(0);
static QUEUE_AUDIO_IN: Churrasco<QUEUE_BYTES> = Churrasco::new();
// Used for feedback calculation of current fifo state
static PRODUCED_IN: AtomicU32 = AtomicU32::new(0);
static CONSUMED_IN: AtomicU32 = AtomicU32::new(0);
/// Consume one audio output frame and send it to the I2S FIFO. If there's
/// insufficient data in the queue, return false without consuming what was
/// there.
#[inline] #[inline]
fn try_write_one_frame<T: BbqHandle>( fn try_consume_one_frame<T: BbqHandle>(
cons: &mut StreamConsumer<T>, cons: &mut StreamConsumer<T>,
i2s: &pac::i2s7::RegisterBlock, i2s: &pac::i2s7::RegisterBlock,
) -> bool { ) -> bool {
@@ -87,7 +110,7 @@ fn try_write_one_frame<T: BbqHandle>(
// consume exactly one frame (8 bytes) // consume exactly one frame (8 bytes)
rgr.release(BYTES_PER_FRAME); rgr.release(BYTES_PER_FRAME);
CONSUMED.fetch_add(BYTES_PER_FRAME as u32, Ordering::Relaxed); CONSUMED_OUT.fetch_add(BYTES_PER_FRAME as u32, Ordering::Relaxed);
return true; return true;
} else { } else {
// Not enough bytes for a full frame: leave it in the queue. // Not enough bytes for a full frame: leave it in the queue.
@@ -97,16 +120,43 @@ fn try_write_one_frame<T: BbqHandle>(
false false
} }
/// Produce one audio input from from the I2S FIFO into the queue. If there's no
/// room in the queue, discard the sample and return false.
#[inline]
fn try_produce_one_frame<T: BbqHandle>(
prod: &mut StreamProducer<T>,
i2s: &pac::i2s6::RegisterBlock,
) -> bool {
let l = i2s.fiford.read().rxdata().bits();
let r = i2s.fiford.read().rxdata().bits();
if let Ok(mut rgr) = prod.grant_exact(BYTES_PER_FRAME) {
unsafe {
let wp = rgr.as_mut_ptr();
core::ptr::copy_nonoverlapping(&l as *const u32 as *const u8, wp, 4);
core::ptr::copy_nonoverlapping(&r as *const u32 as *const u8, wp.add(4), 4);
}
rgr.commit(BYTES_PER_FRAME * 2);
PRODUCED_IN.fetch_add(BYTES_PER_FRAME as u32, Ordering::Relaxed);
true
} else {
// No space in the queue, discard the sample
false
}
}
/// I2S TX ISR. Consume frames until the FIFO is full. Only write full frames
/// (L+R) so we can't desync left/right.
#[interrupt] #[interrupt]
fn FLEXCOMM7() { fn FLEXCOMM7() {
let i2s = unsafe { &*pac::I2S7::ptr() }; let i2s = unsafe { pac::I2S7::ptr().as_ref().unwrap() };
// refill until fifo has >6 words // refill until fifo has >6 words
let mut cons = QUEUE.stream_consumer(); let mut cons = QUEUE_AUDIO_OUT.stream_consumer();
while i2s.fifostat.read().txlvl().bits() <= 6 { while i2s.fifostat.read().txlvl().bits() <= 6 {
if !try_write_one_frame(&mut cons, i2s) { if !try_consume_one_frame(&mut cons, i2s) {
// No complete frame available: write silence to keep FIFO above threshold // No complete frame available: write silence to keep FIFO above threshold
defmt::error!("underflow"); // error!("tx queue underflow");
red_led().toggle(); blue_led().toggle();
i2s.fifowr.write(|w| unsafe { w.bits(0) }); i2s.fifowr.write(|w| unsafe { w.bits(0) });
i2s.fifowr.write(|w| unsafe { w.bits(0) }); i2s.fifowr.write(|w| unsafe { w.bits(0) });
break; break;
@@ -114,38 +164,83 @@ fn FLEXCOMM7() {
} }
} }
struct Audio<T: BbqHandle> { /// I2S RX ISR. Produce frames until the FIFO is empty (or has only a partial frame).
running: AtomicBool, #[interrupt]
i2s: I2sTx, fn FLEXCOMM6() {
producer: StreamProducer<T>, let i2s = unsafe { pac::I2S6::ptr().as_ref().unwrap() };
let mut prod = QUEUE_AUDIO_IN.stream_producer();
while i2s.fifostat.read().rxlvl().bits() >= 2 {
if !try_produce_one_frame(&mut prod, i2s) {
blue_led().toggle();
// error!("rx queue overflow");
break;
} }
impl<T: BbqHandle> Audio<T> {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
fn start(&self) {
self.running.store(true, Ordering::Relaxed);
defmt::info!("playback starting, enabling interrupts");
self.i2s.i2s.fifointenclr.write(|w| w.txlvl().set_bit());
// FIFO trigger threshold = <= 6 entries
self.i2s
.i2s
.fifotrig
.modify(|_, w| unsafe { w.txlvl().bits(6).txlvlena().enabled() });
// FIFO level interrupt enable
self.i2s.i2s.fifointenset.modify(|_, w| w.txlvl().enabled());
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
green_led().on();
}
fn stop(&self) {
// 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.
self.running.store(true, Ordering::Relaxed);
defmt::info!("playback stopped");
pac::NVIC::mask(pac::Interrupt::FLEXCOMM7);
green_led().off();
} }
} }
/// Storage for Audio Class implementation state
struct Audio<T: BbqHandle> {
i2s: I2sHandles,
producer: StreamProducer<T>,
consumer: StreamConsumer<T>,
}
impl<T: BbqHandle> Audio<T> {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
/// Start audio playback. Clear anything in the FIFO, enable interrupts.
fn start_audio_out(&self) {
info!("playback starting, enabling interrupts");
self.i2s.tx.fifocfg.modify(|_, w| w.emptytx().set_bit());
self.i2s.tx.fifointenclr.write(|w| w.txlvl().set_bit());
// TX FIFO trigger threshold = <= 6 entries
self.i2s
.tx
.fifotrig
.modify(|_, w| unsafe { w.txlvl().bits(6).txlvlena().enabled() });
// TX FIFO level interrupt enable
self.i2s.tx.fifointenset.write(|w| w.txlvl().enabled());
// enable interrupts
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
green_led().on();
}
/// Start audio recording. Clear the FIFO, enable interrupts, and enable the peripheral.
fn start_audio_in(&self) {
info!("recording starting, enabling interrupts");
self.i2s.rx.fifocfg.modify(|_, w| w.emptyrx().set_bit());
self.i2s.rx.fifointenclr.write(|w| w.rxlvl().set_bit());
// RX FIFO trigger threshold = >= 2 entries
self.i2s
.rx
.fifotrig
.modify(|_, w| unsafe { w.rxlvl().bits(1).rxlvlena().enabled() }); // 1 = generate when 2 pieces of data are in fifo
// RX FIFO level interrupt enable
self.i2s.rx.fifointenset.write(|w| w.rxlvl().enabled());
// Start the peripheral
self.i2s.rx.cfg1.modify(|_, w| w.mainenable().enabled());
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM6) };
red_led().on();
}
/// Stop audio playback. Disable interrupts, but leave the peripheral
/// running. It is responsible for clocks, so is needed to clock the CODEC
/// and input I2S. Leaving it running continuously is not harmful as the tx
/// FIFO will empty and then emit 0s to the DAC.
fn stop_audio_out(&self) {
pac::NVIC::mask(pac::Interrupt::FLEXCOMM7);
info!("playback stopped");
green_led().off();
}
/// Stop audio recording. Disable the peripheral and mask any interrupts.
fn stop_audio_in(&self) {
self.i2s.rx.cfg1.modify(|_, w| w.mainenable().disabled());
pac::NVIC::mask(pac::Interrupt::FLEXCOMM6);
info!("recording stopped");
red_led().off();
}
}
/// Implement a fixed internal clock offering a single fixed audio rate, which is always valid.
impl<T: BbqHandle> ClockSource for Audio<T> { impl<T: BbqHandle> ClockSource for Audio<T> {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed; const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false; const SOF_SYNC: bool = false;
@@ -161,15 +256,24 @@ impl<T: BbqHandle> ClockSource for Audio<T> {
Ok(true) Ok(true)
} }
} }
/// Implement the main audio handlers. This is a very naive implementation that
/// doesn't handle edge cases properly. For example, while altSetting=1
/// indicates the host expects to be able to emit audio data to the endpoint, it
/// does not indicate that the stream has started.
impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> { impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> {
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) { /// On an alt setting change, start or stop the related audio pipeline. This
// alt setting 0 means stopped /// behaviour should be replaced by a state machine with at least an 'armed
match alt_setting { /// but no data yet' state for playback.
0 => self.stop(), fn alternate_setting_changed(&mut self, terminal: usb_device::UsbDirection, alt_setting: u8) {
1 => self.start(), match (terminal, alt_setting) {
_ => defmt::error!("unexpected alt setting {}", alt_setting), (usb_device::UsbDirection::Out, 0) => self.stop_audio_out(),
(usb_device::UsbDirection::Out, 1) => self.start_audio_out(),
(usb_device::UsbDirection::In, 0) => self.stop_audio_in(),
(usb_device::UsbDirection::In, 1) => self.start_audio_in(),
_ => error!("unexpected alt setting {} on {}", alt_setting, terminal),
} }
} }
/// Receive audio data from USB. If we can, queue it into the output queue which will be drained by the ISR.
fn audio_data_rx( fn audio_data_rx(
&mut self, &mut self,
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>, ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
@@ -177,12 +281,11 @@ impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> {
// Buffer must fit one microframe's (125us @ HS, 1ms @ FS) of audio data // Buffer must fit one microframe's (125us @ HS, 1ms @ FS) of audio data
// (based on how `usbd_uac2` sets up the descriptors), calculate that // (based on how `usbd_uac2` sets up the descriptors), calculate that
// size here. // size here.
let mut buf = let mut buf = [0; (SAMPLE_RATE as usize / USB_FRAME_RATE + 1) * BYTES_PER_FRAME];
[0; (SAMPLE_RATE as usize / USB_FRAME_RATE + 1) * core::mem::size_of::<SampleType>()];
let len = match ep.read(&mut buf) { let len = match ep.read(&mut buf) {
Ok(len) => len, Ok(len) => len,
Err(_) => { Err(_) => {
defmt::error!("usb error in rx callback"); error!("usb error in rx callback");
return; return;
} }
}; };
@@ -191,21 +294,82 @@ impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> {
if let Ok(mut wg) = self.producer.grant_exact(buf.len()) { if let Ok(mut wg) = self.producer.grant_exact(buf.len()) {
wg.copy_from_slice(buf); wg.copy_from_slice(buf);
wg.commit(buf.len()); wg.commit(buf.len());
PRODUCED.fetch_add(buf.len() as u32, Ordering::Relaxed); PRODUCED_OUT.fetch_add(buf.len() as u32, Ordering::Relaxed);
} else { } else {
blue_led().on(); blue_led().on();
defmt::error!("overflowed bbq, asked {}", buf.len()); error!("overflowed bbq, asked {}", buf.len());
}
}
/// Transmit audio data to USB. If we have data in the queue, write it to
/// the endpoint in appropriately sized microframes
fn audio_data_tx(
&mut self,
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::In>,
) {
const NOMINAL_SAMPLES: usize = SAMPLE_RATE as usize / USB_FRAME_RATE;
const DRAINING: i32 = ((FIFO_LENGTH * 4) / 10) as i32;
const FILLING: i32 = ((FIFO_LENGTH * 6) / 10) as i32;
// Allocate space for one additional audio frame, in case the host is
// running slower than us and we need to occasionally stuff an extra
// frame.
let mut buf = [0; (NOMINAL_SAMPLES + 1) * BYTES_PER_FRAME];
let fill = PRODUCED_IN
.load(Ordering::Acquire)
.wrapping_sub(CONSUMED_IN.load(Ordering::Acquire)) as i32
/ BYTES_PER_FRAME as i32;
// Decide how many samples to write based on fill, this is how we deal with clock slippage.
// Draining (<40%) -> 1 fewer
// Filling (>50%) -> 1 more
// Nominal -> Nominal
let samples = match fill {
f if f < DRAINING => NOMINAL_SAMPLES - 1,
f if f > FILLING => NOMINAL_SAMPLES + 1,
_ => NOMINAL_SAMPLES,
};
let mut written = 0;
let mut woff = 0usize;
while written < samples {
let Ok(rgr) = self.consumer.read() else {
// warn!("underflowed IN queue, wrote {} of {}", written, samples);
break;
};
let available_frames = rgr.len() / BYTES_PER_FRAME;
if available_frames == 0 {
break;
}
let to_copy = (samples - written).min(available_frames);
let bytes = to_copy * BYTES_PER_FRAME;
buf[woff..woff + bytes].copy_from_slice(&rgr[..bytes]);
woff += bytes;
rgr.release(bytes);
written += to_copy;
CONSUMED_IN.fetch_add(to_copy as u32, Ordering::Relaxed);
}
if let Err(e) = ep.write(&buf[..written * BYTES_PER_FRAME]) {
warn!("Error writing to IN EP: {:?}", e);
} }
} }
/// Provide rate feedback to the host, so that it doesn't over- or underflow /// Provide rate feedback to the host, so that it doesn't over- or underflow
/// our queue. This implementation is not tuned. /// our queue. This implementation is not tuned and without an I term is
/// pretty unstable, but works fine.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> { fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
let target = FIFO_LENGTH as i32 / 2; let target = FIFO_LENGTH as i32 / 2;
let fill = PRODUCED let fill = PRODUCED_OUT
.load(Ordering::Acquire) .load(Ordering::Acquire)
.wrapping_sub(CONSUMED.load(Ordering::Acquire)) as i32 .wrapping_sub(CONSUMED_OUT.load(Ordering::Acquire)) as i32
/ BYTES_PER_FRAME as i32; / BYTES_PER_FRAME as i32;
let error = fill - target; let error = fill - target;
@@ -224,7 +388,7 @@ impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> {
v = v.clamp(nominal_v - (1 << 14), nominal_v + (1 << 14)); v = v.clamp(nominal_v - (1 << 14), nominal_v + (1 << 14));
// Note: this log will cause continuous underflows // Note: this log will cause continuous underflows
defmt::debug!("fill:{} err:{} fb:{=u32:x}", fill, error, v as u32); debug!("fill:{} err:{} fb:{=u32:x}", fill, error, v as u32);
Some(UsbIsochronousFeedback::new(v as u32)) Some(UsbIsochronousFeedback::new(v as u32))
} }
@@ -253,11 +417,12 @@ fn main() -> ! {
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon), pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
pins::Pio1_21::take().unwrap().into_i2c4_sda_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 the variables do not get used
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),
pins::Pio0_19::take().unwrap().into_i2s7_ws_pin(&mut iocon), pins::Pio0_19::take().unwrap().into_i2s7_ws_pin(&mut iocon),
pins::Pio1_13::take().unwrap().into_i2s6_sda_pin(&mut iocon),
pins::Pio1_31::take().unwrap(), // MCLK pins::Pio1_31::take().unwrap(), // MCLK
); );
@@ -287,10 +452,13 @@ fn main() -> ! {
); );
let i2s_peripheral = { let i2s_peripheral = {
let fc6 = hal.flexcomm.6.release();
let fc7 = hal.flexcomm.7.release(); let fc7 = hal.flexcomm.7.release();
hw::init_i2s(fc7.0, fc7.2, &mut syscon)
hw::init_i2s(fc7.0, fc7.2, fc6.0, fc6.2, &mut syscon)
}; };
debug!("usb");
#[cfg(feature = "usbhs")] #[cfg(feature = "usbhs")]
let (usb_speed, usb_peripheral) = ( let (usb_speed, usb_peripheral) = (
UsbSpeed::High, UsbSpeed::High,
@@ -312,41 +480,33 @@ fn main() -> ! {
clocks.support_usbfs_token().unwrap(), clocks.support_usbfs_token().unwrap(),
), ),
); );
let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin); let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin);
defmt::debug!("codec init"); debug!("codec init");
wm8904::init_codec(&mut i2c_bus); wm8904::init_codec(&mut i2c_bus);
// let consumer_ref = FIFO_CONSUMER_STORE.init(consumer);
// unsafe { FIFO_CONSUMER = consumer_ref as *mut _ };
let mut audio = Audio { let mut audio = Audio {
i2s: i2s_peripheral, i2s: i2s_peripheral,
producer: QUEUE.stream_producer(), producer: QUEUE_AUDIO_OUT.stream_producer(),
running: AtomicBool::new(false), consumer: QUEUE_AUDIO_IN.stream_consumer(),
}; };
let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio) let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
.with_output_config(TerminalConfig::builder().base_id(2).build()); .with_output_config(TerminalConfig::builder().base_id(2).build())
.with_input_config(TerminalConfig::builder().base_id(4).build());
let mut uac2 = config.build(&usb_bus).unwrap(); let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d)) let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(USB_VID, USB_PID))
.composite_with_iads()
.strings(&[StringDescriptors::default() .strings(&[StringDescriptors::default()
.manufacturer("Generic") .manufacturer(USB_MANUFACTURER)
.product("usbd_uac2 device") .product(USB_PRODUCT)])
.serial_number("123456789")])
.unwrap() .unwrap()
.max_packet_size_0(64) .max_packet_size_0(64) // Required to be 64 on HS, allowed on FS
.unwrap() .unwrap()
.device_class(0xef)
.device_sub_class(0x02)
.device_protocol(0x01)
.build(); .build();
defmt::info!("main loop"); debug!("main loop");
loop { loop {
usb_dev.poll(&mut [&mut uac2]); usb_dev.poll(&mut [&mut uac2]);
+14 -8
View File
@@ -1,7 +1,7 @@
use cortex_m::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead}; use cortex_m::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead};
use defmt::warn; use defmt::warn;
use crate::{CODEC_I2C_ADDR, MCLK_FREQ, SAMPLE_RATE, SampleType}; use crate::{BYTES_PER_FRAME, CODEC_I2C_ADDR, MCLK_FREQ, SAMPLE_RATE};
// copied from NXP SDK WM8904_Init // copied from NXP SDK WM8904_Init
pub(crate) fn init_codec<T>(i2c: &mut T) pub(crate) fn init_codec<T>(i2c: &mut T)
@@ -31,26 +31,29 @@ where
} }
defmt::debug!("[codec] write seq done"); defmt::debug!("[codec] write seq done");
i2c.write(CODEC_I2C_ADDR, &[0x14, 0x00, 0x00]).ok(); // clock rates 0 i2c.write(CODEC_I2C_ADDR, &[0x14, 0x00, 0x00]).ok(); // clock rates 0
i2c.write(CODEC_I2C_ADDR, &[0x0c, 0x00, 0x00]).ok(); // power management 0 = IN PGAs disabled i2c.write(CODEC_I2C_ADDR, &[0x05, 0x00, 0x03]).ok(); // vmid control 0 = normal | enabled
i2c.write(CODEC_I2C_ADDR, &[0x0c, 0x00, 0x03]).ok(); // power management 0 = INL_ENA | INR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0e, 0x00, 0x03]).ok(); // power management 2 = HPL_PGA_ENA | HPR_PGA_ENA i2c.write(CODEC_I2C_ADDR, &[0x0e, 0x00, 0x03]).ok(); // power management 2 = HPL_PGA_ENA | HPR_PGA_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0f, 0x00, 0x00]).ok(); // power management 3 = line outs disabled i2c.write(CODEC_I2C_ADDR, &[0x0f, 0x00, 0x00]).ok(); // power management 3 = line outs disabled
i2c.write(CODEC_I2C_ADDR, &[0x12, 0x00, 0x0c]).ok(); // power management 6 = DACL_ENA | DACR_ENA i2c.write(CODEC_I2C_ADDR, &[0x12, 0x00, 0x0f]).ok(); // power management 6 = DACL_ENA | DACR_ENA | ADCL_ENA | ADCR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0a, 0x00, 0x00]).ok(); // analog adc 0 = ADC_OSR128 i2c.write(CODEC_I2C_ADDR, &[0x0a, 0x00, 0x01]).ok(); // analog adc 0 = ADC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x18, 0x00, 0x50]).ok(); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC i2c.write(CODEC_I2C_ADDR, &[0x18, 0x00, 0x50]).ok(); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC
i2c.write(CODEC_I2C_ADDR, &[0x21, 0x00, 0x40]).ok(); // dac digital 1 = DAC_OSR128 i2c.write(CODEC_I2C_ADDR, &[0x21, 0x00, 0x40]).ok(); // dac digital 1 = DAC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // analog lin 0 = 0dB (unmute) i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // analog lin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // analog rin 0 = 0dB (unmute) i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // analog rin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x2e, 0x00, 1 << 4]).ok(); // analog lin 1 = single ended, inv_input = in2l
i2c.write(CODEC_I2C_ADDR, &[0x2f, 0x00, 1 << 4]).ok(); // analog rin 1 = single ended, inv_input = in2r
i2c.write(CODEC_I2C_ADDR, &[0x39, 0x00, 0x39]).ok(); // analog out1 left = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x39, 0x00, 0x39]).ok(); // analog out1 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3a, 0x00, 0x39]).ok(); // analog out1 right = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x3a, 0x00, 0x39]).ok(); // analog out1 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3b, 0x00, 0x39]).ok(); // analog out2 left = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x3b, 0x00, 0x39]).ok(); // analog out2 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3c, 0x00, 0x39]).ok(); // analog out2 right = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x3c, 0x00, 0x39]).ok(); // analog out2 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x43, 0x00, 0x03]).ok(); // dc server 0 = HPOUTL_ENA | HPOUTR_ENA i2c.write(CODEC_I2C_ADDR, &[0x43, 0x00, 0x03]).ok(); // dc servo 0 = HPOUTL_ENA | HPOUTR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x5a, 0x00, 0xff]).ok(); // analog hp 0 = remove all shorts etc i2c.write(CODEC_I2C_ADDR, &[0x5a, 0x00, 0xff]).ok(); // analog hp 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x5e, 0x00, 0xff]).ok(); // analog lineout 0 = remove all shorts etc i2c.write(CODEC_I2C_ADDR, &[0x5e, 0x00, 0xff]).ok(); // analog lineout 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x68, 0x00, 0x01]).ok(); // enable class w charge pump i2c.write(CODEC_I2C_ADDR, &[0x68, 0x00, 0x01]).ok(); // enable class w charge pump
i2c.write(CODEC_I2C_ADDR, &[0x62, 0x00, 0x01]).ok(); // enable charge pump i2c.write(CODEC_I2C_ADDR, &[0x62, 0x00, 0x01]).ok(); // enable charge pump
let aif_wl = match core::mem::size_of::<SampleType>() { let aif_wl = match BYTES_PER_FRAME {
4 => 0, // 16 bits per sample 4 => 0, // 16 bits per sample
8 => 3, // 32 bits per sample 8 => 3, // 32 bits per sample
_ => { _ => {
@@ -98,7 +101,7 @@ where
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = CLK_SYS_ENA i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = CLK_SYS_ENA
// Calculate bclk_div // Calculate bclk_div
let bits_per_frame = core::mem::size_of::<SampleType>() * 8; let bits_per_frame = BYTES_PER_FRAME * 8;
let bits_per_second = bits_per_frame as u32 * SAMPLE_RATE; let bits_per_second = bits_per_frame as u32 * SAMPLE_RATE;
let bclk_div = MCLK_FREQ / bits_per_second; let bclk_div = MCLK_FREQ / bits_per_second;
i2c.write(CODEC_I2C_ADDR, &[0x1a, 0x00, bclk_div as u8]) i2c.write(CODEC_I2C_ADDR, &[0x1a, 0x00, bclk_div as u8])
@@ -106,5 +109,8 @@ where
i2c.write(CODEC_I2C_ADDR, &[0x1b, 0x00, 0x00]).ok(); // audio interface 3 = input lrclock i2c.write(CODEC_I2C_ADDR, &[0x1b, 0x00, 0x00]).ok(); // audio interface 3 = input lrclock
i2c.write(CODEC_I2C_ADDR, &[0x3d, 0x00, 0x00]).ok(); // analog out12 zc = play source = dac i2c.write(CODEC_I2C_ADDR, &[0x3d, 0x00, 0x00]).ok(); // analog out12 zc = play source = dac
i2c.write(CODEC_I2C_ADDR, &[0x1e, 0x01, 0xff]).ok(); // dac vol left = update left/right = 0dB i2c.write(CODEC_I2C_ADDR, &[0x1e, 0x01, 0xc0]).ok(); // dac vol left = update left/right = 0dB
i2c.write(CODEC_I2C_ADDR, &[0x24, 0x01, 0xc0]).ok(); // adc vol left = update left/right = 0dB
i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // pga gain = 0dB
i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // pga gain = 0dB
} }
+4 -4
View File
@@ -195,7 +195,7 @@ impl Descriptor for ClockSource {
writer.write_u8(self.bm_attributes())?; // bmAttributes writer.write_u8(self.bm_attributes())?; // bmAttributes
writer.write_u8(self.bm_controls())?; // bmControls writer.write_u8(self.bm_controls())?; // bmControls
writer.write_u8(self.assoc_terminal)?; // bAssocTerminal writer.write_u8(self.assoc_terminal)?; // bAssocTerminal
writer.write_u8(self.string.map_or(0, |n| u8::from(n)))?; // iClockSource writer.write_u8(self.string.map_or(0, u8::from))?; // iClockSource
Ok(()) Ok(())
} }
} }
@@ -247,7 +247,7 @@ impl Descriptor for InputTerminal {
| ((self.overflow_control as u8) << 2) | ((self.overflow_control as u8) << 2)
| ((self.phantom_power_control as u8) << 4), | ((self.phantom_power_control as u8) << 4),
)?; )?;
writer.write_u8(self.string.map_or(0, |s| u8::from(s)))?; writer.write_u8(self.string.map_or(0, u8::from))?;
Ok(()) Ok(())
} }
} }
@@ -289,7 +289,7 @@ impl Descriptor for OutputTerminal {
| ((self.underflow_control as u8) << 6), | ((self.underflow_control as u8) << 6),
)?; )?;
writer.write_u8(self.overflow_control as u8)?; writer.write_u8(self.overflow_control as u8)?;
writer.write_u8(self.string.map_or(0, |n| u8::from(n)))?; // iTerminal writer.write_u8(self.string.map_or(0, u8::from))?; // iTerminal
Ok(()) Ok(())
} }
} }
@@ -839,7 +839,7 @@ impl Descriptor for AudioStreamingInterface {
writer.write_u32::<LittleEndian>(self.format_bitmap as u32)?; writer.write_u32::<LittleEndian>(self.format_bitmap as u32)?;
writer.write_u8(self.num_channels)?; writer.write_u8(self.num_channels)?;
writer.write(&self.channel_config.bytes)?; writer.write(&self.channel_config.bytes)?;
writer.write_u8(self.string.map_or(0, |s| u8::from(s)))?; writer.write_u8(self.string.map_or(0, u8::from))?;
Ok(()) Ok(())
} }
} }
+89 -61
View File
@@ -16,7 +16,7 @@ use log::*;
use num_traits::{ConstZero, ToPrimitive}; use num_traits::{ConstZero, ToPrimitive};
use usb_device::control::{Recipient, Request, RequestType}; use usb_device::control::{Recipient, Request, RequestType};
use usb_device::device::DEFAULT_ALTERNATE_SETTING; use usb_device::device::{DEFAULT_ALTERNATE_SETTING, UsbDeviceBuilder, UsbVidPid};
use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out}; use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out};
use usb_device::{UsbDirection, class_prelude::*}; use usb_device::{UsbDirection, class_prelude::*};
@@ -63,7 +63,7 @@ impl RangeType for u32 {}
/// Represent a range request/response. /// Represent a range request/response.
/// ///
/// ref: UAC2 5.2.2 /// ref: UAC2 5.2.2
#[derive(PartialEq, Eq, Ord)] #[derive(PartialEq, Eq)]
pub struct RangeEntry<T: RangeType> { pub struct RangeEntry<T: RangeType> {
pub min: T, pub min: T,
pub max: T, pub max: T,
@@ -93,10 +93,14 @@ impl<T: RangeType> RangeEntry<T> {
// The spec guarantees that ranges do not overlap, so compare by min is correct. // The spec guarantees that ranges do not overlap, so compare by min is correct.
impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> { impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.min.partial_cmp(&other.min) Some(self.cmp(other))
}
}
impl<T: RangeType + PartialOrd> Ord for RangeEntry<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.min.cmp(&other.min)
} }
} }
/// Represent Isochronous feedback as integer and fractional parts, internally as 16.16. /// Represent Isochronous feedback as integer and fractional parts, internally as 16.16.
/// ///
/// Provides conversions to and from the different USB formats. /// Provides conversions to and from the different USB formats.
@@ -146,6 +150,10 @@ pub trait AudioHandler<'a, B: UsbBus> {
/// `ep.read()`. /// `ep.read()`.
fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>); fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>);
/// Called when we must produce audio data for the host. `ep` is ready for
/// `ep.write()`.
fn audio_data_tx(&mut self, ep: &Endpoint<'a, B, endpoint::In>);
/// Called when it's time to send an isochronous feedback update. Should /// Called when it's time to send an isochronous feedback update. Should
/// return the correct feedback payload. Feedback always runs at 1ms (in /// return the correct feedback payload. Feedback always runs at 1ms (in
/// this implementation), and will be passed the nominal frame size. /// this implementation), and will be passed the nominal frame size.
@@ -183,6 +191,7 @@ pub trait ClockSource {
fn sample_rate(&self) -> u32; fn sample_rate(&self) -> u32;
/// Called when the host requests to set the sample rate. Not necessarily called at all startups, /// Called when the host requests to set the sample rate. Not necessarily called at all startups,
/// so alt_setting should start/stop the clock. Not required for 'fixed' clocks. /// so alt_setting should start/stop the clock. Not required for 'fixed' clocks.
#[allow(unused_variables)]
fn set_sample_rate( fn set_sample_rate(
&mut self, &mut self,
sample_rate: u32, sample_rate: u32,
@@ -239,7 +248,7 @@ pub trait ClockSource {
}; };
let cs = descriptors::ClockSource { let cs = descriptors::ClockSource {
id: id, id,
clock_type: Self::CLOCK_TYPE, clock_type: Self::CLOCK_TYPE,
sof_sync: Self::SOF_SYNC, sof_sync: Self::SOF_SYNC,
frequency_access, frequency_access,
@@ -367,6 +376,7 @@ pub struct TerminalConfig<D: EndpointDirection> {
} }
impl<D: EndpointDirection> TerminalConfig<D> { impl<D: EndpointDirection> TerminalConfig<D> {
#[allow(clippy::too_many_arguments)]
fn new( fn new(
base_id: u8, base_id: u8,
clock_source_id: u8, clock_source_id: u8,
@@ -400,7 +410,7 @@ impl<D: EndpointDirection> TerminalConfig<D> {
TerminalConfigBuilder::new() TerminalConfigBuilder::new()
} }
} }
impl<'a> TerminalConfigurationDescriptors for TerminalConfig<Out> { impl TerminalConfigurationDescriptors for TerminalConfig<Out> {
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) { fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) {
let input_terminal = InputTerminal { let input_terminal = InputTerminal {
id: self.base_id, id: self.base_id,
@@ -437,7 +447,7 @@ impl<'a> TerminalConfigurationDescriptors for TerminalConfig<Out> {
// fn get_interface_descriptor(&self, id: InterfaceIndex) ) // fn get_interface_descriptor(&self, id: InterfaceIndex) )
} }
impl<'a> TerminalConfigurationDescriptors for TerminalConfig<In> { impl TerminalConfigurationDescriptors for TerminalConfig<In> {
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) { fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) {
let output_terminal = OutputTerminal { let output_terminal = OutputTerminal {
id: self.base_id, id: self.base_id,
@@ -838,7 +848,7 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbClass<B>
// UAC2 4.7 // UAC2 4.7
if let Some(descs) = additional_descs { if let Some(descs) = additional_descs {
for desc in descs.into_iter() { for desc in descs.iter() {
desc.write_descriptor(writer)?; desc.write_descriptor(writer)?;
} }
} }
@@ -893,10 +903,14 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbClass<B>
fn endpoint_in_complete(&mut self, addr: EndpointAddress) { fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
debug!("EP {} IN complete", addr); debug!("EP {} IN complete", addr);
if let Some(fb_ep) = self.feedback.as_ref() if let Some(_fb_ep) = self.feedback.as_ref()
&& addr.index() == self.fb_ep && addr.index() == self.fb_ep
{ {
self.emit_feedback(); self.emit_feedback();
} else if let Some(in_ep) = self.input.as_ref()
&& addr.index() == self.in_ep
{
self.audio_impl.audio_data_tx(&in_ep.endpoint);
} else { } else {
debug!(" unexpected IN on {}", addr); debug!(" unexpected IN on {}", addr);
} }
@@ -978,11 +992,11 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
fn class_request_out(&mut self, xfer: ControlOut<B>) { fn class_request_out(&mut self, xfer: ControlOut<B>) {
let req = xfer.request(); let req = xfer.request();
match (req.recipient, req.request.try_into()) { match (req.recipient, req.request.into()) {
(Recipient::Interface, Ok(ClassSpecificRequest::Cur)) => self.set_interface_cur(xfer), (Recipient::Interface, ClassSpecificRequest::Cur) => self.set_interface_cur(xfer),
(Recipient::Interface, Ok(ClassSpecificRequest::Range)) => {} (Recipient::Interface, ClassSpecificRequest::Range) => {}
(Recipient::Endpoint, Ok(ClassSpecificRequest::Cur)) => self.set_endpoint_cur(xfer), (Recipient::Endpoint, ClassSpecificRequest::Cur) => self.set_endpoint_cur(xfer),
(Recipient::Endpoint, Ok(ClassSpecificRequest::Range)) => {} (Recipient::Endpoint, ClassSpecificRequest::Range) => {}
_ => { _ => {
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -992,13 +1006,11 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
fn class_request_in(&mut self, xfer: ControlIn<B>) { fn class_request_in(&mut self, xfer: ControlIn<B>) {
let req = xfer.request(); let req = xfer.request();
match (req.recipient, req.request.try_into()) { match (req.recipient, req.request.into()) {
(Recipient::Interface, Ok(ClassSpecificRequest::Cur)) => self.get_interface_cur(xfer), (Recipient::Interface, ClassSpecificRequest::Cur) => self.get_interface_cur(xfer),
(Recipient::Interface, Ok(ClassSpecificRequest::Range)) => { (Recipient::Interface, ClassSpecificRequest::Range) => self.get_interface_range(xfer),
self.get_interface_range(xfer) (Recipient::Endpoint, ClassSpecificRequest::Cur) => self.get_endpoint_cur(xfer),
} (Recipient::Endpoint, ClassSpecificRequest::Range) => self.get_endpoint_range(xfer),
(Recipient::Endpoint, Ok(ClassSpecificRequest::Cur)) => self.get_endpoint_cur(xfer),
(Recipient::Endpoint, Ok(ClassSpecificRequest::Range)) => self.get_endpoint_range(xfer),
_ => { _ => {
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -1012,12 +1024,16 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
debug!(" SET_ALT_INTERFACE {} {}", iface, alt_setting); debug!(" SET_ALT_INTERFACE {} {}", iface, alt_setting);
if self.input.is_some() && iface == self.in_iface { if let Some(input) = &mut self.input
let old_alt = self.input.as_ref().unwrap().alt_setting; && iface == self.in_iface
{
let old_alt = input.alt_setting;
if old_alt != alt_setting { if old_alt != alt_setting {
self.audio_impl self.audio_impl
.alternate_setting_changed(UsbDirection::In, alt_setting); .alternate_setting_changed(UsbDirection::In, alt_setting);
self.input.as_mut().unwrap().alt_setting = alt_setting; input.alt_setting = alt_setting;
// Start the IN cycle
self.audio_impl.audio_data_tx(&input.endpoint);
xfer.accept().ok(); xfer.accept().ok();
} }
} else if self.output.is_some() && iface == self.out_iface { } else if self.output.is_some() && iface == self.out_iface {
@@ -1041,13 +1057,15 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
let req = xfer.request(); let req = xfer.request();
let iface = req.index as u8; let iface = req.index as u8;
debug!(" GET_ALT_INTERFACE {}", iface); debug!(" GET_ALT_INTERFACE {}", iface);
if self.input.is_some() && iface == self.in_iface { if let Some(input) = self.input.as_ref()
xfer.accept_with(&[self.input.as_ref().unwrap().alt_setting]) && iface == self.in_iface
.ok(); {
xfer.accept_with(&[input.alt_setting]).ok();
return; return;
} else if self.output.is_some() && iface == self.out_iface { } else if let Some(output) = self.output.as_ref()
xfer.accept_with(&[self.output.as_ref().unwrap().alt_setting]) && iface == self.out_iface
.ok(); {
xfer.accept_with(&[output.alt_setting]).ok();
return; return;
} }
debug!(" Unimplemented."); debug!(" Unimplemented.");
@@ -1109,9 +1127,8 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
channel: u8, channel: u8,
control: u8, control: u8,
) { ) {
match entity { if entity == 1 {
1 => return self.get_clock_cur(xfer, channel, control), return self.get_clock_cur(xfer, channel, control);
_ => {}
} }
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -1123,49 +1140,48 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
channel: u8, channel: u8,
control: u8, control: u8,
) { ) {
match entity { if entity == 1 {
1 => return self.set_clock_cur(xfer, channel, control), return self.set_clock_cur(xfer, channel, control);
_ => {}
} }
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn set_streaming_interface_cur( fn set_streaming_interface_cur(
&mut self, &mut self,
xfer: ControlOut<B>, _xfer: ControlOut<B>,
direction: UsbDirection, _direction: UsbDirection,
entity: u8, _entity: u8,
channel: u8, _channel: u8,
control: u8, _control: u8,
) { ) {
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn get_endpoint_cur(&mut self, xfer: ControlIn<B>) { fn get_endpoint_cur(&mut self, xfer: ControlIn<B>) {
let req = xfer.request(); let req = xfer.request();
let entity = (req.index >> 8) as u8; let _entity = (req.index >> 8) as u8;
let interface = (req.index & 0xff) as u8; let _interface = (req.index & 0xff) as u8;
let control = (req.value >> 8) as u8; let _control = (req.value >> 8) as u8;
let channel = (req.value & 0xff) as u8; let _channel = (req.value & 0xff) as u8;
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn get_endpoint_range(&mut self, xfer: ControlIn<B>) { fn get_endpoint_range(&mut self, xfer: ControlIn<B>) {
let req = xfer.request(); let req = xfer.request();
let entity = (req.index >> 8) as u8; let _entity = (req.index >> 8) as u8;
let interface = (req.index & 0xff) as u8; let _interface = (req.index & 0xff) as u8;
let control = (req.value >> 8) as u8; let _control = (req.value >> 8) as u8;
let channel = (req.value & 0xff) as u8; let _channel = (req.value & 0xff) as u8;
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn set_endpoint_cur(&mut self, xfer: ControlOut<B>) { fn set_endpoint_cur(&mut self, xfer: ControlOut<B>) {
let req = xfer.request(); let req = xfer.request();
let entity = (req.index >> 8) as u8; let _entity = (req.index >> 8) as u8;
let interface = (req.index & 0xff) as u8; let _interface = (req.index & 0xff) as u8;
let control = (req.value >> 8) as u8; let _control = (req.value >> 8) as u8;
let channel = (req.value & 0xff) as u8; let _channel = (req.value & 0xff) as u8;
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -1203,8 +1219,8 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
} }
} }
fn get_clock_cur(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) { fn get_clock_cur(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) {
match control.try_into() { match control.into() {
Ok(ClockSourceControlSelector::SamFreqControl) => { ClockSourceControlSelector::SamFreqControl => {
debug!(" SamplingFreqControl"); debug!(" SamplingFreqControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1222,7 +1238,7 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
}) })
.ok(); .ok();
} }
Ok(ClockSourceControlSelector::ClockValidControl) => { ClockSourceControlSelector::ClockValidControl => {
debug!(" ClockValidControl"); debug!(" ClockValidControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1248,8 +1264,8 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
} }
} }
fn set_clock_cur(&mut self, xfer: ControlOut<B>, channel: u8, control: u8) { fn set_clock_cur(&mut self, xfer: ControlOut<B>, channel: u8, control: u8) {
match control.try_into() { match control.into() {
Ok(ClockSourceControlSelector::SamFreqControl) => { ClockSourceControlSelector::SamFreqControl => {
debug!(" SamplingFreqControl"); debug!(" SamplingFreqControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1266,7 +1282,7 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
); );
xfer.accept().ok(); xfer.accept().ok();
} }
Err(e) => { Err(_e) => {
error!(" SET SamplingFreqControl CUR ERROR BAD DATA"); error!(" SET SamplingFreqControl CUR ERROR BAD DATA");
} }
} }
@@ -1277,8 +1293,8 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
} }
} }
fn get_clock_range(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) { fn get_clock_range(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) {
match control.try_into() { match control.into() {
Ok(ClockSourceControlSelector::SamFreqControl) => { ClockSourceControlSelector::SamFreqControl => {
debug!(" SamplingFreqControl"); debug!(" SamplingFreqControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1321,3 +1337,15 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
} }
} }
} }
/// Set up a `UsbDeviceBuilder` with the device IDs for Usb Audio Class 2.0
pub fn builder<'a, B: UsbBus>(
alloc: &'a UsbBusAllocator<B>,
vid_pid: UsbVidPid,
) -> UsbDeviceBuilder<'a, B> {
UsbDeviceBuilder::new(alloc, vid_pid)
.composite_with_iads() // required by UAC2 4.6
.device_class(0xef) // required by UAC2 4.2
.device_sub_class(0x02) // required by UAC2 4.2
.device_protocol(0x01) // required by UAC2 4.2
}
+1
View File
@@ -1,4 +1,5 @@
// src/log.rs (or log/mod.rs) // src/log.rs (or log/mod.rs)
#[allow(unused_imports)]
#[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
pub use defmt::{debug, error, info, trace, warn}; pub use defmt::{debug, error, info, trace, warn};