From bef75583d90f2a36209416cbab150e01fd10fa34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Mon, 17 Aug 2020 22:50:29 +0200 Subject: [PATCH 01/14] Add I2S module --- Cargo.toml | 2 + examples/i2s-controller-demo/Cargo.toml | 20 + examples/i2s-controller-demo/Embed.toml | 21 + examples/i2s-controller-demo/src/main.rs | 202 ++++++++ examples/i2s-peripheral-demo/Cargo.toml | 20 + examples/i2s-peripheral-demo/Embed.toml | 21 + examples/i2s-peripheral-demo/src/main.rs | 116 +++++ nrf-hal-common/src/i2s.rs | 576 +++++++++++++++++++++++ nrf-hal-common/src/lib.rs | 2 + 9 files changed, 980 insertions(+) create mode 100644 examples/i2s-controller-demo/Cargo.toml create mode 100755 examples/i2s-controller-demo/Embed.toml create mode 100644 examples/i2s-controller-demo/src/main.rs create mode 100644 examples/i2s-peripheral-demo/Cargo.toml create mode 100755 examples/i2s-peripheral-demo/Embed.toml create mode 100644 examples/i2s-peripheral-demo/src/main.rs create mode 100644 nrf-hal-common/src/i2s.rs diff --git a/Cargo.toml b/Cargo.toml index 36bf029b..c58c67e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ members = [ "examples/gpiote-demo", "examples/wdt-demo", "examples/comp-demo", + "examples/i2s-controller-demo", + "examples/i2s-peripheral-demo", ] [profile.dev] 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..c7cabf1a --- /dev/null +++ b/examples/i2s-controller-demo/src/main.rs @@ -0,0 +1,202 @@ +#![no_std] +#![no_main] + +// I2S `controller mode` demo +// Generates Morse code audio signals from text, playing back over I2S +// Tested with nRF52840-DK and a UDA1334a DAC + +use embedded_hal::digital::v2::InputPin; +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, Pin, PullUp}, + gpiote::*, + i2s::*, + }, + 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, bool, U256>, + consumer: Consumer<'static, bool, U256>, + gpiote: Gpiote, + btn1: Pin>, + btn2: Pin>, + btn3: 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); + let btn1 = p0.p0_11.into_pullup_input().degrade(); + let btn2 = p0.p0_12.into_pullup_input().degrade(); + let led1 = p0.p0_13.into_push_pull_output(Level::High).degrade(); + let btn3 = p0.p0_24.into_pullup_input().degrade(); + let btn4 = p0.p0_25.into_pullup_input().degrade(); + + 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.enable_interrupt(I2SEvent::Stopped) + .tx_buffer(&ctx.resources.mute_buf[..]) + .ok(); + i2s.enable().start(); + + 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; + } + + let gpiote = Gpiote::new(ctx.device.GPIOTE); + gpiote.channel0().output_pin(led1).init_high(); + gpiote.port().input_pin(&btn1).low(); + gpiote.port().input_pin(&btn2).low(); + gpiote.port().input_pin(&btn3).low(); + gpiote.port().input_pin(&btn4).low(); + gpiote.port().enable_interrupt(); + + *ctx.resources.queue = Some(Queue::new()); + let (producer, consumer) = ctx.resources.queue.as_mut().unwrap().split(); + + ctx.spawn.tick().ok(); + + init::LateResources { + i2s, + producer, + consumer, + gpiote, + btn1, + btn2, + btn3, + } + } + #[idle] + fn idle(_: idle::Context) -> ! { + rprintln!("Press a button..."); + loop { + cortex_m::asm::wfi(); + } + } + + #[task(binds = I2S, resources = [i2s])] + fn on_i2s(ctx: on_i2s::Context) { + let i2s = ctx.resources.i2s; + if i2s.is_event_triggered(I2SEvent::Stopped) { + i2s.reset_event(I2SEvent::Stopped); + rprintln!("I2S transmission was stopped"); + } + } + + #[task(binds = GPIOTE, resources = [gpiote], 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 = [gpiote, consumer, i2s, signal_buf, mute_buf], schedule = [tick])] + fn tick(ctx: tick::Context) { + let i2s = ctx.resources.i2s; + if let Some(on) = ctx.resources.consumer.dequeue() { + if on { + i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); + ctx.resources.gpiote.channel0().clear(); + } else { + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); + ctx.resources.gpiote.channel0().set(); + } + } else { + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); + ctx.resources.gpiote.channel0().set(); + } + ctx.schedule.tick(ctx.scheduled + 5_000_000.cycles()).ok(); + } + + #[task(resources = [btn1, btn2, btn3, i2s, producer])] + fn debounce(ctx: debounce::Context) { + let msg = if ctx.resources.btn1.is_low().unwrap() { + Some("Radioactivity") + } else if ctx.resources.btn2.is_low().unwrap() { + Some("Is in the air for you and me") + } else { + None + }; + if let Some(m) = msg { + rprintln!("{}", m); + for action in encode(m) { + for _ in 0..action.duration { + ctx.resources + .producer + .enqueue(action.state == State::On) + .ok(); + } + } + } + if ctx.resources.btn3.is_low().unwrap() { + ctx.resources.i2s.stop(); + } else { + ctx.resources.i2s.start(); + } + } + + 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..bfa3b3a2 --- /dev/null +++ b/nrf-hal-common/src/i2s.rs @@ -0,0 +1,576 @@ +//! 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, +} + +impl I2S { + /// Takes ownership of the raw I2S peripheral, returning a safe wrapper i 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 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 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). + #[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() > 4096 { + 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: &mut B) -> Result<(), Error> { + if (buf.ptr() as usize) < SRAM_LOWER || (buf.ptr() as usize) > SRAM_UPPER { + return Err(Error::DMABufferNotInDataMemory); + } + + if buf.maxcnt() > 4096 { + 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 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| unsafe { w.ptr().bits(addr) }); + Ok(()) + } + + /// Sets the size (in 32bit words) of the receive and transmit buffers. + #[inline(always)] + pub fn set_buffersize(&self, n_32bit: u32) -> Result<(), Error> { + if n_32bit > 4096 { + return Err(Error::BufferTooLong); + } + self.i2s.rxtxd.maxcnt.write(|w| unsafe { 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 a307827d..d64838bb 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(feature = "9160"))] pub mod ppi; #[cfg(not(feature = "9160"))] From d3f3c0747a2d6e0d807b44b7c54a59bf5b59c3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Fri, 21 Aug 2020 01:28:09 +0200 Subject: [PATCH 02/14] Correct rxtxd maxcnt --- nrf-hal-common/src/i2s.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nrf-hal-common/src/i2s.rs b/nrf-hal-common/src/i2s.rs index bfa3b3a2..6bd95c98 100644 --- a/nrf-hal-common/src/i2s.rs +++ b/nrf-hal-common/src/i2s.rs @@ -248,7 +248,7 @@ impl I2S { return Err(Error::DMABufferNotInDataMemory); } - if buf.maxcnt() > 4096 { + if buf.maxcnt() > 16_384 { return Err(Error::BufferTooLong); } @@ -271,7 +271,7 @@ impl I2S { return Err(Error::DMABufferNotInDataMemory); } - if buf.maxcnt() > 4096 { + if buf.maxcnt() > 16_384 { return Err(Error::BufferTooLong); } @@ -310,7 +310,7 @@ impl I2S { /// Sets the size (in 32bit words) of the receive and transmit buffers. #[inline(always)] pub fn set_buffersize(&self, n_32bit: u32) -> Result<(), Error> { - if n_32bit > 4096 { + if n_32bit > 16_384 { return Err(Error::BufferTooLong); } self.i2s.rxtxd.maxcnt.write(|w| unsafe { w.bits(n_32bit) }); From e87dfb85c61b93d7913af5e9a89cc776ae65f687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Sun, 23 Aug 2020 13:10:36 +0200 Subject: [PATCH 03/14] Update controller demo to UARTE input --- examples/i2s-controller-demo/src/main.rs | 145 +++++++++++++---------- 1 file changed, 82 insertions(+), 63 deletions(-) diff --git a/examples/i2s-controller-demo/src/main.rs b/examples/i2s-controller-demo/src/main.rs index c7cabf1a..bb603732 100644 --- a/examples/i2s-controller-demo/src/main.rs +++ b/examples/i2s-controller-demo/src/main.rs @@ -2,7 +2,7 @@ #![no_main] // I2S `controller mode` demo -// Generates Morse code audio signals from text, playing back over I2S +// 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; @@ -20,6 +20,9 @@ use { gpio::{Input, Level, Pin, PullUp}, gpiote::*, i2s::*, + pac::{TIMER0, UARTE0}, + timer::Timer, + uarte::*, }, nrf52840_hal as hal, rtic::cyccnt::U32Ext, @@ -38,10 +41,13 @@ const APP: () = { queue: Option>, producer: Producer<'static, bool, U256>, consumer: Consumer<'static, bool, U256>, + #[init(5_000_000)] + speed: u32, + uarte: Uarte, + uarte_timer: Timer, gpiote: Gpiote, btn1: Pin>, btn2: Pin>, - btn3: Pin>, } #[init(resources = [queue, signal_buf, mute_buf], spawn = [tick])] @@ -53,12 +59,8 @@ const APP: () = { rtt_init_print!(); let p0 = hal::gpio::p0::Parts::new(ctx.device.P0); - let btn1 = p0.p0_11.into_pullup_input().degrade(); - let btn2 = p0.p0_12.into_pullup_input().degrade(); - let led1 = p0.p0_13.into_push_pull_output(Level::High).degrade(); - let btn3 = p0.p0_24.into_pullup_input().degrade(); - let btn4 = p0.p0_25.into_pullup_input().degrade(); + // 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(); @@ -72,9 +74,7 @@ const APP: () = { None, Some(&sdout_pin), ); - i2s.enable_interrupt(I2SEvent::Stopped) - .tx_buffer(&ctx.resources.mute_buf[..]) - .ok(); + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); i2s.enable().start(); let signal_buf = ctx.resources.signal_buf; @@ -84,17 +84,37 @@ const APP: () = { signal_buf[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; } + // Configure LED and buttons + let led1 = p0.p0_13.into_push_pull_output(Level::High).degrade(); + 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.channel0().output_pin(led1).init_high(); gpiote.port().input_pin(&btn1).low(); gpiote.port().input_pin(&btn2).low(); - gpiote.port().input_pin(&btn3).low(); - gpiote.port().input_pin(&btn4).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 { @@ -104,74 +124,73 @@ const APP: () = { gpiote, btn1, btn2, - btn3, + uarte, + uarte_timer: Timer::new(ctx.device.TIMER0), } } - #[idle] - fn idle(_: idle::Context) -> ! { - rprintln!("Press a button..."); + #[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; 255][..]; loop { - cortex_m::asm::wfi(); - } - } - - #[task(binds = I2S, resources = [i2s])] - fn on_i2s(ctx: on_i2s::Context) { - let i2s = ctx.resources.i2s; - if i2s.is_event_triggered(I2SEvent::Stopped) { - i2s.reset_event(I2SEvent::Stopped); - rprintln!("I2S transmission was stopped"); + match uarte.read_timeout(uarte_rx_buf, uarte_timer, 200_000) { + 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 == State::On).ok(); + } + } + } + } + _ => {} + } } } - #[task(binds = GPIOTE, resources = [gpiote], 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 = [gpiote, consumer, i2s, signal_buf, mute_buf], schedule = [tick])] + #[task(resources = [consumer, i2s, signal_buf, mute_buf, gpiote, speed], schedule = [tick])] fn tick(ctx: tick::Context) { let i2s = ctx.resources.i2s; - if let Some(on) = ctx.resources.consumer.dequeue() { - if on { - i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); - ctx.resources.gpiote.channel0().clear(); - } else { - i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); - ctx.resources.gpiote.channel0().set(); + if let Some(is_on) = ctx.resources.consumer.dequeue() { + match is_on { + true => { + i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); + ctx.resources.gpiote.channel0().clear(); + } + false => { + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); + ctx.resources.gpiote.channel0().set(); + } } } else { i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); ctx.resources.gpiote.channel0().set(); } - ctx.schedule.tick(ctx.scheduled + 5_000_000.cycles()).ok(); + ctx.schedule + .tick(ctx.scheduled + ctx.resources.speed.cycles()) + .ok(); } - #[task(resources = [btn1, btn2, btn3, i2s, producer])] + #[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) { - let msg = if ctx.resources.btn1.is_low().unwrap() { - Some("Radioactivity") - } else if ctx.resources.btn2.is_low().unwrap() { - Some("Is in the air for you and me") - } else { - None - }; - if let Some(m) = msg { - rprintln!("{}", m); - for action in encode(m) { - for _ in 0..action.duration { - ctx.resources - .producer - .enqueue(action.state == State::On) - .ok(); - } - } + if ctx.resources.btn1.is_low().unwrap() { + rprintln!("Go slower"); + *ctx.resources.speed += 600_000; } - if ctx.resources.btn3.is_low().unwrap() { - ctx.resources.i2s.stop(); - } else { - ctx.resources.i2s.start(); + if ctx.resources.btn2.is_low().unwrap() { + rprintln!("Go faster"); + *ctx.resources.speed -= 600_000; } } From 5c2a0e28b33a0231827afb6abc808af03d215286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Sun, 23 Aug 2020 17:05:44 +0200 Subject: [PATCH 04/14] Reduce UARTE buffer size, handle buffer full case --- examples/i2s-controller-demo/src/main.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/i2s-controller-demo/src/main.rs b/examples/i2s-controller-demo/src/main.rs index bb603732..6aca8882 100644 --- a/examples/i2s-controller-demo/src/main.rs +++ b/examples/i2s-controller-demo/src/main.rs @@ -135,9 +135,19 @@ const APP: () = { uarte_timer, producer, } = ctx.resources; - let uarte_rx_buf = &mut [0u8; 255][..]; + let uarte_rx_buf = &mut [0u8; 32][..]; loop { match uarte.read_timeout(uarte_rx_buf, uarte_timer, 200_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 == State::On).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); From 78b8532ccfbae77db0529d990cdf67c0c89c877a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Sun, 23 Aug 2020 17:35:01 +0200 Subject: [PATCH 05/14] Controller demo: Use State in queue --- examples/i2s-controller-demo/src/main.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/i2s-controller-demo/src/main.rs b/examples/i2s-controller-demo/src/main.rs index 6aca8882..0bbadbab 100644 --- a/examples/i2s-controller-demo/src/main.rs +++ b/examples/i2s-controller-demo/src/main.rs @@ -38,9 +38,9 @@ const APP: () = { #[init([0; 32])] mute_buf: [i16; 32], #[init(None)] - queue: Option>, - producer: Producer<'static, bool, U256>, - consumer: Consumer<'static, bool, U256>, + queue: Option>, + producer: Producer<'static, State, U256>, + consumer: Consumer<'static, State, U256>, #[init(5_000_000)] speed: u32, uarte: Uarte, @@ -135,15 +135,15 @@ const APP: () = { uarte_timer, producer, } = ctx.resources; - let uarte_rx_buf = &mut [0u8; 32][..]; + let uarte_rx_buf = &mut [0u8; 64][..]; loop { - match uarte.read_timeout(uarte_rx_buf, uarte_timer, 200_000) { + 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 == State::On).ok(); + producer.enqueue(action.state).ok(); } } } @@ -153,7 +153,7 @@ const APP: () = { rprintln!("{}", msg); for action in encode(msg) { for _ in 0..action.duration { - producer.enqueue(action.state == State::On).ok(); + producer.enqueue(action.state).ok(); } } } @@ -166,13 +166,13 @@ const APP: () = { #[task(resources = [consumer, i2s, signal_buf, mute_buf, gpiote, speed], schedule = [tick])] fn tick(ctx: tick::Context) { let i2s = ctx.resources.i2s; - if let Some(is_on) = ctx.resources.consumer.dequeue() { - match is_on { - true => { + if let Some(state) = ctx.resources.consumer.dequeue() { + match state { + State::On => { i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); ctx.resources.gpiote.channel0().clear(); } - false => { + State::Off => { i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); ctx.resources.gpiote.channel0().set(); } From 13a0aacedde21811c4d839001aa491d150e3fe13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Sun, 23 Aug 2020 22:09:50 +0200 Subject: [PATCH 06/14] Cleanup demo & add comments --- examples/i2s-controller-demo/src/main.rs | 27 ++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/i2s-controller-demo/src/main.rs b/examples/i2s-controller-demo/src/main.rs index 0bbadbab..8f9e36f1 100644 --- a/examples/i2s-controller-demo/src/main.rs +++ b/examples/i2s-controller-demo/src/main.rs @@ -77,6 +77,7 @@ const APP: () = { 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 { @@ -128,6 +129,7 @@ const APP: () = { uarte_timer: Timer::new(ctx.device.TIMER0), } } + #[idle(resources=[uarte, uarte_timer, producer])] fn idle(ctx: idle::Context) -> ! { let idle::Resources { @@ -166,20 +168,19 @@ const APP: () = { #[task(resources = [consumer, i2s, signal_buf, mute_buf, gpiote, speed], schedule = [tick])] fn tick(ctx: tick::Context) { let i2s = ctx.resources.i2s; - if let Some(state) = ctx.resources.consumer.dequeue() { - match state { - State::On => { - i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); - ctx.resources.gpiote.channel0().clear(); - } - State::Off => { - i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); - ctx.resources.gpiote.channel0().set(); - } + match ctx.resources.consumer.dequeue() { + Some(State::On) => { + // Move TX pointer to signal buffer (sound ON) + i2s.tx_buffer(&ctx.resources.signal_buf[..]).ok(); + // Set LED on + ctx.resources.gpiote.channel0().clear(); + } + _ => { + // Move TX pointer to silent buffer (sound OFF) + i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); + // Set LED off + ctx.resources.gpiote.channel0().set(); } - } else { - i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); - ctx.resources.gpiote.channel0().set(); } ctx.schedule .tick(ctx.scheduled + ctx.resources.speed.cycles()) From d87e088fe28b73776ee2ffdc7655b7b6395e7cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Sun, 23 Aug 2020 22:29:09 +0200 Subject: [PATCH 07/14] Cleanup demo --- examples/i2s-controller-demo/src/main.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/examples/i2s-controller-demo/src/main.rs b/examples/i2s-controller-demo/src/main.rs index 8f9e36f1..b487839b 100644 --- a/examples/i2s-controller-demo/src/main.rs +++ b/examples/i2s-controller-demo/src/main.rs @@ -5,7 +5,7 @@ // 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; +use embedded_hal::digital::v2::{InputPin, OutputPin}; use heapless::{ consts::*, spsc::{Consumer, Producer, Queue}, @@ -17,7 +17,7 @@ use { sync::atomic::{compiler_fence, Ordering}, }, hal::{ - gpio::{Input, Level, Pin, PullUp}, + gpio::{Input, Level, Output, Pin, PullUp, PushPull}, gpiote::*, i2s::*, pac::{TIMER0, UARTE0}, @@ -48,6 +48,7 @@ const APP: () = { gpiote: Gpiote, btn1: Pin>, btn2: Pin>, + led: Pin>, } #[init(resources = [queue, signal_buf, mute_buf], spawn = [tick])] @@ -85,13 +86,10 @@ const APP: () = { signal_buf[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; } - // Configure LED and buttons - let led1 = p0.p0_13.into_push_pull_output(Level::High).degrade(); + // 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.channel0().output_pin(led1).init_high(); gpiote.port().input_pin(&btn1).low(); gpiote.port().input_pin(&btn2).low(); gpiote.port().enable_interrupt(); @@ -125,6 +123,7 @@ const APP: () = { gpiote, btn1, btn2, + led: p0.p0_13.into_push_pull_output(Level::High).degrade(), uarte, uarte_timer: Timer::new(ctx.device.TIMER0), } @@ -165,21 +164,19 @@ const APP: () = { } } - #[task(resources = [consumer, i2s, signal_buf, mute_buf, gpiote, speed], schedule = [tick])] + #[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(); - // Set LED on - ctx.resources.gpiote.channel0().clear(); + ctx.resources.led.set_low().ok(); } _ => { // Move TX pointer to silent buffer (sound OFF) i2s.tx_buffer(&ctx.resources.mute_buf[..]).ok(); - // Set LED off - ctx.resources.gpiote.channel0().set(); + ctx.resources.led.set_high().ok(); } } ctx.schedule From 02619774549e01016bb6ed6847d8db02c2e7eacb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Wed, 26 Aug 2020 08:34:41 +0200 Subject: [PATCH 08/14] Merge upstream --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71a356a9..28e44f67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,13 +16,10 @@ members = [ "examples/lpcomp-demo", "examples/qdec-demo", "examples/comp-demo", -<<<<<<< HEAD "examples/i2s-controller-demo", "examples/i2s-peripheral-demo", -======= "examples/twim-demo", "examples/twis-demo", ->>>>>>> master ] [profile.dev] From f3a4bccfe823d38a9cc59c1692d0535ac3796a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Thu, 27 Aug 2020 00:39:28 +0200 Subject: [PATCH 09/14] Add set_ prefix to setters, add docs --- nrf-hal-common/src/i2s.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nrf-hal-common/src/i2s.rs b/nrf-hal-common/src/i2s.rs index 6bd95c98..bece17a2 100644 --- a/nrf-hal-common/src/i2s.rs +++ b/nrf-hal-common/src/i2s.rs @@ -18,7 +18,7 @@ pub struct I2S { } impl I2S { - /// Takes ownership of the raw I2S peripheral, returning a safe wrapper i controller mode. + /// 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>>, @@ -172,14 +172,14 @@ impl I2S { /// Enables/disables I2S transmission (TX). #[inline(always)] - pub fn tx_enabled(&self, enabled: bool) -> &Self { + 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 rx_enabled(&self, enabled: bool) -> &Self { + pub fn set_rx_enabled(&self, enabled: bool) -> &Self { self.i2s.config.rxen.write(|w| w.rxen().bit(enabled)); self } @@ -242,6 +242,7 @@ impl I2S { } /// 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 { From 36c6d9b5ab814105238d90a66439c953990259bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Thu, 27 Aug 2020 00:41:07 +0200 Subject: [PATCH 10/14] Add 'static lifetime requirement for RX buffer --- nrf-hal-common/src/i2s.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nrf-hal-common/src/i2s.rs b/nrf-hal-common/src/i2s.rs index bece17a2..e5782095 100644 --- a/nrf-hal-common/src/i2s.rs +++ b/nrf-hal-common/src/i2s.rs @@ -267,7 +267,7 @@ impl I2S { /// Sets the receive data buffer (RX). #[inline(always)] - pub fn rx_buffer(&self, buf: &mut B) -> Result<(), Error> { + 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); } From 859ecec9667f386ae03c1c788439b6f223678a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Thu, 27 Aug 2020 00:47:30 +0200 Subject: [PATCH 11/14] Use constant for maximum MAXCNT, with docs --- nrf-hal-common/src/i2s.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nrf-hal-common/src/i2s.rs b/nrf-hal-common/src/i2s.rs index e5782095..a1867247 100644 --- a/nrf-hal-common/src/i2s.rs +++ b/nrf-hal-common/src/i2s.rs @@ -17,6 +17,9 @@ 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( @@ -249,7 +252,7 @@ impl I2S { return Err(Error::DMABufferNotInDataMemory); } - if buf.maxcnt() > 16_384 { + if buf.maxcnt() > MAX_DMA_MAXCNT { return Err(Error::BufferTooLong); } @@ -272,7 +275,7 @@ impl I2S { return Err(Error::DMABufferNotInDataMemory); } - if buf.maxcnt() > 16_384 { + if buf.maxcnt() > MAX_DMA_MAXCNT { return Err(Error::BufferTooLong); } @@ -311,7 +314,7 @@ impl I2S { /// Sets the size (in 32bit words) of the receive and transmit buffers. #[inline(always)] pub fn set_buffersize(&self, n_32bit: u32) -> Result<(), Error> { - if n_32bit > 16_384 { + if n_32bit > MAX_DMA_MAXCNT { return Err(Error::BufferTooLong); } self.i2s.rxtxd.maxcnt.write(|w| unsafe { w.bits(n_32bit) }); From ddb49518e0ea28e5fdf9ae03a60466810c3d565d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Thu, 27 Aug 2020 00:56:22 +0200 Subject: [PATCH 12/14] Mark set_rx_ptr function as unsafe --- nrf-hal-common/src/i2s.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nrf-hal-common/src/i2s.rs b/nrf-hal-common/src/i2s.rs index a1867247..3ed9325c 100644 --- a/nrf-hal-common/src/i2s.rs +++ b/nrf-hal-common/src/i2s.rs @@ -303,11 +303,11 @@ impl I2S { /// Sets the receive buffer RAM start address. #[inline(always)] - pub fn set_rx_ptr(&self, addr: u32) -> Result<(), Error> { + 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| unsafe { w.ptr().bits(addr) }); + self.i2s.rxd.ptr.write(|w| w.ptr().bits(addr)); Ok(()) } From ed40e4184da095b4d2bda33f4c818753a8b86685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Thu, 27 Aug 2020 22:12:07 +0200 Subject: [PATCH 13/14] trigger GitHub checks From 0d609fbe5780ffc612e5af09ed7a40fea648a02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Alse=CC=81r?= Date: Fri, 28 Aug 2020 16:36:51 +0200 Subject: [PATCH 14/14] Mark set_buffersize as unsafe --- nrf-hal-common/src/i2s.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nrf-hal-common/src/i2s.rs b/nrf-hal-common/src/i2s.rs index 3ed9325c..0915abe1 100644 --- a/nrf-hal-common/src/i2s.rs +++ b/nrf-hal-common/src/i2s.rs @@ -313,11 +313,11 @@ impl I2S { /// Sets the size (in 32bit words) of the receive and transmit buffers. #[inline(always)] - pub fn set_buffersize(&self, n_32bit: u32) -> Result<(), Error> { + 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| unsafe { w.bits(n_32bit) }); + self.i2s.rxtxd.maxcnt.write(|w| w.bits(n_32bit)); Ok(()) }