kind of working usb->i2s, but major buffering / timing problems
This commit is contained in:
@@ -2,12 +2,14 @@
|
|||||||
rustflags = [
|
rustflags = [
|
||||||
"-C", "link-arg=-Tlink.x",
|
"-C", "link-arg=-Tlink.x",
|
||||||
"-C", "link-arg=-Tdefmt.x",
|
"-C", "link-arg=-Tdefmt.x",
|
||||||
|
"-C", "debug-assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
[target.thumbv8m.main-none-eabihf]
|
[target.thumbv8m.main-none-eabihf]
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-C", "link-arg=-Tlink.x",
|
"-C", "link-arg=-Tlink.x",
|
||||||
"-C", "link-arg=-Tdefmt.x",
|
"-C", "link-arg=-Tdefmt.x",
|
||||||
|
# "-C", "debug-assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
|||||||
Generated
+22
@@ -275,6 +275,16 @@ dependencies = [
|
|||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heapless"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4"
|
||||||
|
dependencies = [
|
||||||
|
"hash32 0.3.1",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inout"
|
name = "inout"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -350,10 +360,12 @@ dependencies = [
|
|||||||
"defmt-rtt",
|
"defmt-rtt",
|
||||||
"embedded-hal 1.0.0",
|
"embedded-hal 1.0.0",
|
||||||
"embedded-io",
|
"embedded-io",
|
||||||
|
"heapless 0.9.3",
|
||||||
"log-to-defmt",
|
"log-to-defmt",
|
||||||
"lpc55-hal",
|
"lpc55-hal",
|
||||||
"nb 1.1.0",
|
"nb 1.1.0",
|
||||||
"panic-halt",
|
"panic-halt",
|
||||||
|
"panic-probe",
|
||||||
"static_cell",
|
"static_cell",
|
||||||
"usb-device",
|
"usb-device",
|
||||||
"usbd-uac2",
|
"usbd-uac2",
|
||||||
@@ -463,6 +475,16 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
|
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "panic-probe"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
|
||||||
|
dependencies = [
|
||||||
|
"cortex-m",
|
||||||
|
"defmt 1.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ defmt = "1.0.1"
|
|||||||
defmt-rtt = "1.1.0"
|
defmt-rtt = "1.1.0"
|
||||||
embedded-hal = "1.0.0"
|
embedded-hal = "1.0.0"
|
||||||
embedded-io = "0.7.1"
|
embedded-io = "0.7.1"
|
||||||
|
heapless = "0.9.3"
|
||||||
log-to-defmt = "0.1.0"
|
log-to-defmt = "0.1.0"
|
||||||
lpc55-hal = { version = "0.5.0", path = "../lpc55-hal" }
|
lpc55-hal = { version = "0.5.0", path = "../lpc55-hal" }
|
||||||
nb = "1.1.0"
|
nb = "1.1.0"
|
||||||
panic-halt = "1.0.0"
|
panic-halt = "1.0.0"
|
||||||
|
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
|
||||||
static_cell = "2.1.1"
|
static_cell = "2.1.1"
|
||||||
usb-device = "0.3"
|
usb-device = "0.3"
|
||||||
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]}
|
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ MEMORY
|
|||||||
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
|
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
|
||||||
|
|
||||||
/* for use with standard link.x */
|
/* for use with standard link.x */
|
||||||
RAM : ORIGIN = 0x20000000, LENGTH = 64K
|
RAM : ORIGIN = 0x20000000, LENGTH = 192K
|
||||||
|
|
||||||
/* would be used with proper link.x */
|
/* would be used with proper link.x */
|
||||||
/* needs changes to r0 (initialization code) */
|
/* needs changes to r0 (initialization code) */
|
||||||
@@ -18,9 +18,4 @@ MEMORY
|
|||||||
|
|
||||||
/* USB1 SRAM regin */
|
/* USB1 SRAM regin */
|
||||||
/* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */
|
/* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */
|
||||||
|
|
||||||
/* To define our own USB RAM section in one regular */
|
|
||||||
/* RAM, probably easiest to shorten length of RAM */
|
|
||||||
/* above, and use this freed RAM section */
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+319
-125
@@ -1,83 +1,82 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
// extern crate panic_semihosting;
|
extern crate panic_probe;
|
||||||
extern crate panic_halt;
|
#[defmt::panic_handler]
|
||||||
use core::cell::OnceCell;
|
fn panic() -> ! {
|
||||||
use cortex_m::asm::delay;
|
panic_probe::hard_fault()
|
||||||
|
}
|
||||||
|
|
||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
use embedded_io::{ErrorType, Write};
|
|
||||||
use nb;
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use hal::prelude::*;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use lpc55_hal as hal;
|
|
||||||
use lpc55_hal::{
|
|
||||||
drivers::{
|
|
||||||
Serial,
|
|
||||||
pins::{PinId, Pio0_29, Pio0_30},
|
|
||||||
serial::Tx,
|
|
||||||
},
|
|
||||||
peripherals::flexcomm::Usart0,
|
|
||||||
typestates::pin::{
|
|
||||||
flexcomm::{Usart, UsartPins},
|
|
||||||
function::{FC0_RXD_SDA_MOSI_DATA, FC0_TXD_SCL_MISO_WS},
|
|
||||||
state::Special,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use core::convert::Infallible;
|
|
||||||
use defmt;
|
use defmt;
|
||||||
|
use defmt::debug;
|
||||||
use defmt_rtt as _;
|
use defmt_rtt as _;
|
||||||
|
use embedded_io::Write;
|
||||||
|
use hal::Syscon;
|
||||||
use hal::drivers::{Timer, UsbBus, pins};
|
use hal::drivers::{Timer, UsbBus, pins};
|
||||||
use static_cell::StaticCell;
|
use hal::peripherals::flexcomm::Flexcomm7;
|
||||||
|
use hal::prelude::*;
|
||||||
|
use hal::raw as pac;
|
||||||
|
use hal::time::Hertz;
|
||||||
|
use heapless::spsc::Queue;
|
||||||
|
use lpc55_hal::drivers::clocks::Pll;
|
||||||
|
use lpc55_hal::peripherals::syscon::ClockControl;
|
||||||
|
use lpc55_hal::raw::{FLEXCOMM7, I2S7};
|
||||||
|
use lpc55_hal::{self as hal};
|
||||||
|
|
||||||
use usb_device::{
|
use usb_device::{
|
||||||
bus,
|
bus::{self},
|
||||||
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid},
|
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid},
|
||||||
endpoint::IsochronousSynchronizationType,
|
endpoint::IsochronousSynchronizationType,
|
||||||
};
|
};
|
||||||
use usbd_uac2::{
|
use usbd_uac2::{
|
||||||
self, AudioClassConfig, RangeEntry, TerminalConfig, USB_CLASS_AUDIO, UsbAudioClass,
|
self, AudioClassConfig, RangeEntry, TerminalConfig, UsbAudioClass, UsbAudioClockImpl, UsbSpeed,
|
||||||
UsbAudioClockImpl, UsbSpeed,
|
|
||||||
constants::{FunctionCode, TerminalType},
|
constants::{FunctionCode, TerminalType},
|
||||||
descriptors::{ChannelConfig, ClockSource, ClockType, FormatType1, LockDelay},
|
descriptors::{ChannelConfig, ClockType, FormatType1, LockDelay},
|
||||||
};
|
};
|
||||||
|
|
||||||
type SERIAL_RX_PIN = hal::Pin<Pio0_29, Special<FC0_RXD_SDA_MOSI_DATA>>;
|
const CODEC_I2C_ADDR: u8 = 0b0011010;
|
||||||
type SERIAL_TX_PIN = hal::Pin<Pio0_30, Special<FC0_TXD_SCL_MISO_WS>>;
|
|
||||||
type SERIAL_PINS = (SERIAL_TX_PIN, SERIAL_RX_PIN);
|
|
||||||
|
|
||||||
static SERIAL: StaticCell<DefmtUart<Usart0>> = StaticCell::new();
|
const SINE_LUT: [i32; 32] = [
|
||||||
|
0, 1636536, 3210180, 4660460, 5931640, 6974871, 7750062, 8227422, 8388607, 8227422, 7750062,
|
||||||
|
6974871, 5931640, 4660460, 3210180, 1636536, 0, -1636536, -3210180, -4660460, -5931640,
|
||||||
|
-6974871, -7750062, -8227422, -8388607, -8227422, -7750062, -6974871, -5931640, -4660460,
|
||||||
|
-3210180, -1636536,
|
||||||
|
];
|
||||||
|
|
||||||
pub struct DefmtUart<U>(pub Tx<U>)
|
pub fn i2s_sine_test(i2s: &pac::I2S7) -> ! {
|
||||||
where
|
let mut idx = 0;
|
||||||
U: Usart;
|
let mut count = 0usize;
|
||||||
|
|
||||||
impl<U> ErrorType for DefmtUart<U>
|
defmt::debug!("starting sine test");
|
||||||
where
|
|
||||||
U: Usart,
|
loop {
|
||||||
{
|
if i2s.fifostat.read().txnotfull().bit_is_set() {
|
||||||
type Error = Infallible;
|
let sample = SINE_LUT[idx] * 32;
|
||||||
|
|
||||||
|
// ✅ Left channel
|
||||||
|
i2s.fifowr.write(|w| unsafe { w.bits(sample as u32) });
|
||||||
|
|
||||||
|
// wait for space if needed
|
||||||
|
while !i2s.fifostat.read().txnotfull().bit_is_set() {}
|
||||||
|
|
||||||
|
// ✅ Right channel
|
||||||
|
i2s.fifowr.write(|w| unsafe { w.bits(sample as u32) });
|
||||||
|
|
||||||
|
idx = (idx + 1) & (SINE_LUT.len() - 1);
|
||||||
|
count += 1;
|
||||||
|
if count.is_multiple_of(48000) {
|
||||||
|
defmt::debug!("frames sent: {}", count)
|
||||||
}
|
}
|
||||||
impl<U> Write for DefmtUart<U>
|
|
||||||
where
|
|
||||||
U: Usart,
|
|
||||||
{
|
|
||||||
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
|
||||||
buf.iter().map(|c| nb::block!(self.0.write(*c))).last();
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
|
||||||
// Blocking write, so flush is a no-op
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Clock {}
|
struct Clock {}
|
||||||
impl Clock {
|
impl Clock {
|
||||||
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(44100)];
|
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(48000)];
|
||||||
}
|
}
|
||||||
impl UsbAudioClockImpl for Clock {
|
impl UsbAudioClockImpl for Clock {
|
||||||
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
|
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
|
||||||
@@ -90,10 +89,225 @@ impl UsbAudioClockImpl for Clock {
|
|||||||
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
|
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
|
||||||
Ok(&Clock::RATES)
|
Ok(&Clock::RATES)
|
||||||
}
|
}
|
||||||
|
fn get_clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Audio {}
|
struct Audio {
|
||||||
impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio {}
|
running: RefCell<bool>,
|
||||||
|
i2s: I2sTx,
|
||||||
|
queue: RefCell<heapless::spsc::Queue<(u32, u32), 352>>,
|
||||||
|
}
|
||||||
|
impl Audio {
|
||||||
|
fn poll(&self) {
|
||||||
|
if !*self.running.borrow() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let stat = self.i2s.i2s.fifostat.read();
|
||||||
|
|
||||||
|
if stat.txerr().bit_is_set() {
|
||||||
|
self.i2s.i2s.fifostat.modify(|_, w| w.txerr().set_bit());
|
||||||
|
defmt::error!("fifo tx error, txlvl: {}", stat.txlvl().bits());
|
||||||
|
}
|
||||||
|
if stat.txlvl().bits() <= 6 {
|
||||||
|
// fifo is 8 deep
|
||||||
|
if let Some(sample) = self.queue.borrow_mut().dequeue() {
|
||||||
|
self.i2s
|
||||||
|
.i2s
|
||||||
|
.fifowr
|
||||||
|
.write(|w| unsafe { w.bits(sample.0 as u32) });
|
||||||
|
self.i2s
|
||||||
|
.i2s
|
||||||
|
.fifowr
|
||||||
|
.write(|w| unsafe { w.bits(sample.1 as u32) });
|
||||||
|
} else {
|
||||||
|
defmt::error!("queue underflow");
|
||||||
|
self.i2s.i2s.fifowr.write(|w| unsafe { w.bits(0 as u32) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio {
|
||||||
|
fn alternate_setting_changed<CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>(
|
||||||
|
&self,
|
||||||
|
ac: &mut usbd_uac2::AudioClass<'a, B, CS, AU>,
|
||||||
|
terminal: usb_device::UsbDirection,
|
||||||
|
alt_setting: u8,
|
||||||
|
) {
|
||||||
|
match alt_setting {
|
||||||
|
0 => *self.running.borrow_mut() = false,
|
||||||
|
1 => *self.running.borrow_mut() = true,
|
||||||
|
_ => defmt::error!("unexpected alt setting {}", alt_setting),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn audio_data_rx(&self, ep: &usb_device::endpoint::Endpoint<'a, B, usb_device::endpoint::Out>) {
|
||||||
|
let mut buf = [0; 384];
|
||||||
|
let len = match ep.read(&mut buf) {
|
||||||
|
Ok(len) => len,
|
||||||
|
Err(e) => {
|
||||||
|
defmt::error!("usb error in rx callback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let buf = &buf[..len];
|
||||||
|
for sample in buf.chunks_exact(8).map(|b| {
|
||||||
|
(
|
||||||
|
u32::from_le_bytes(b[..4].try_into().unwrap()),
|
||||||
|
u32::from_le_bytes(b[4..].try_into().unwrap()),
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
self.queue.borrow_mut().enqueue(sample).ok(); // TODO: ok is not ok here, it means we have overflowed the
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from NXP SDK WM8904_Init
|
||||||
|
fn init_codec<T>(i2c: &mut T)
|
||||||
|
where
|
||||||
|
T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write,
|
||||||
|
{
|
||||||
|
let mut buf = [0u8; 2];
|
||||||
|
match i2c.write_read(CODEC_I2C_ADDR, &[0], &mut buf) {
|
||||||
|
Ok(_) => {
|
||||||
|
let chip_id = ((buf[0] as u16) << 8) | buf[1] as u16;
|
||||||
|
defmt::debug!("Read chip ID: {:x}", chip_id)
|
||||||
|
}
|
||||||
|
Err(_) => defmt::error!("Error reading I2C"),
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = OPCLK_ENA | CLK_SYS_ENA | CLK_DSP_ENA | TOCLK_ENA
|
||||||
|
i2c.write(CODEC_I2C_ADDR, &[0x6c, 0x01, 0x00]).ok(); // write sequencer 0 ENA
|
||||||
|
i2c.write(CODEC_I2C_ADDR, &[0x6f, 0x01, 0x00]).ok(); // write sequencer 3 START, INDEX=0
|
||||||
|
// wait on write sequencer
|
||||||
|
defmt::debug!("[codec] waiting on write seq");
|
||||||
|
loop {
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
i2c.write_read(CODEC_I2C_ADDR, &[0x70], &mut buf).ok();
|
||||||
|
if buf[1] & 1 == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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, 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, 0x03]).ok(); // power management 3 = LINEOUTL_ENA | LINEOUTR_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, 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, &[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, &[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
|
||||||
|
i2c.write(CODEC_I2C_ADDR, &[0x19, 0x00, 0x0e]).ok(); // audio if 1 = i2s, 32 bits mode
|
||||||
|
i2c.write(CODEC_I2C_ADDR, &[0x15, (0x05 << 2), 0x05]).ok(); // sys clock rate 512fs, sample rate 48
|
||||||
|
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = CLK_SYS_ENA
|
||||||
|
i2c.write(CODEC_I2C_ADDR, &[0x1a, 0x00, 0x08]).ok(); // audio interface 2 = no gpio, sysclk / 8
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct I2sTx {
|
||||||
|
pub i2s: I2S7,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_i2s(mut fc7: FLEXCOMM7, mut i2s7: I2S7, syscon: &mut Syscon) -> I2sTx {
|
||||||
|
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| {
|
||||||
|
w.func()
|
||||||
|
.alt1()
|
||||||
|
.mode()
|
||||||
|
.inactive()
|
||||||
|
.slew()
|
||||||
|
.fast()
|
||||||
|
.invert()
|
||||||
|
.disabled()
|
||||||
|
.digimode()
|
||||||
|
.digital()
|
||||||
|
.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_0x0()); // FRO 96MHz
|
||||||
|
pac::SYSCON::ptr()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.mclkdiv
|
||||||
|
.modify(|_, w| w.div().bits(3).halt().run().reset().released()); // div by 4 = 24MHz
|
||||||
|
pac::SYSCON::ptr()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.mclkio
|
||||||
|
.modify(|_, w| w.mclkio().output());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Select I2S TX function
|
||||||
|
fc7.pselid.write(|w| w.persel().i2s_transmit());
|
||||||
|
|
||||||
|
let regs = i2s7;
|
||||||
|
|
||||||
|
// Enable TX FIFO only
|
||||||
|
regs.fifocfg.modify(|_, w| {
|
||||||
|
w.enabletx()
|
||||||
|
.enabled()
|
||||||
|
.enablerx()
|
||||||
|
.disabled()
|
||||||
|
.dmatx()
|
||||||
|
.disabled()
|
||||||
|
.txi2se0()
|
||||||
|
.zero()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Flush
|
||||||
|
regs.fifocfg.modify(|_, w| w.emptytx().set_bit());
|
||||||
|
|
||||||
|
// Config
|
||||||
|
regs.cfg1.modify(|_, w| unsafe {
|
||||||
|
w.mstslvcfg()
|
||||||
|
.normal_master()
|
||||||
|
.onechannel()
|
||||||
|
.dual_channel()
|
||||||
|
.datalen()
|
||||||
|
.bits(31)
|
||||||
|
.mainenable()
|
||||||
|
.enabled()
|
||||||
|
.mode()
|
||||||
|
.classic_mode()
|
||||||
|
.datapause()
|
||||||
|
.normal()
|
||||||
|
});
|
||||||
|
|
||||||
|
regs.cfg2
|
||||||
|
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) });
|
||||||
|
|
||||||
|
regs.div.modify(|_, w| unsafe { w.div().bits(7) }); // Clock source is MCLK (24MHz on FRO96) / 8 = 3MHz
|
||||||
|
|
||||||
|
I2sTx { i2s: regs }
|
||||||
|
}
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
@@ -106,24 +320,32 @@ fn main() -> ! {
|
|||||||
let mut gpio = hal.gpio.enabled(&mut syscon);
|
let mut gpio = hal.gpio.enabled(&mut syscon);
|
||||||
let mut iocon = hal.iocon.enabled(&mut syscon);
|
let mut iocon = hal.iocon.enabled(&mut syscon);
|
||||||
|
|
||||||
|
debug!("start");
|
||||||
|
|
||||||
let mut red_led = pins::Pio1_6::take()
|
let mut red_led = pins::Pio1_6::take()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_gpio_pin(&mut iocon, &mut gpio)
|
.into_gpio_pin(&mut iocon, &mut gpio)
|
||||||
.into_output(hal::drivers::pins::Level::Low); // start turned on
|
.into_output(hal::drivers::pins::Level::Low); // start turned on
|
||||||
|
|
||||||
|
debug!("iocon");
|
||||||
let usb0_vbus_pin = pins::Pio0_22::take()
|
let usb0_vbus_pin = pins::Pio0_22::take()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_usb0_vbus_pin(&mut iocon);
|
.into_usb0_vbus_pin(&mut iocon);
|
||||||
|
let codec_i2c_pins = (
|
||||||
|
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
|
||||||
|
pins::Pio1_21::take().unwrap().into_i2c4_sda_pin(&mut iocon),
|
||||||
|
);
|
||||||
|
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_31::take().unwrap(), // MCLK
|
||||||
|
);
|
||||||
|
|
||||||
let serial_rx_pin = pins::Pio0_29::take()
|
// iocon.disabled(&mut syscon).release(); // save the environment :)
|
||||||
.unwrap()
|
|
||||||
.into_usart0_rx_pin(&mut iocon);
|
|
||||||
let serial_tx_pin = pins::Pio0_30::take()
|
|
||||||
.unwrap()
|
|
||||||
.into_usart0_tx_pin(&mut iocon);
|
|
||||||
|
|
||||||
iocon.disabled(&mut syscon).release(); // save the environment :)
|
|
||||||
|
|
||||||
|
debug!("clocks");
|
||||||
|
// TODO: figure out how to configure the PLL for a more suitable audio clock.
|
||||||
let clocks = hal::ClockRequirements::default()
|
let clocks = hal::ClockRequirements::default()
|
||||||
// .system_frequency(24.mhz())
|
// .system_frequency(24.mhz())
|
||||||
// .system_frequency(72.mhz())
|
// .system_frequency(72.mhz())
|
||||||
@@ -136,16 +358,22 @@ fn main() -> ! {
|
|||||||
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
|
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let usart = hal
|
debug!("peripherals");
|
||||||
|
|
||||||
|
let i2c_peripheral = hal
|
||||||
.flexcomm
|
.flexcomm
|
||||||
.0
|
.4
|
||||||
.enabled_as_usart(&mut syscon, &clocks.support_flexcomm_token().unwrap());
|
.enabled_as_i2c(&mut syscon, &clocks.support_flexcomm_token().unwrap());
|
||||||
|
let mut i2c_bus = I2cMaster::new(
|
||||||
|
i2c_peripheral,
|
||||||
|
codec_i2c_pins,
|
||||||
|
Hertz::try_from(400.kHz()).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
let serial_config = hal::drivers::serial::config::Config::default().speed(115_200.Hz());
|
let i2s_peripheral = {
|
||||||
let serial = Serial::new(usart, (serial_tx_pin, serial_rx_pin), serial_config).split();
|
let fc7 = hal.flexcomm.7.release();
|
||||||
let serial_tx = DefmtUart(serial.0);
|
init_i2s(fc7.0, fc7.2, &mut syscon)
|
||||||
|
};
|
||||||
// defmt_serial::defmt_serial(SERIAL.init(serial_tx));
|
|
||||||
|
|
||||||
let usb_peripheral = hal.usbhs.enabled_as_device(
|
let usb_peripheral = hal.usbhs.enabled_as_device(
|
||||||
&mut anactrl,
|
&mut anactrl,
|
||||||
@@ -157,7 +385,16 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin);
|
let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin);
|
||||||
let clock = Clock {};
|
let clock = Clock {};
|
||||||
let audio = Audio {};
|
|
||||||
|
defmt::debug!("codec init");
|
||||||
|
init_codec(&mut i2c_bus);
|
||||||
|
|
||||||
|
// i2s_sine_test(&i2s_peripheral.i2s);
|
||||||
|
let audio = Audio {
|
||||||
|
i2s: i2s_peripheral,
|
||||||
|
queue: RefCell::new(heapless::spsc::Queue::new()),
|
||||||
|
running: RefCell::new(false),
|
||||||
|
};
|
||||||
|
|
||||||
let config = AudioClassConfig::new(UsbSpeed::High, FunctionCode::Other, &clock, &audio)
|
let config = AudioClassConfig::new(UsbSpeed::High, FunctionCode::Other, &clock, &audio)
|
||||||
.with_input_config(TerminalConfig::new(
|
.with_input_config(TerminalConfig::new(
|
||||||
@@ -165,13 +402,13 @@ fn main() -> ! {
|
|||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
FormatType1 {
|
FormatType1 {
|
||||||
bit_resolution: 24,
|
bit_resolution: 32,
|
||||||
bytes_per_sample: 4,
|
bytes_per_sample: 4,
|
||||||
},
|
},
|
||||||
TerminalType::ExtLineConnector,
|
TerminalType::ExtLineConnector,
|
||||||
ChannelConfig::default_chans(2),
|
ChannelConfig::default_chans(2),
|
||||||
IsochronousSynchronizationType::Adaptive,
|
IsochronousSynchronizationType::Asynchronous,
|
||||||
LockDelay::Milliseconds(10),
|
LockDelay::Undefined(0),
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
.with_output_config(TerminalConfig::new(
|
.with_output_config(TerminalConfig::new(
|
||||||
@@ -179,12 +416,12 @@ fn main() -> ! {
|
|||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
FormatType1 {
|
FormatType1 {
|
||||||
bit_resolution: 24,
|
bit_resolution: 32,
|
||||||
bytes_per_sample: 4,
|
bytes_per_sample: 4,
|
||||||
},
|
},
|
||||||
TerminalType::ExtLineConnector,
|
TerminalType::ExtLineConnector,
|
||||||
ChannelConfig::default_chans(2),
|
ChannelConfig::default_chans(2),
|
||||||
IsochronousSynchronizationType::Adaptive,
|
IsochronousSynchronizationType::Asynchronous,
|
||||||
LockDelay::Milliseconds(10),
|
LockDelay::Milliseconds(10),
|
||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
@@ -206,53 +443,10 @@ fn main() -> ! {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
defmt::info!("main loop");
|
defmt::info!("main loop");
|
||||||
let mut need_zlp = false;
|
|
||||||
let mut buf = [0u8; 8];
|
|
||||||
let mut size = 0;
|
|
||||||
let mut buf_in_use = false;
|
|
||||||
loop {
|
loop {
|
||||||
// if !usb_dev.poll(&mut []) {
|
usb_dev.poll(&mut [&mut uac2]);
|
||||||
// if !usb_dev.poll(&mut [&mut serial]) {
|
audio.poll();
|
||||||
if !usb_dev.poll(&mut [&mut uac2]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// let mut buf = [0u8; 512];
|
|
||||||
|
|
||||||
// match serial.read(&mut buf) {
|
|
||||||
// Ok(count) if count > 0 => {
|
|
||||||
// assert!(count == 1);
|
|
||||||
// // hprintln!("received some data on the serial port: {:?}", &buf[..count]).ok();
|
|
||||||
// // cortex_m_semihosting::hprintln!("received:\n{}", core::str::from_utf8(&buf[..count]).unwrap()).ok();
|
|
||||||
// red_led.set_low().ok(); // Turn on
|
|
||||||
|
|
||||||
// // cortex_m_semihosting::hprintln!("read {:?}", &buf[..count]).ok();
|
|
||||||
// cortex_m_semihosting::hprintln!("read {:?}", count).ok();
|
|
||||||
|
|
||||||
// // Echo back in upper case
|
|
||||||
// for c in buf[0..count].iter_mut() {
|
|
||||||
// if (0x61 <= *c && *c <= 0x7a) || (0x41 <= *c && *c <= 0x5a) {
|
|
||||||
// *c ^= 0x20;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let mut write_offset = 0;
|
|
||||||
// while write_offset < count {
|
|
||||||
// match serial.write(&buf[write_offset..count]) {
|
|
||||||
// Ok(len) if len > 0 => {
|
|
||||||
// write_offset += len;
|
|
||||||
// cortex_m_semihosting::hprintln!("wrote {:?}", len).ok();
|
|
||||||
|
|
||||||
// },
|
|
||||||
// _ => {},
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // hprintln!("wrote it back").ok();
|
|
||||||
// }
|
|
||||||
// _ => {}
|
|
||||||
// }
|
|
||||||
|
|
||||||
red_led.set_high().ok(); // Turn off
|
red_led.set_high().ok(); // Turn off
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-7
@@ -703,6 +703,10 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UAC2 4.9 & 2.4.10
|
// UAC2 4.9 & 2.4.10
|
||||||
|
if let Some(stream) = &self.input {
|
||||||
|
stream.write_interface_descriptors(writer)?;
|
||||||
|
stream.write_endpoint_descriptors(writer)?;
|
||||||
|
}
|
||||||
if let Some(stream) = &self.output {
|
if let Some(stream) = &self.output {
|
||||||
stream.write_interface_descriptors(writer)?;
|
stream.write_interface_descriptors(writer)?;
|
||||||
stream.write_endpoint_descriptors(writer)?;
|
stream.write_endpoint_descriptors(writer)?;
|
||||||
@@ -714,11 +718,6 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
|
|||||||
writer.endpoint(feedback)?;
|
writer.endpoint(feedback)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(stream) = &self.input {
|
|
||||||
stream.write_interface_descriptors(writer)?;
|
|
||||||
stream.write_endpoint_descriptors(writer)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn control_out(&mut self, xfer: ControlOut<B>) {
|
fn control_out(&mut self, xfer: ControlOut<B>) {
|
||||||
@@ -741,6 +740,39 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn endpoint_out(&mut self, addr: EndpointAddress) {
|
||||||
|
debug!("EP {} out data", addr);
|
||||||
|
if addr.index() == self.out_ep {
|
||||||
|
self.audio_impl
|
||||||
|
.audio_data_rx(&self.output.as_ref().unwrap().endpoint)
|
||||||
|
} else {
|
||||||
|
debug!(" unexpected OUT on {}", addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll(&mut self) {
|
||||||
|
debug!("poll");
|
||||||
|
// no streaming in alt 0
|
||||||
|
if self.output.as_ref().unwrap().alt_setting != 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let mut buf = [0; 1024];
|
||||||
|
match self.output.as_ref().unwrap().endpoint.read(&mut buf) {
|
||||||
|
Ok(len) if len > 0 => {
|
||||||
|
debug!("EP OUT data {:?}", len);
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("EP OUT empty");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(UsbError::WouldBlock) => break,
|
||||||
|
Err(err) => {
|
||||||
|
debug!("EP OUT error {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<'a, B, CS, AU> {
|
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<'a, B, CS, AU> {
|
||||||
@@ -816,6 +848,11 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
self.output.as_mut().unwrap().alt_setting = alt_setting;
|
self.output.as_mut().unwrap().alt_setting = alt_setting;
|
||||||
xfer.accept().ok();
|
xfer.accept().ok();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
" not handled (in: {}, out: {}, got: {}).",
|
||||||
|
self.in_iface, self.out_iface, iface
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get_alt_interface(&mut self, xfer: ControlIn<B>) {
|
fn get_alt_interface(&mut self, xfer: ControlIn<B>) {
|
||||||
@@ -985,7 +1022,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
debug!(" SamplingFreqControl");
|
debug!(" SamplingFreqControl");
|
||||||
if channel != 0 {
|
if channel != 0 {
|
||||||
error!(
|
error!(
|
||||||
" Invalid channel {} for SamplingFreqControl GET RANGE. Ignoring.",
|
" Invalid channel {} for SamplingFreqControl GET CUR. Ignoring.",
|
||||||
channel
|
channel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1000,7 +1037,28 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
_ => debug!(" Unimplemented."),
|
Ok(ClockSourceControlSelector::ClockValidControl) => {
|
||||||
|
debug!(" ClockValidControl");
|
||||||
|
if channel != 0 {
|
||||||
|
error!(
|
||||||
|
" Invalid channel {} for ClockValidControl GET CUR. Ignoring.",
|
||||||
|
channel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
xfer.accept(|mut buf| match self.clock_impl.get_clock_validity() {
|
||||||
|
Ok(valid) => {
|
||||||
|
debug!(" {}", valid);
|
||||||
|
buf.write_u8(valid as u8)
|
||||||
|
.map_err(|_e| UsbError::BufferOverflow)?;
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
|
Err(_e) => Err(UsbError::InvalidState),
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!(" Unimplemented.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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) {
|
||||||
@@ -1034,4 +1092,17 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn read(&mut self, buf: &mut [u8]) -> usb_device::Result<usize> {
|
||||||
|
match self.output.as_mut().unwrap().endpoint.read(buf) {
|
||||||
|
Ok(len) => {
|
||||||
|
debug!("NO CB read {} bytes", len);
|
||||||
|
Ok(len)
|
||||||
|
}
|
||||||
|
Err(UsbError::WouldBlock) => Err(UsbError::WouldBlock),
|
||||||
|
Err(e) => {
|
||||||
|
error!("read error");
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user