lpc55s28-evk: add audio input
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user