Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
719d4391b8
|
|||
|
ffbdef1b6d
|
|||
|
fb050c4a40
|
+29
-21
@@ -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
|
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,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};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user