diff --git a/Cargo.toml b/Cargo.toml index e348ad99..6d264e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ members = [ "examples/lpcomp-demo", "examples/qdec-demo", "examples/comp-demo", + "examples/i2s-controller-demo", + "examples/i2s-peripheral-demo", "examples/pwm-demo", "examples/twim-demo", "examples/twis-demo", diff --git a/examples/i2s-controller-demo/Cargo.toml b/examples/i2s-controller-demo/Cargo.toml new file mode 100644 index 00000000..4a69f0c5 --- /dev/null +++ b/examples/i2s-controller-demo/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "i2s-controller-demo" +version = "0.1.0" +authors = ["Henrik Alsér"] +edition = "2018" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cortex-m = "0.6.2" +cortex-m-rtic = "0.5.3" +rtt-target = {version = "0.2.0", features = ["cortex-m"] } +nrf52840-hal = { features = ["rt"], path = "../../nrf52840-hal" } +heapless = "0.5.5" +small_morse = "0.1.0" + +[dependencies.embedded-hal] +version = "0.2.3" +features = ["unproven"] diff --git a/examples/i2s-controller-demo/Embed.toml b/examples/i2s-controller-demo/Embed.toml new file mode 100755 index 00000000..eda5e3e0 --- /dev/null +++ b/examples/i2s-controller-demo/Embed.toml @@ -0,0 +1,21 @@ +[default.probe] +protocol = "Swd" + +[default.flashing] +enabled = true +halt_afterwards = false +restore_unwritten_bytes = false + +[default.general] +chip = "nRF52840" +chip_descriptions = [] +log_level = "Warn" + +[default.rtt] +enabled = true +channels = [] +timeout = 3000 +show_timestamps = true + +[default.gdb] +enabled = false \ No newline at end of file diff --git a/examples/i2s-controller-demo/src/main.rs b/examples/i2s-controller-demo/src/main.rs new file mode 100644 index 00000000..b487839b --- /dev/null +++ b/examples/i2s-controller-demo/src/main.rs @@ -0,0 +1,229 @@ +#![no_std] +#![no_main] + +// I2S `controller mode` demo +// Generates Morse code audio signals for text from UART, playing back over I2S +// Tested with nRF52840-DK and a UDA1334a DAC + +use embedded_hal::digital::v2::{InputPin, OutputPin}; +use heapless::{ + consts::*, + spsc::{Consumer, Producer, Queue}, +}; +use small_morse::{encode, State}; +use { + core::{ + panic::PanicInfo, + sync::atomic::{compiler_fence, Ordering}, + }, + hal::{ + gpio::{Input, Level, Output, Pin, PullUp, PushPull}, + gpiote::*, + i2s::*, + pac::{TIMER0, UARTE0}, + timer::Timer, + uarte::*, + }, + nrf52840_hal as hal, + rtic::cyccnt::U32Ext, + rtt_target::{rprintln, rtt_init_print}, +}; + +#[rtic::app(device = crate::hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +const APP: () = { + struct Resources { + i2s: hal::i2s::I2S, + #[init([0; 32])] + signal_buf: [i16; 32], + #[init([0; 32])] + mute_buf: [i16; 32], + #[init(None)] + queue: Option>, + producer: Producer<'static, State, U256>, + consumer: Consumer<'static, State, U256>, + #[init(5_000_000)] + speed: u32, + uarte: Uarte, + uarte_timer: Timer, + gpiote: Gpiote, + btn1: Pin>, + btn2: Pin>, + led: Pin>, + } + + #[init(resources = [queue, signal_buf, mute_buf], spawn = [tick])] + fn init(mut ctx: init::Context) -> init::LateResources { + let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc(); + // Enable the monotonic timer (CYCCNT) + ctx.core.DCB.enable_trace(); + ctx.core.DWT.enable_cycle_counter(); + rtt_init_print!(); + + let p0 = hal::gpio::p0::Parts::new(ctx.device.P0); + + // Configure I2S controller + let mck_pin = p0.p0_28.into_push_pull_output(Level::Low).degrade(); + let sck_pin = p0.p0_29.into_push_pull_output(Level::Low).degrade(); + let lrck_pin = p0.p0_31.into_push_pull_output(Level::Low).degrade(); + let sdout_pin = p0.p0_30.into_push_pull_output(Level::Low).degrade(); + + let i2s = I2S::new_controller( + ctx.device.I2S, + Some(&mck_pin), + &sck_pin, + &lrck_pin, + None, + Some(&sdout_pin), + ); + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); + i2s.enable().start(); + + // Fill signal buffer with triangle waveform, 2 channels interleaved + let signal_buf = ctx.resources.signal_buf; + let len = signal_buf.len() / 2; + for x in 0..len { + signal_buf[2 * x] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; + signal_buf[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; + } + + // Configure buttons + let btn1 = p0.p0_11.into_pullup_input().degrade(); + let btn2 = p0.p0_12.into_pullup_input().degrade(); + let gpiote = Gpiote::new(ctx.device.GPIOTE); + gpiote.port().input_pin(&btn1).low(); + gpiote.port().input_pin(&btn2).low(); + gpiote.port().enable_interrupt(); + + // Configure the onboard USB CDC UARTE + let uarte = Uarte::new( + ctx.device.UARTE0, + Pins { + txd: p0.p0_06.into_push_pull_output(Level::High).degrade(), + rxd: p0.p0_08.into_floating_input().degrade(), + cts: None, + rts: None, + }, + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + + *ctx.resources.queue = Some(Queue::new()); + let (producer, consumer) = ctx.resources.queue.as_mut().unwrap().split(); + + rprintln!("Morse code generator"); + rprintln!("Send me text over UART @ 115_200 baud"); + rprintln!("Press button 1 to slow down or button 2 to speed up"); + + ctx.spawn.tick().ok(); + + init::LateResources { + i2s, + producer, + consumer, + gpiote, + btn1, + btn2, + led: p0.p0_13.into_push_pull_output(Level::High).degrade(), + uarte, + uarte_timer: Timer::new(ctx.device.TIMER0), + } + } + + #[idle(resources=[uarte, uarte_timer, producer])] + fn idle(ctx: idle::Context) -> ! { + let idle::Resources { + uarte, + uarte_timer, + producer, + } = ctx.resources; + let uarte_rx_buf = &mut [0u8; 64][..]; + loop { + match uarte.read_timeout(uarte_rx_buf, uarte_timer, 100_000) { + Ok(_) => { + if let Ok(msg) = core::str::from_utf8(&uarte_rx_buf[..]) { + rprintln!("{}", msg); + for action in encode(msg) { + for _ in 0..action.duration { + producer.enqueue(action.state).ok(); + } + } + } + } + Err(hal::uarte::Error::Timeout(n)) if n > 0 => { + if let Ok(msg) = core::str::from_utf8(&uarte_rx_buf[0..n]) { + rprintln!("{}", msg); + for action in encode(msg) { + for _ in 0..action.duration { + producer.enqueue(action.state).ok(); + } + } + } + } + _ => {} + } + } + } + + #[task(resources = [consumer, i2s, signal_buf, mute_buf, led, speed], schedule = [tick])] + fn tick(ctx: tick::Context) { + let i2s = ctx.resources.i2s; + match ctx.resources.consumer.dequeue() { + Some(State::On) => { + // Move TX pointer to signal buffer (sound ON) + i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); + ctx.resources.led.set_low().ok(); + } + _ => { + // Move TX pointer to silent buffer (sound OFF) + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); + ctx.resources.led.set_high().ok(); + } + } + ctx.schedule + .tick(ctx.scheduled + ctx.resources.speed.cycles()) + .ok(); + } + + #[task(binds = GPIOTE, resources = [gpiote, speed], schedule = [debounce])] + fn on_gpiote(ctx: on_gpiote::Context) { + ctx.resources.gpiote.reset_events(); + ctx.schedule.debounce(ctx.start + 3_000_000.cycles()).ok(); + } + + #[task(resources = [btn1, btn2, i2s, speed])] + fn debounce(ctx: debounce::Context) { + if ctx.resources.btn1.is_low().unwrap() { + rprintln!("Go slower"); + *ctx.resources.speed += 600_000; + } + if ctx.resources.btn2.is_low().unwrap() { + rprintln!("Go faster"); + *ctx.resources.speed -= 600_000; + } + } + + extern "C" { + fn SWI0_EGU0(); + fn SWI1_EGU1(); + } +}; + +fn triangle_wave(x: i32, length: usize, amplitude: i32, phase: i32, periods: i32) -> i32 { + let length = length as i32; + amplitude + - ((2 * periods * (x + phase + length / (4 * periods)) * amplitude / length) + % (2 * amplitude) + - amplitude) + .abs() + - amplitude / 2 +} + +#[inline(never)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + cortex_m::interrupt::disable(); + rprintln!("{}", info); + loop { + compiler_fence(Ordering::SeqCst); + } +} diff --git a/examples/i2s-peripheral-demo/Cargo.toml b/examples/i2s-peripheral-demo/Cargo.toml new file mode 100644 index 00000000..dd01b2c8 --- /dev/null +++ b/examples/i2s-peripheral-demo/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "i2s-peripheral-demo" +version = "0.1.0" +authors = ["Henrik Alsér"] +edition = "2018" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cortex-m = "0.6.2" +cortex-m-rtic = "0.5.3" +rtt-target = {version = "0.2.0", features = ["cortex-m"] } +nrf52840-hal = { features = ["rt"], path = "../../nrf52840-hal" } +small_morse = "0.1.0" +m = "0.1.1" + +[dependencies.embedded-hal] +version = "0.2.3" +features = ["unproven"] diff --git a/examples/i2s-peripheral-demo/Embed.toml b/examples/i2s-peripheral-demo/Embed.toml new file mode 100755 index 00000000..eda5e3e0 --- /dev/null +++ b/examples/i2s-peripheral-demo/Embed.toml @@ -0,0 +1,21 @@ +[default.probe] +protocol = "Swd" + +[default.flashing] +enabled = true +halt_afterwards = false +restore_unwritten_bytes = false + +[default.general] +chip = "nRF52840" +chip_descriptions = [] +log_level = "Warn" + +[default.rtt] +enabled = true +channels = [] +timeout = 3000 +show_timestamps = true + +[default.gdb] +enabled = false \ No newline at end of file diff --git a/examples/i2s-peripheral-demo/src/main.rs b/examples/i2s-peripheral-demo/src/main.rs new file mode 100644 index 00000000..cb50753c --- /dev/null +++ b/examples/i2s-peripheral-demo/src/main.rs @@ -0,0 +1,116 @@ +#![no_std] +#![no_main] + +// I2S `peripheral mode` demo +// RMS level indicator using an RGB LED (APA102 on ItsyBitsy nRF52840) + +use embedded_hal::blocking::spi::Write; +use m::Float; +use { + core::{ + panic::PanicInfo, + sync::atomic::{compiler_fence, Ordering}, + }, + hal::{ + gpio::Level, + i2s::*, + pac::SPIM0, + spim::{Frequency, Mode as SPIMode, Phase, Pins, Polarity, Spim}, + }, + nrf52840_hal as hal, + rtt_target::{rprintln, rtt_init_print}, +}; + +const OFF: [u8; 9] = [0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF]; +const GREEN: [u8; 9] = [0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10, 0x00, 0xFF]; +const ORANGE: [u8; 9] = [0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x10, 0x10, 0xFF]; +const RED: [u8; 9] = [0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x10, 0xFF]; + +#[rtic::app(device = crate::hal::pac, peripherals = true)] +const APP: () = { + struct Resources { + i2s: I2S, + #[init([0; 128])] + rx_buf: [i16; 128], + rgb: Spim, + } + + #[init(resources = [rx_buf])] + fn init(ctx: init::Context) -> init::LateResources { + let _clocks = hal::clocks::Clocks::new(ctx.device.CLOCK).enable_ext_hfosc(); + rtt_init_print!(); + rprintln!("Play me some audio..."); + + let p0 = hal::gpio::p0::Parts::new(ctx.device.P0); + let mck_pin = p0.p0_25.into_floating_input().degrade(); + let sck_pin = p0.p0_24.into_floating_input().degrade(); + let lrck_pin = p0.p0_16.into_floating_input().degrade(); + let sdin_pin = p0.p0_14.into_floating_input().degrade(); + + // Configure I2S reception + let i2s = I2S::new_peripheral( + ctx.device.I2S, + Some(&mck_pin), + &sck_pin, + &lrck_pin, + Some(&sdin_pin), + None, + ); + i2s.enable_interrupt(I2SEvent::RxPtrUpdated) + .rx_buffer(&mut ctx.resources.rx_buf[..]) + .ok(); + i2s.enable().start(); + + // Configure APA102 RGB LED control + let p1 = hal::gpio::p1::Parts::new(ctx.device.P1); + let rgb_data_pin = p0.p0_08.into_push_pull_output(Level::Low).degrade(); + let rgb_clk_pin = p1.p1_09.into_push_pull_output(Level::Low).degrade(); + + let rgb = Spim::new( + ctx.device.SPIM0, + Pins { + miso: None, + mosi: Some(rgb_data_pin), + sck: rgb_clk_pin, + }, + Frequency::M4, + SPIMode { + polarity: Polarity::IdleLow, + phase: Phase::CaptureOnFirstTransition, + }, + 0, + ); + + init::LateResources { i2s, rgb } + } + + #[task(binds = I2S, resources = [i2s, rx_buf, rgb])] + fn on_i2s(ctx: on_i2s::Context) { + let on_i2s::Resources { i2s, rx_buf, rgb } = ctx.resources; + if i2s.is_event_triggered(I2SEvent::RxPtrUpdated) { + i2s.reset_event(I2SEvent::RxPtrUpdated); + // Calculate mono summed RMS of received buffer + let rms = Float::sqrt( + (rx_buf.iter().map(|x| *x as i32).map(|x| x * x).sum::() / rx_buf.len() as i32) + as f32, + ) as u16; + let color = match rms { + 0..=4 => &OFF, + 5..=10_337 => &GREEN, + 10_338..=16_383 => &ORANGE, + _ => &RED, + }; + as Write>::write(rgb, color).ok(); + } + } +}; + +#[inline(never)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + cortex_m::interrupt::disable(); + rprintln!("{}", info); + loop { + compiler_fence(Ordering::SeqCst); + } +} diff --git a/nrf-hal-common/src/i2s.rs b/nrf-hal-common/src/i2s.rs new file mode 100644 index 00000000..0915abe1 --- /dev/null +++ b/nrf-hal-common/src/i2s.rs @@ -0,0 +1,580 @@ +//! HAL interface for the I2S peripheral. +//! + +#[cfg(not(feature = "9160"))] +use crate::pac::{i2s, I2S as I2S_PAC}; +#[cfg(feature = "9160")] +use crate::pac::{i2s_ns as i2s, I2S_NS as I2S_PAC}; +use crate::{ + gpio::{Floating, Input, Output, Pin, PushPull}, + pac::generic::Reg, + target_constants::{SRAM_LOWER, SRAM_UPPER}, +}; +use core::sync::atomic::{compiler_fence, Ordering}; +use i2s::{_EVENTS_RXPTRUPD, _EVENTS_STOPPED, _EVENTS_TXPTRUPD, _TASKS_START, _TASKS_STOP}; + +pub struct I2S { + i2s: I2S_PAC, +} + +// I2S EasyDMA MAXCNT bit length = 14 +const MAX_DMA_MAXCNT: u32 = 16_384; + +impl I2S { + /// Takes ownership of the raw I2S peripheral, returning a safe wrapper in controller mode. + pub fn new_controller( + i2s: I2S_PAC, + mck_pin: Option<&Pin>>, + sck_pin: &Pin>, + lrck_pin: &Pin>, + sdin_pin: Option<&Pin>>, + sdout_pin: Option<&Pin>>, + ) -> Self { + i2s.config.mcken.write(|w| w.mcken().enabled()); + i2s.config.mckfreq.write(|w| w.mckfreq()._32mdiv16()); + i2s.config.ratio.write(|w| w.ratio()._192x()); + i2s.config.mode.write(|w| w.mode().master()); + i2s.config.swidth.write(|w| w.swidth()._16bit()); + i2s.config.align.write(|w| w.align().left()); + i2s.config.format.write(|w| w.format().i2s()); + i2s.config.channels.write(|w| w.channels().stereo()); + + if let Some(p) = mck_pin { + i2s.psel.mck.write(|w| { + unsafe { w.pin().bits(p.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(p.port().bit()); + w.connect().connected() + }); + } + + i2s.psel.sck.write(|w| { + unsafe { w.pin().bits(sck_pin.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(sck_pin.port().bit()); + w.connect().connected() + }); + + i2s.psel.lrck.write(|w| { + unsafe { w.pin().bits(lrck_pin.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(lrck_pin.port().bit()); + w.connect().connected() + }); + + if let Some(p) = sdin_pin { + i2s.psel.sdin.write(|w| { + unsafe { w.pin().bits(p.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(p.port().bit()); + w.connect().connected() + }); + } + + if let Some(p) = sdout_pin { + i2s.psel.sdout.write(|w| { + unsafe { w.pin().bits(p.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(p.port().bit()); + w.connect().connected() + }); + } + + Self { i2s } + } + + /// Takes ownership of the raw I2S peripheral, returning a safe wrapper i peripheral mode. + pub fn new_peripheral( + i2s: I2S_PAC, + mck_pin: Option<&Pin>>, + sck_pin: &Pin>, + lrck_pin: &Pin>, + sdin_pin: Option<&Pin>>, + sdout_pin: Option<&Pin>>, + ) -> Self { + i2s.config.txen.write(|w| w.txen().enabled()); + i2s.config.rxen.write(|w| w.rxen().enabled()); + i2s.config.mode.write(|w| w.mode().slave()); + i2s.config.swidth.write(|w| w.swidth()._16bit()); + i2s.config.align.write(|w| w.align().left()); + i2s.config.format.write(|w| w.format().i2s()); + i2s.config.channels.write(|w| w.channels().stereo()); + + if let Some(p) = mck_pin { + i2s.psel.mck.write(|w| { + unsafe { w.pin().bits(p.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(p.port().bit()); + w.connect().connected() + }); + } + + i2s.psel.sck.write(|w| { + unsafe { w.pin().bits(sck_pin.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(sck_pin.port().bit()); + w.connect().connected() + }); + + i2s.psel.lrck.write(|w| { + unsafe { w.pin().bits(lrck_pin.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(lrck_pin.port().bit()); + w.connect().connected() + }); + + if let Some(p) = sdin_pin { + i2s.psel.sdin.write(|w| { + unsafe { w.pin().bits(p.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(p.port().bit()); + w.connect().connected() + }); + } + + if let Some(p) = sdout_pin { + i2s.psel.sdout.write(|w| { + unsafe { w.pin().bits(p.pin()) }; + #[cfg(any(feature = "52833", feature = "52840"))] + w.port().bit(p.port().bit()); + w.connect().connected() + }); + } + + i2s.enable.write(|w| w.enable().enabled()); + Self { i2s } + } + + /// Enables the I2S module. + #[inline(always)] + pub fn enable(&self) -> &Self { + self.i2s.enable.write(|w| w.enable().enabled()); + self + } + + /// Disables the I2S module. + #[inline(always)] + pub fn disable(&self) -> &Self { + self.i2s.enable.write(|w| w.enable().disabled()); + self + } + + /// Starts I2S transfer. + #[inline(always)] + pub fn start(&self) { + self.i2s.tasks_start.write(|w| unsafe { w.bits(1) }); + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub fn stop(&self) { + compiler_fence(Ordering::SeqCst); + self.i2s.tasks_stop.write(|w| unsafe { w.bits(1) }); + while self.i2s.events_stopped.read().bits() == 0 {} + } + + /// Enables/disables I2S transmission (TX). + #[inline(always)] + pub fn set_tx_enabled(&self, enabled: bool) -> &Self { + self.i2s.config.txen.write(|w| w.txen().bit(enabled)); + self + } + + /// Enables/disables I2S reception (RX). + #[inline(always)] + pub fn set_rx_enabled(&self, enabled: bool) -> &Self { + self.i2s.config.rxen.write(|w| w.rxen().bit(enabled)); + self + } + + /// Sets MCK generator frequency. + #[inline(always)] + pub fn set_mck_frequency(&self, freq: MckFreq) -> &Self { + self.i2s + .config + .mckfreq + .write(|w| unsafe { w.mckfreq().bits(freq.into()) }); + self + } + + /// Sets MCK / LRCK ratio. + #[inline(always)] + pub fn set_ratio(&self, ratio: Ratio) -> &Self { + self.i2s + .config + .ratio + .write(|w| unsafe { w.ratio().bits(ratio.into()) }); + self + } + + /// Sets sample width. + #[inline(always)] + pub fn set_sample_width(&self, width: SampleWidth) -> &Self { + self.i2s + .config + .swidth + .write(|w| unsafe { w.swidth().bits(width.into()) }); + self + } + + /// Sets the sample alignment within a frame. + #[inline(always)] + pub fn set_align(&self, align: Align) -> &Self { + self.i2s.config.align.write(|w| w.align().bit(align.into())); + self + } + + /// Sets the frame format. + #[inline(always)] + pub fn set_format(&self, format: Format) -> &Self { + self.i2s + .config + .format + .write(|w| w.format().bit(format.into())); + self + } + + /// Sets the I2S channel configuation. + #[inline(always)] + pub fn set_channels(&self, channels: Channels) -> &Self { + self.i2s + .config + .channels + .write(|w| unsafe { w.channels().bits(channels.into()) }); + self + } + + /// Sets the transmit data buffer (TX). + /// NOTE: The TX buffer must live until the transfer is done, or corrupted data will be transmitted. + #[inline(always)] + pub fn tx_buffer(&self, buf: &B) -> Result<(), Error> { + if (buf.ptr() as usize) < SRAM_LOWER || (buf.ptr() as usize) > SRAM_UPPER { + return Err(Error::DMABufferNotInDataMemory); + } + + if buf.maxcnt() > MAX_DMA_MAXCNT { + return Err(Error::BufferTooLong); + } + + self.i2s + .txd + .ptr + .write(|w| unsafe { w.ptr().bits(buf.ptr()) }); + self.i2s + .rxtxd + .maxcnt + .write(|w| unsafe { w.bits(buf.maxcnt()) }); + + Ok(()) + } + + /// Sets the receive data buffer (RX). + #[inline(always)] + pub fn rx_buffer(&self, buf: &'static mut B) -> Result<(), Error> { + if (buf.ptr() as usize) < SRAM_LOWER || (buf.ptr() as usize) > SRAM_UPPER { + return Err(Error::DMABufferNotInDataMemory); + } + + if buf.maxcnt() > MAX_DMA_MAXCNT { + return Err(Error::BufferTooLong); + } + + self.i2s + .rxd + .ptr + .write(|w| unsafe { w.ptr().bits(buf.ptr()) }); + self.i2s + .rxtxd + .maxcnt + .write(|w| unsafe { w.bits(buf.maxcnt()) }); + + Ok(()) + } + + /// Sets the transmit buffer RAM start address. + #[inline(always)] + pub fn set_tx_ptr(&self, addr: u32) -> Result<(), Error> { + if (addr as usize) < SRAM_LOWER || (addr as usize) > SRAM_UPPER { + return Err(Error::DMABufferNotInDataMemory); + } + self.i2s.txd.ptr.write(|w| unsafe { w.ptr().bits(addr) }); + Ok(()) + } + + /// Sets the receive buffer RAM start address. + #[inline(always)] + pub unsafe fn set_rx_ptr(&self, addr: u32) -> Result<(), Error> { + if (addr as usize) < SRAM_LOWER || (addr as usize) > SRAM_UPPER { + return Err(Error::DMABufferNotInDataMemory); + } + self.i2s.rxd.ptr.write(|w| w.ptr().bits(addr)); + Ok(()) + } + + /// Sets the size (in 32bit words) of the receive and transmit buffers. + #[inline(always)] + pub unsafe fn set_buffersize(&self, n_32bit: u32) -> Result<(), Error> { + if n_32bit > MAX_DMA_MAXCNT { + return Err(Error::BufferTooLong); + } + self.i2s.rxtxd.maxcnt.write(|w| w.bits(n_32bit)); + Ok(()) + } + + /// Checks if an event has been triggered. + #[inline(always)] + pub fn is_event_triggered(&self, event: I2SEvent) -> bool { + match event { + I2SEvent::Stopped => self.i2s.events_stopped.read().bits() != 0, + I2SEvent::RxPtrUpdated => self.i2s.events_rxptrupd.read().bits() != 0, + I2SEvent::TxPtrUpdated => self.i2s.events_txptrupd.read().bits() != 0, + } + } + + /// Marks event as handled. + #[inline(always)] + pub fn reset_event(&self, event: I2SEvent) { + match event { + I2SEvent::Stopped => self.i2s.events_stopped.reset(), + I2SEvent::RxPtrUpdated => self.i2s.events_rxptrupd.reset(), + I2SEvent::TxPtrUpdated => self.i2s.events_txptrupd.reset(), + } + } + + /// Enables interrupt triggering on the specified event. + #[inline(always)] + pub fn enable_interrupt(&self, event: I2SEvent) -> &Self { + match event { + I2SEvent::Stopped => self.i2s.intenset.modify(|_r, w| w.stopped().set()), + I2SEvent::RxPtrUpdated => self.i2s.intenset.modify(|_r, w| w.rxptrupd().set()), + I2SEvent::TxPtrUpdated => self.i2s.intenset.modify(|_r, w| w.txptrupd().set()), + }; + self + } + + /// Disables interrupt triggering on the specified event. + #[inline(always)] + pub fn disable_interrupt(&self, event: I2SEvent) -> &Self { + match event { + I2SEvent::Stopped => self.i2s.intenclr.modify(|_r, w| w.stopped().clear()), + I2SEvent::RxPtrUpdated => self.i2s.intenclr.modify(|_r, w| w.rxptrupd().clear()), + I2SEvent::TxPtrUpdated => self.i2s.intenclr.modify(|_r, w| w.txptrupd().clear()), + }; + self + } + + /// Returns reference to `Stopped` event endpoint for PPI. + #[inline(always)] + pub fn event_stopped(&self) -> &Reg { + &self.i2s.events_stopped + } + + /// Returns reference to `RxPtrUpdated` event endpoint for PPI. + #[inline(always)] + pub fn event_rx_ptr_updated(&self) -> &Reg { + &self.i2s.events_rxptrupd + } + + /// Returns reference to `TxPtrUpdated` event endpoint for PPI. + #[inline(always)] + pub fn event_tx_ptr_updated(&self) -> &Reg { + &self.i2s.events_txptrupd + } + + /// Returns reference to `Start` task endpoint for PPI. + #[inline(always)] + pub fn task_start(&self) -> &Reg { + &self.i2s.tasks_start + } + + /// Returns reference to `Stop` task endpoint for PPI. + #[inline(always)] + pub fn task_stop(&self) -> &Reg { + &self.i2s.tasks_stop + } + + /// Consumes `self` and returns back the raw peripheral. + pub fn free(self) -> I2S_PAC { + self.disable(); + self.i2s + } +} + +#[derive(Debug)] +pub enum Error { + DMABufferNotInDataMemory, + BufferTooLong, +} + +/// I2S Mode +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Mode { + Controller, + Peripheral, +} + +/// Master clock generator frequency. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum MckFreq { + _32MDiv8, + _32MDiv10, + _32MDiv11, + _32MDiv15, + _32MDiv16, + _32MDiv21, + _32MDiv23, + _32MDiv30, + _32MDiv31, + _32MDiv32, + _32MDiv42, + _32MDiv63, + _32MDiv125, +} +impl From for u32 { + fn from(variant: MckFreq) -> Self { + variant as _ + } +} + +/// MCK / LRCK ratio. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Ratio { + _32x, + _48x, + _64x, + _96x, + _128x, + _192x, + _256x, + _384x, + _512x, +} +impl From for u8 { + fn from(variant: Ratio) -> Self { + variant as _ + } +} + +/// Sample width. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SampleWidth { + _8bit, + _16bit, + _24bit, +} +impl From for u8 { + fn from(variant: SampleWidth) -> Self { + variant as _ + } +} + +/// Alignment of sample within a frame. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Align { + Left, + Right, +} +impl From for bool { + fn from(variant: Align) -> Self { + match variant { + Align::Left => false, + Align::Right => true, + } + } +} + +/// Frame format. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Format { + I2S, + Aligned, +} +impl From for bool { + fn from(variant: Format) -> Self { + match variant { + Format::I2S => false, + Format::Aligned => true, + } + } +} + +/// Enable channels. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Channels { + Left, + Right, + Stereo, +} +impl From for u8 { + fn from(variant: Channels) -> Self { + variant as _ + } +} + +/// I2S events +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum I2SEvent { + RxPtrUpdated, + TxPtrUpdated, + Stopped, +} + +/// Trait to represent valid sample buffers. +pub trait I2SBuffer { + fn ptr(&self) -> u32; + fn maxcnt(&self) -> u32; +} + +impl I2SBuffer for [i8] { + fn ptr(&self) -> u32 { + self.as_ptr() as u32 + } + fn maxcnt(&self) -> u32 { + self.len() as u32 / 4 + } +} + +impl I2SBuffer for [i16] { + fn ptr(&self) -> u32 { + self.as_ptr() as u32 + } + fn maxcnt(&self) -> u32 { + self.len() as u32 / 2 + } +} + +impl I2SBuffer for [i32] { + fn ptr(&self) -> u32 { + self.as_ptr() as u32 + } + fn maxcnt(&self) -> u32 { + self.len() as u32 + } +} + +impl I2SBuffer for [u8] { + fn ptr(&self) -> u32 { + self.as_ptr() as u32 + } + fn maxcnt(&self) -> u32 { + self.len() as u32 / 4 + } +} + +impl I2SBuffer for [u16] { + fn ptr(&self) -> u32 { + self.as_ptr() as u32 + } + fn maxcnt(&self) -> u32 { + self.len() as u32 / 2 + } +} + +impl I2SBuffer for [u32] { + fn ptr(&self) -> u32 { + self.as_ptr() as u32 + } + fn maxcnt(&self) -> u32 { + self.len() as u32 + } +} diff --git a/nrf-hal-common/src/lib.rs b/nrf-hal-common/src/lib.rs index 781f5ede..1e085125 100644 --- a/nrf-hal-common/src/lib.rs +++ b/nrf-hal-common/src/lib.rs @@ -37,6 +37,8 @@ pub mod ecb; pub mod gpio; #[cfg(not(feature = "9160"))] pub mod gpiote; +#[cfg(not(any(feature = "51", feature = "52810")))] +pub mod i2s; #[cfg(not(any(feature = "52810", feature = "9160")))] pub mod lpcomp; #[cfg(not(feature = "9160"))]