diff --git a/examples/lpc55s28-evk/src/hw.rs b/examples/lpc55s28-evk/src/hw.rs index 6f2a694..834a371 100644 --- a/examples/lpc55s28-evk/src/hw.rs +++ b/examples/lpc55s28-evk/src/hw.rs @@ -10,6 +10,7 @@ use defmt::debug; use hal::{ Enabled, Iocon, Pin, drivers::pins, + peripherals::syscon::{ClockControl, ResetControl}, traits::wg::digital::v2::{OutputPin, ToggleableOutputPin}, typestates::pin::{gpio::direction::Output, state::Gpio}, }; @@ -143,18 +144,35 @@ pub(crate) fn init_audio_pll() { debug!("pll0 locked after {} tries", i); } -pub struct I2sTx { - pub i2s: pac::I2S7, +pub struct I2sHandles { + 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"); // Enable BOTH - syscon.reset(&mut fc7); - syscon.enable_clock(&mut fc7); - - unsafe { - pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| { + fc7.clear_reset(syscon); + fc7.enable_clock(syscon); + fc6.clear_reset(syscon); + fc6.enable_clock(syscon); + { + 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() .alt1() .mode() @@ -168,57 +186,65 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) - .od() .normal() }); - pac::SYSCON::ptr() - .as_ref() - .unwrap() - .fcclksel7() - .modify(|_, w| w.sel().enum_0x5()); // MCLK - pac::SYSCON::ptr() - .as_ref() - .unwrap() - .mclkclksel - .modify(|_, w| w.sel().enum_0x1()); // PLL0 - pac::SYSCON::ptr() - .as_ref() - .unwrap() - .mclkdiv - .modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k - pac::SYSCON::ptr() - .as_ref() - .unwrap() - .mclkio - .modify(|_, w| w.mclkio().output()); - }; + // FC7 clock + sc.fcclksel7().modify(|_, w| w.sel().enum_0x5()); // MCLK + // FC6 clock + sc.fcclksel6().modify(|_, w| w.sel().enum_0x5()); // MCLK + // MCLK out + sc.mclkio.modify(|_, w| w.mclkio().output()); + + // Enable clock for sysctl, it's not mapped into Peripherals + sc.ahbclkctrlset[2].write(|w| unsafe { w.bits(1 << 15) }); + while sc.ahbclkctrl2.read().sysctl().is_disable() {} + let sysctrl = unsafe { pac::SYSCTL::ptr().as_ref().unwrap() }; + sysctrl.sharedctrlset[0].write(|w| w.sharedscksel().flexcomm7().sharedwssel().flexcomm7()); // FC7 drives shared SCK, WS + sysctrl.fcctrlsel[7].write(|w| { + w.sckinsel() + .shared_set0_i2s_signals() + .wsinsel() + .shared_set0_i2s_signals() + }); // FC7 uses shared set + sysctrl.fcctrlsel[6].write(|w| { + w.sckinsel() + .shared_set0_i2s_signals() + .wsinsel() + .shared_set0_i2s_signals() + }); + + // for _ in 0..1000 { + // cortex_m::asm::nop(); + // } + } // Select I2S TX function 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 - regs.fifocfg.modify(|_, w| { + out_regs.fifocfg.write(|w| { w.enabletx() .enabled() - .enablerx() - .disabled() - .dmatx() - .disabled() - .txi2se0() + .txi2se0() // transmit 0s when empty - only supported option for 32b data .zero() + .emptytx() // reset the tx queue + .set_bit() }); - // Flush - regs.fifocfg.modify(|_, w| w.emptytx().set_bit()); - - regs.cfg2 - .modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64 + out_regs + .cfg2 + .write(|w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64 let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16; - regs.div - .modify(|_, w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz + out_regs + .div + .write(|w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz - // Config - regs.cfg1.modify(|_, w| unsafe { + // TX Config + out_regs.cfg1.write(|w| unsafe { w.mstslvcfg() .normal_master() .onechannel() @@ -233,7 +259,33 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) - .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 { diff --git a/examples/lpc55s28-evk/src/main.rs b/examples/lpc55s28-evk/src/main.rs index 1ad70ca..549da9a 100644 --- a/examples/lpc55s28-evk/src/main.rs +++ b/examples/lpc55s28-evk/src/main.rs @@ -3,12 +3,22 @@ //! This is a minimal implementation intended to demonstrate only the minimum //! 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. //! -//! Packets from USB are placed in a `bbqueue`. They are consumed by the I2S -//! FIFO in the FLEXCOMM7 interrupt. This makes the implementation very sensitive -//! to interrupt::free critical sections (which are widely used in the USB bus driver) +//! Packets from USB are placed in a `bbqueue` (a lock free ring buffer). They +//! are consumed by the I2S FIFO in the FLEXCOMM7 interrupt. This makes the +//! 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_std] @@ -26,9 +36,9 @@ use bbqueue::{ prod_cons::stream::{StreamConsumer, StreamProducer}, traits::bbqhdl::BbqHandle, }; -use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use core::sync::atomic::{AtomicU32, Ordering}; use cortex_m_rt::entry; -use defmt::debug; +use defmt::{debug, error, info, warn}; use defmt_rtt as _; use hal::raw as pac; use hal::{ @@ -41,18 +51,24 @@ use lpc55_hal as hal; use pac::interrupt; use usb_device::{ bus::{self}, - device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid}, + device::{StringDescriptors, UsbVidPid}, }; use usbd_uac2::{ self, AudioHandler, ClockSource, RangeEntry, TerminalConfig, UsbAudioClassConfig, 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 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 FIFO_LENGTH: usize = 256; // frames const MCLK_FREQ: u32 = 12288000; @@ -62,18 +78,25 @@ const SAMPLE_RATE: u32 = 48000; const USB_FRAME_RATE: usize = 8000; #[cfg(feature = "usbfs")] const USB_FRAME_RATE: usize = 1000; -type SampleType = (i32, i32); const BYTES_PER_FRAME: usize = 8; 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 = 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 = 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 = 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] -fn try_write_one_frame( +fn try_consume_one_frame( cons: &mut StreamConsumer, i2s: &pac::i2s7::RegisterBlock, ) -> bool { @@ -87,7 +110,7 @@ fn try_write_one_frame( // consume exactly one frame (8 bytes) 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; } else { // Not enough bytes for a full frame: leave it in the queue. @@ -97,16 +120,43 @@ fn try_write_one_frame( 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( + prod: &mut StreamProducer, + 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] fn FLEXCOMM7() { - let i2s = unsafe { &*pac::I2S7::ptr() }; + let i2s = unsafe { pac::I2S7::ptr().as_ref().unwrap() }; // 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 { - 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 - defmt::error!("underflow"); - red_led().toggle(); + // error!("tx queue underflow"); + blue_led().toggle(); i2s.fifowr.write(|w| unsafe { w.bits(0) }); i2s.fifowr.write(|w| unsafe { w.bits(0) }); break; @@ -114,38 +164,83 @@ fn FLEXCOMM7() { } } -struct Audio { - running: AtomicBool, - i2s: I2sTx, - producer: StreamProducer, -} -impl Audio { - const RATES: [RangeEntry; 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(); +/// I2S RX ISR. Produce frames until the FIFO is empty (or has only a partial frame). +#[interrupt] +fn FLEXCOMM6() { + 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; + } } } +/// Storage for Audio Class implementation state +struct Audio { + i2s: I2sHandles, + producer: StreamProducer, + consumer: StreamConsumer, +} +impl Audio { + const RATES: [RangeEntry; 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 ClockSource for Audio { const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed; const SOF_SYNC: bool = false; @@ -161,15 +256,24 @@ impl ClockSource for Audio { 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 AudioHandler<'_, B> for Audio { - fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) { - // alt setting 0 means stopped - match alt_setting { - 0 => self.stop(), - 1 => self.start(), - _ => defmt::error!("unexpected alt setting {}", alt_setting), + /// On an alt setting change, start or stop the related audio pipeline. This + /// behaviour should be replaced by a state machine with at least an 'armed + /// but no data yet' state for playback. + fn alternate_setting_changed(&mut self, terminal: usb_device::UsbDirection, alt_setting: u8) { + match (terminal, 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( &mut self, ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>, @@ -177,12 +281,11 @@ impl AudioHandler<'_, B> for Audio { // Buffer must fit one microframe's (125us @ HS, 1ms @ FS) of audio data // (based on how `usbd_uac2` sets up the descriptors), calculate that // size here. - let mut buf = - [0; (SAMPLE_RATE as usize / USB_FRAME_RATE + 1) * core::mem::size_of::()]; + let mut buf = [0; (SAMPLE_RATE as usize / USB_FRAME_RATE + 1) * BYTES_PER_FRAME]; let len = match ep.read(&mut buf) { Ok(len) => len, Err(_) => { - defmt::error!("usb error in rx callback"); + error!("usb error in rx callback"); return; } }; @@ -191,21 +294,82 @@ impl AudioHandler<'_, B> for Audio { if let Ok(mut wg) = self.producer.grant_exact(buf.len()) { wg.copy_from_slice(buf); wg.commit(buf.len()); - PRODUCED.fetch_add(buf.len() as u32, Ordering::Relaxed); + PRODUCED_OUT.fetch_add(buf.len() as u32, Ordering::Relaxed); } else { 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 - /// 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 { let target = FIFO_LENGTH as i32 / 2; - let fill = PRODUCED + let fill = PRODUCED_OUT .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; let error = fill - target; @@ -224,7 +388,7 @@ impl AudioHandler<'_, B> for Audio { v = v.clamp(nominal_v - (1 << 14), nominal_v + (1 << 14)); // Note: this log will cause continuous underflows - defmt::debug!("fill:{} err:{} fb:{=u32:x}", fill, error, v as u32); + debug!("fill:{} err:{} fb:{=u32:x}", fill, error, 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_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 = ( pins::Pio0_21::take().unwrap().into_spi7_sck_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::Pio1_13::take().unwrap().into_i2s6_sda_pin(&mut iocon), pins::Pio1_31::take().unwrap(), // MCLK ); @@ -287,10 +452,13 @@ fn main() -> ! { ); let i2s_peripheral = { + let fc6 = hal.flexcomm.6.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")] let (usb_speed, usb_peripheral) = ( UsbSpeed::High, @@ -312,41 +480,33 @@ fn main() -> ! { clocks.support_usbfs_token().unwrap(), ), ); - let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin); - defmt::debug!("codec init"); + debug!("codec init"); 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 { i2s: i2s_peripheral, - producer: QUEUE.stream_producer(), - running: AtomicBool::new(false), + producer: QUEUE_AUDIO_OUT.stream_producer(), + consumer: QUEUE_AUDIO_IN.stream_consumer(), }; 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 usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d)) - .composite_with_iads() + let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(USB_VID, USB_PID)) .strings(&[StringDescriptors::default() - .manufacturer("Generic") - .product("usbd_uac2 device") - .serial_number("123456789")]) + .manufacturer(USB_MANUFACTURER) + .product(USB_PRODUCT)]) .unwrap() - .max_packet_size_0(64) + .max_packet_size_0(64) // Required to be 64 on HS, allowed on FS .unwrap() - .device_class(0xef) - .device_sub_class(0x02) - .device_protocol(0x01) .build(); - defmt::info!("main loop"); + debug!("main loop"); loop { usb_dev.poll(&mut [&mut uac2]); diff --git a/examples/lpc55s28-evk/src/wm8904.rs b/examples/lpc55s28-evk/src/wm8904.rs index 5c9c7b2..88bedc8 100644 --- a/examples/lpc55s28-evk/src/wm8904.rs +++ b/examples/lpc55s28-evk/src/wm8904.rs @@ -1,7 +1,7 @@ use cortex_m::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead}; 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 pub(crate) fn init_codec(i2c: &mut T) @@ -31,26 +31,29 @@ where } defmt::debug!("[codec] write seq done"); 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, &[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, &[0x0a, 0x00, 0x00]).ok(); // analog adc 0 = ADC_OSR128 + 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, 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, &[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, &[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, &[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, &[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, &[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, &[0x62, 0x00, 0x01]).ok(); // enable charge pump - let aif_wl = match core::mem::size_of::() { + let aif_wl = match BYTES_PER_FRAME { 4 => 0, // 16 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 // Calculate bclk_div - let bits_per_frame = core::mem::size_of::() * 8; + let bits_per_frame = BYTES_PER_FRAME * 8; let bits_per_second = bits_per_frame as u32 * SAMPLE_RATE; let bclk_div = MCLK_FREQ / bits_per_second; 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, &[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 }