diff --git a/Cargo.toml b/Cargo.toml index 46f90320..affc65be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,6 +158,7 @@ pin-utils = "0.1" usb-device = { version = "0.2", features = ["test-class-high-speed"] } usbd-serial = "0.1" usbd-hid = "0.6" +wm8960 = "0.1.0" [target.'cfg(all(target_arch = "arm", target_os = "none"))'.dev-dependencies] board = { path = "board" } diff --git a/board/build.rs b/board/build.rs index 53c5481c..18afd238 100644 --- a/board/build.rs +++ b/board/build.rs @@ -103,6 +103,7 @@ PROVIDE(BOARD_PWM = DefaultHandler); PROVIDE(BOARD_DMA_A = DefaultHandler); PROVIDE(BOARD_DMA_B = DefaultHandler); PROVIDE(BOARD_PIT = DefaultHandler); +PROVIDE(BOARD_SAI1 = DefaultHandler); PROVIDE(BOARD_GPT1 = DefaultHandler); PROVIDE(BOARD_GPT2 = DefaultHandler); PROVIDE(BOARD_USB1 = DefaultHandler); diff --git a/board/src/imxrt1010evk.rs b/board/src/imxrt1010evk.rs index 8de941d8..9ce4c989 100644 --- a/board/src/imxrt1010evk.rs +++ b/board/src/imxrt1010evk.rs @@ -57,6 +57,16 @@ pub type Console = hal::lpuart::Lpuart; /// host and the MCU. pub type ConsolePins = crate::hal::lpuart::Pins; +pub type Sai1MclkPin = iomuxc::gpio::GPIO_08; + +pub type Sai1TxPins = + hal::sai::Pins; + +pub type Sai1RxPins = + hal::sai::Pins; + +pub type Sai1 = hal::sai::Sai<1, Sai1MclkPin, Sai1TxPins, ()>; + pub type SpiPins = hal::lpspi::Pins< iomuxc::gpio_ad::GPIO_AD_04, // SDO, J57_8 iomuxc::gpio_ad::GPIO_AD_03, // SDI, J57_10 @@ -146,6 +156,7 @@ pub struct Specifics { pub console: Console, pub spi: Spi, pub i2c: I2c, + pub sai1: Sai1, pub pwm: Pwm, pub tp34: Tp34, pub tp31: Tp31, @@ -180,6 +191,16 @@ impl Specifics { console.set_parity(None); }); + let sai1 = { + let sai1 = unsafe { ral::sai::SAI1::instance() }; + let pins = Sai1TxPins { + sync: iomuxc.gpio.p07, + bclk: iomuxc.gpio.p06, + data: iomuxc.gpio.p04, + }; + Sai1::from_tx(sai1, iomuxc.gpio.p08, pins) + }; + #[cfg(feature = "spi")] let spi = { let lpspi1 = unsafe { ral::lpspi::LPSPI1::instance() }; @@ -248,6 +269,7 @@ impl Specifics { console, spi, i2c, + sai1, pwm, tp34: iomuxc.gpio_sd.p02, tp31: iomuxc.gpio_sd.p01, @@ -356,6 +378,7 @@ pub mod interrupt { pub const BOARD_DMA_A: Interrupt = Interrupt::DMA7; pub const BOARD_DMA_B: Interrupt = Interrupt::DMA11; pub const BOARD_PIT: Interrupt = Interrupt::PIT; + pub const BOARD_SAI1: Interrupt = Interrupt::SAI1; pub const BOARD_GPT1: Interrupt = Interrupt::GPT1; pub const BOARD_GPT2: Interrupt = Interrupt::GPT2; pub const BOARD_USB1: Interrupt = Interrupt::USB_OTG1; @@ -369,6 +392,7 @@ pub mod interrupt { (BOARD_DMA_A, syms::BOARD_DMA_A), (BOARD_DMA_B, syms::BOARD_DMA_B), (BOARD_PIT, syms::BOARD_PIT), + (BOARD_SAI1, syms::BOARD_SAI1), (BOARD_GPT1, syms::BOARD_GPT1), (BOARD_GPT2, syms::BOARD_GPT2), (BOARD_USB1, syms::BOARD_USB1), diff --git a/board/src/imxrt1060evk.rs b/board/src/imxrt1060evk.rs index ded7fb59..9e99fdb1 100644 --- a/board/src/imxrt1060evk.rs +++ b/board/src/imxrt1060evk.rs @@ -315,6 +315,7 @@ pub mod interrupt { pub const BOARD_DMA_A: Interrupt = Interrupt::DMA7_DMA23; pub const BOARD_DMA_B: Interrupt = Interrupt::DMA11_DMA27; pub const BOARD_PIT: Interrupt = Interrupt::PIT; + pub const BOARD_SAI1: Interrupt = Interrupt::SAI1; pub const BOARD_GPT1: Interrupt = Interrupt::GPT1; pub const BOARD_GPT2: Interrupt = Interrupt::GPT2; pub const BOARD_USB1: Interrupt = Interrupt::USB_OTG1; @@ -328,6 +329,7 @@ pub mod interrupt { (BOARD_DMA_A, syms::BOARD_DMA_A), (BOARD_DMA_B, syms::BOARD_DMA_B), (BOARD_PIT, syms::BOARD_PIT), + (BOARD_SAI1, syms::BOARD_SAI1), (BOARD_GPT1, syms::BOARD_GPT1), (BOARD_GPT2, syms::BOARD_GPT2), (BOARD_USB1, syms::BOARD_USB1), diff --git a/board/src/lib.rs b/board/src/lib.rs index c5819562..dad59613 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -231,6 +231,7 @@ mod board_interrupts { pub fn BOARD_DMA_A(); pub fn BOARD_DMA_B(); pub fn BOARD_PIT(); + pub fn BOARD_SAI1(); pub fn BOARD_GPT1(); pub fn BOARD_GPT2(); pub fn BOARD_USB1(); diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index 862c6d44..d0a25bd3 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -295,6 +295,7 @@ pub mod interrupt { pub const BOARD_DMA_A: Interrupt = Interrupt::DMA7_DMA23; pub const BOARD_DMA_B: Interrupt = Interrupt::DMA11_DMA27; pub const BOARD_PIT: Interrupt = Interrupt::PIT; + pub const BOARD_SAI1: Interrupt = Interrupt::SAI1; pub const BOARD_GPT1: Interrupt = Interrupt::GPT1; pub const BOARD_GPT2: Interrupt = Interrupt::GPT2; pub const BOARD_USB1: Interrupt = Interrupt::USB_OTG1; @@ -308,6 +309,7 @@ pub mod interrupt { (BOARD_DMA_A, syms::BOARD_DMA_A), (BOARD_DMA_B, syms::BOARD_DMA_B), (BOARD_PIT, syms::BOARD_PIT), + (BOARD_SAI1, syms::BOARD_SAI1), (BOARD_GPT1, syms::BOARD_GPT1), (BOARD_GPT2, syms::BOARD_GPT2), (BOARD_USB1, syms::BOARD_USB1), diff --git a/examples/rtic_sai_wm8960.rs b/examples/rtic_sai_wm8960.rs new file mode 100644 index 00000000..62a177ff --- /dev/null +++ b/examples/rtic_sai_wm8960.rs @@ -0,0 +1,204 @@ +//! Audio playback using sai peripheral and imxrt-hal. +//! +//! Plays back a simple 440Hz (A note) simple square wave tone with the SAI peripheral +//! to a Wolfson WM8960 codec on a number of the EVK boards. +//! +//! The audio stream itself is expected to be a 48000Hz 16bit stereo signal. + +#![no_main] +#![no_std] + +/// Half of a sine wave (0 to pi) +/// Can be used to generate a full sine wave by inverting (-1 * SIN_LUT[X]); +const SIN_LUT: [u16; 256] = [ + 0, 402, 804, 1206, 1608, 2009, 2411, 2811, 3212, 3612, 4011, 4410, 4808, 5205, 5602, 5998, + 6393, 6787, 7180, 7571, 7962, 8351, 8740, 9127, 9512, 9896, 10279, 10660, 11039, 11417, 11793, + 12167, 12540, 12910, 13279, 13646, 14010, 14373, 14733, 15091, 15447, 15800, 16151, 16500, + 16846, 17190, 17531, 17869, 18205, 18538, 18868, 19195, 19520, 19841, 20160, 20475, 20788, + 21097, 21403, 21706, 22006, 22302, 22595, 22884, 23170, 23453, 23732, 24008, 24279, 24548, + 24812, 25073, 25330, 25583, 25833, 26078, 26320, 26557, 26791, 27020, 27246, 27467, 27684, + 27897, 28106, 28311, 28511, 28707, 28899, 29086, 29269, 29448, 29622, 29792, 29957, 30118, + 30274, 30425, 30572, 30715, 30853, 30986, 31114, 31238, 31357, 31471, 31581, 31686, 31786, + 31881, 31972, 32058, 32138, 32214, 32286, 32352, 32413, 32470, 32522, 32568, 32610, 32647, + 32679, 32706, 32729, 32746, 32758, 32766, 32767, 32766, 32758, 32746, 32729, 32706, 32679, + 32647, 32610, 32568, 32522, 32470, 32413, 32352, 32286, 32214, 32138, 32058, 31972, 31881, + 31786, 31686, 31581, 31471, 31357, 31238, 31114, 30986, 30853, 30715, 30572, 30425, 30274, + 30118, 29957, 29792, 29622, 29448, 29269, 29086, 28899, 28707, 28511, 28311, 28106, 27897, + 27684, 27467, 27246, 27020, 26791, 26557, 26320, 26078, 25833, 25583, 25330, 25073, 24812, + 24548, 24279, 24008, 23732, 23453, 23170, 22884, 22595, 22302, 22006, 21706, 21403, 21097, + 20788, 20475, 20160, 19841, 19520, 19195, 18868, 18538, 18205, 17869, 17531, 17190, 16846, + 16500, 16151, 15800, 15447, 15091, 14733, 14373, 14010, 13646, 13279, 12910, 12540, 12167, + 11793, 11417, 11039, 10660, 10279, 9896, 9512, 9127, 8740, 8351, 7962, 7571, 7180, 6787, 6393, + 5998, 5602, 5205, 4808, 4410, 4011, 3612, 3212, 2811, 2411, 2009, 1608, 1206, 804, 402, +]; + +/// Generate a sine wave sample +fn sine(t: u32) -> u16 { + let p = t % 512; + let s = SIN_LUT[(p % 256) as usize]; + if p < 256 { + (32768 + s) / 2 + } else { + (32768 - s) / 2 + } +} + +/// Generate a square wave sample +fn square(t: u32) -> u16 { + if (t % 128) > 64 { + 32767 + } else { + 0 + } +} + +#[rtic::app(device = board, peripherals = false, dispatchers = [BOARD_SWTASK0])] +mod app { + + // + // Configure the demo below. + // + + /// How frequently (milliseconds) should we poll audio + const AUDIO_POLL_MS: u32 = 1000 * (board::PIT_FREQUENCY / 1_000); + + use crate::{sine, square}; + use imxrt_hal as hal; + use wm8960::WM8960; + + type SaiTx = hal::sai::Tx<1, 16, 2, hal::sai::PackingNone>; + + // + // End configurations. + // + + #[local] + struct Local { + /// Toggle when we poll. + _led: board::Led, + + /// i2c for codec + _wm8960: WM8960, + + /// This timer tells us how frequently work on audio. + audio_pit: hal::pit::Pit<2>, + + /// Sample counter for the wave generation + counter: u32, + } + + #[shared] + struct Shared { + /// Serial audio interface + sai1_tx: SaiTx, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let mut cortex_m = cx.core; + let ( + board::Common { + pit: (_, _, mut audio_pit, _), + .. + }, + board::Specifics { led, sai1, i2c, .. }, + ) = board::new(); + let (Some(sai1_tx), None) = sai1.split(&hal::sai::SaiConfig::i2s(hal::sai::bclk_div(8))) + else { + panic!("Unexpected return from sai split"); + }; + + let mut sai1_tx: SaiTx = sai1_tx; + + let regs = sai1_tx.reg_dump(); + defmt::println!( + "Regdump of config: TCR1: {:b}, TCR2 {:b}, TCR3 {:b}, TCR4 {:b}, TCR5 {:b}", + regs[0], + regs[1], + regs[2], + regs[3], + regs[4] + ); + + cortex_m.DCB.enable_trace(); + cortex_m::peripheral::DWT::unlock(); + cortex_m.DWT.enable_cycle_counter(); + + audio_pit.set_load_timer_value(AUDIO_POLL_MS); + audio_pit.set_interrupt_enable(true); + audio_pit.enable(); + + let codec_cfg = wm8960::Config { + master: false, + format: wm8960::Format { + mclk_freq: 24_576_000, + sample_rate: wm8960::SampleRate::SR48000, + bit_width: wm8960::BitWidth::BW16, + }, + protocol: wm8960::Protocol::I2S, + route: wm8960::Route::Playback, + sysclk: wm8960::Sysclk { + source: wm8960::SysclkSource::Mclk, + freq: 0, + }, + speaker_en: true, + }; + + let wm8960 = WM8960::new(i2c, &codec_cfg).unwrap(); + + let mut counter: u32 = 0; + for _i in 0..31 { + sai1_tx.write_frame(0, [sine(counter), square(counter)]); + counter += 1; + } + sai1_tx.set_interrupts( + hal::sai::Interrupts::FIFO_WARNING | hal::sai::Interrupts::FIFO_REQUEST, + ); + sai1_tx.set_enable(true); + ( + Shared { sai1_tx }, + Local { + _led: led, + _wm8960: wm8960, + audio_pit, + counter, + }, + ) + } + + #[task(binds = BOARD_SAI1, shared = [sai1_tx], local = [counter], priority = 2)] + fn sai1_interrupt(mut cx: sai1_interrupt::Context) { + let sai1_interrupt::LocalResources { counter, .. } = cx.local; + + cx.shared.sai1_tx.lock(|sai1_tx| { + while sai1_tx.status().contains(hal::sai::Status::FIFO_REQUEST) { + sai1_tx.write_frame(0, [sine(*counter), square(*counter)]); + *counter = (*counter).wrapping_add(1); + } + }); + } + + #[task(binds = BOARD_PIT, shared = [sai1_tx], local = [audio_pit], priority = 1)] + fn pit_interrupt(mut cx: pit_interrupt::Context) { + let pit_interrupt::LocalResources { audio_pit, .. } = cx.local; + + //led.toggle(); + while audio_pit.is_elapsed() { + audio_pit.clear_elapsed(); + } + + let (status, write_pos, read_pos) = cx.shared.sai1_tx.lock(|sai1_tx| { + let status = sai1_tx.status(); + let (write_pos, read_pos) = sai1_tx.fifo_position(0); + (status, write_pos, read_pos) + }); + + defmt::println!( + "Audio synthesis tx status {:#x}, fifo underrun? {}, write pos {}, read pos {}", + status.bits(), + status.contains(hal::sai::Status::FIFO_ERROR), + write_pos, + read_pos, + ); + } +}