From cecd77938c694ff2bad2a259ff64f2f468dcb04a Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Wed, 9 Nov 2022 19:14:43 +0100 Subject: [PATCH 01/20] Draft: Initial support for I2S with a working example. Co-authored-by: @brainstorm --- embassy-nrf/Cargo.toml | 1 + embassy-nrf/src/chips/nrf52840.rs | 5 + embassy-nrf/src/i2s.rs | 403 ++++++++++++++++++++++++++++++ embassy-nrf/src/lib.rs | 2 + examples/nrf/Cargo.toml | 2 +- examples/nrf/src/bin/i2s.rs | 48 ++++ 6 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 embassy-nrf/src/i2s.rs create mode 100644 examples/nrf/src/bin/i2s.rs diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 67b6bec40..aa1576fd4 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -48,6 +48,7 @@ nrf9160-s = ["_nrf9160"] nrf9160-ns = ["_nrf9160"] gpiote = [] +i2s = [] time-driver-rtc1 = ["_time-driver"] # Features starting with `_` are for internal use only. They're not intended diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 4beadfba8..cf800c7b4 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -164,6 +164,9 @@ embassy_hal_common::peripherals! { // PDM PDM, + + // I2S + I2S, } #[cfg(feature = "nightly")] @@ -285,6 +288,8 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); +impl_i2s!(I2S, I2S, I2S); + pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs new file mode 100644 index 000000000..0199ac615 --- /dev/null +++ b/embassy-nrf/src/i2s.rs @@ -0,0 +1,403 @@ +#![macro_use] + +//! I2S + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::drop::OnDrop; +use embassy_hal_common::{into_ref, PeripheralRef}; +use pac::i2s::config::mcken; + +use crate::{pac, Peripheral}; +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; +use crate::gpio::sealed::Pin as _; + +// TODO: Define those in lib.rs somewhere else +// +// I2S EasyDMA MAXCNT bit length = 14 +const MAX_DMA_MAXCNT: u32 = 1 << 14; + +// Limits for Easy DMA - it can only read from data ram +pub const SRAM_LOWER: usize = 0x2000_0000; +pub const SRAM_UPPER: usize = 0x3000_0000; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + BufferTooLong, + BufferZeroLength, + DMABufferNotInDataMemory, + BufferMisaligned, + // TODO: add other error variants. +} + +#[derive(Clone)] +#[non_exhaustive] +pub struct Config { + pub ratio: Ratio, + pub sample_width: SampleWidth, + pub align: Align, + pub format: Format, + pub channels: Channels, +} + +impl Default for Config { + fn default() -> Self { + Self { + ratio: Ratio::_32x, + sample_width: SampleWidth::_16bit, + align: Align::Left, + format: Format::I2S, + channels: Channels::Stereo, + } + } +} + +/// 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 _ + } +} + +#[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 { + Stereo, + Left, + Right, +} + +impl From for u8 { + fn from(variant: Channels) -> Self { + variant as _ + } +} + +/// 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 = 0x20000000, +// _32MDiv10 = 0x18000000, +// _32MDiv11 = 0x16000000, +// _32MDiv15 = 0x11000000, +// _32MDiv16 = 0x10000000, +// _32MDiv21 = 0x0C000000, +// _32MDiv23 = 0x0B000000, +// _32MDiv30 = 0x08800000, +// _32MDiv31 = 0x08400000, +// _32MDiv32 = 0x08000000, +// _32MDiv42 = 0x06000000, +// _32MDiv63 = 0x04100000, +// _32MDiv125 = 0x020C0000, +// } + + +/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. +/// +/// For more details about EasyDMA, consult the module documentation. +pub struct I2s<'d, T: Instance> { + output: I2sOutput<'d, T>, + input: I2sInput<'d, T>, +} + +/// Transmitter interface to the UARTE peripheral obtained +/// via [Uarte]::split. +pub struct I2sOutput<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +/// Receiver interface to the UARTE peripheral obtained +/// via [Uarte]::split. +pub struct I2sInput<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> I2s<'d, T> { + /// Create a new I2S + pub fn new( + i2s: impl Peripheral

+ 'd, + // irq: impl Peripheral

+ 'd, + mck: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

+ 'd, + sdin: impl Peripheral

+ 'd, + sdout: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(mck, sck, lrck, sdin, sdout); + Self::new_inner( + i2s, + // irq, + mck.map_into(), sck.map_into(), lrck.map_into(), sdin.map_into(), sdout.map_into(), config) + } + + fn new_inner( + i2s: impl Peripheral

+ 'd, + // irq: impl Peripheral

+ 'd, + mck: PeripheralRef<'d, AnyPin>, + sck: PeripheralRef<'d, AnyPin>, + lrck: PeripheralRef<'d, AnyPin>, + sdin: PeripheralRef<'d, AnyPin>, + sdout: PeripheralRef<'d, AnyPin>, + config: Config, + ) -> Self { + into_ref!( + i2s, + // irq, + mck, sck, lrck, sdin, sdout); + + let r = T::regs(); + + // TODO get configuration rather than hardcoding ratio, swidth, align, format, channels + + r.config.mcken.write(|w| w.mcken().enabled()); + r.config.mckfreq.write(|w| w.mckfreq()._32mdiv16()); + r.config.ratio.write(|w| w.ratio()._192x()); + r.config.mode.write(|w| w.mode().master()); + r.config.swidth.write(|w| w.swidth()._16bit()); + r.config.align.write(|w| w.align().left()); + r.config.format.write(|w| w.format().i2s()); + r.config.channels.write(|w| w.channels().stereo()); + + r.psel.mck.write(|w| { + unsafe { w.bits(mck.psel_bits()) }; + w.connect().connected() + }); + + r.psel.sck.write(|w| { + unsafe { w.bits(sck.psel_bits()) }; + w.connect().connected() + }); + + r.psel.lrck.write(|w| { + unsafe { w.bits(lrck.psel_bits()) }; + w.connect().connected() + }); + + r.psel.sdin.write(|w| { + unsafe { w.bits(sdin.psel_bits()) }; + w.connect().connected() + }); + + r.psel.sdout.write(|w| { + unsafe { w.bits(sdout.psel_bits()) }; + w.connect().connected() + }); + + r.enable.write(|w| w.enable().enabled()); + + Self { + output: I2sOutput { + _p: unsafe { i2s.clone_unchecked() }, + }, + input: I2sInput { _p: i2s }, + } + } + + /// Enables the I2S module. + #[inline(always)] + pub fn enable(&self) -> &Self { + let r = T::regs(); + r.enable.write(|w| w.enable().enabled()); + self + } + + /// Disables the I2S module. + #[inline(always)] + pub fn disable(&self) -> &Self { + let r = T::regs(); + r.enable.write(|w| w.enable().disabled()); + self + } + + /// Starts I2S transfer. + #[inline(always)] + pub fn start(&self) -> &Self { + let r = T::regs(); + self.enable(); + r.tasks_start.write(|w| unsafe { w.bits(1) }); + self + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub fn stop(&self) -> &Self { + todo!() + } + + /// Enables/disables I2S transmission (TX). + #[inline(always)] + pub fn set_tx_enabled(&self, enabled: bool) -> &Self { + let r = T::regs(); + r.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 { + let r = T::regs(); + r.config.rxen.write(|w| w.rxen().bit(enabled)); + self + } + + /// Transmits the given `tx_buffer`. + /// Buffer address must be 4 byte aligned and located in RAM. + /// Returns a value that represents the in-progress DMA transfer. + // TODO Define a better interface for the input buffer + #[allow(unused_mut)] + pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { + self.output.tx(ptr, len).await + } +} + +impl<'d, T: Instance> I2sOutput<'d, T> { + /// Transmits the given `tx_buffer`. + /// Buffer address must be 4 byte aligned and located in RAM. + /// Returns a value that represents the in-progress DMA transfer. + // TODO Define a better interface for the input buffer + pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { + if ptr as u32 % 4 != 0 { + return Err(Error::BufferMisaligned); + } + let maxcnt = (len / (core::mem::size_of::() / core::mem::size_of::())) as u32; + if maxcnt > MAX_DMA_MAXCNT { + return Err(Error::BufferTooLong); + } + if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { + return Err(Error::DMABufferNotInDataMemory); + } + + let r = T::regs(); + let _s = T::state(); + + // TODO we can not progress until the last buffer written in TXD.PTR + // has started the transmission. + // We can use some sync primitive from `embassy-sync`. + + r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + + Ok(()) + } +} + +pub(crate) mod sealed { + use core::sync::atomic::AtomicU8; + + use embassy_sync::waitqueue::AtomicWaker; + + use super::*; + + pub struct State { + pub input_waker: AtomicWaker, + pub output_waker: AtomicWaker, + pub buffers_refcount: AtomicU8, + } + impl State { + pub const fn new() -> Self { + Self { + input_waker: AtomicWaker::new(), + output_waker: AtomicWaker::new(), + buffers_refcount: AtomicU8::new(0), + } + } + } + + pub trait Instance { + fn regs() -> &'static pac::i2s::RegisterBlock; + fn state() -> &'static State; + } +} + +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + type Interrupt: Interrupt; +} + +macro_rules! impl_i2s { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::i2s::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::i2s::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::i2s::sealed::State { + static STATE: crate::i2s::sealed::State = crate::i2s::sealed::State::new(); + &STATE + } + } + impl crate::i2s::Instance for peripherals::$type { + type Interrupt = crate::interrupt::$irq; + } + }; +} + diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index bc70fc2f6..ac797db9b 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -74,6 +74,8 @@ pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; +// #[cfg(all(feature = "i2s", feature = "nrf52840"))] +pub mod i2s; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod nvmc; #[cfg(any( diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index c633f82f5..a79044e8e 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -14,7 +14,7 @@ embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "i2s", "unstable-pac"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } embedded-io = "0.3.1" diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs new file mode 100644 index 000000000..556f6b2e2 --- /dev/null +++ b/examples/nrf/src/bin/i2s.rs @@ -0,0 +1,48 @@ +// Example inspired by RTIC's I2S demo: https://github.com/nrf-rs/nrf-hal/blob/master/examples/i2s-controller-demo/src/main.rs + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::{i2s}; +use {defmt_rtt as _, panic_probe as _}; + +#[repr(align(4))] +pub struct Aligned(T); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let config = i2s::Config::default(); + + let mut i2s = i2s::I2s::new(p.I2S, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); + + let mut signal_buf: Aligned<[i16; 32]> = Aligned([0i16; 32]); + let len = signal_buf.0.len() / 2; + for x in 0..len { + signal_buf.0[2 * x] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; + signal_buf.0[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; + } + + let ptr = &signal_buf.0 as *const i16 as *const u8; + let len = signal_buf.0.len() * core::mem::size_of::(); + + i2s.start(); + i2s.set_tx_enabled(true); + + loop { + i2s.tx(ptr, len).await; + } +} + +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 +} From 3760b60db382c8c4f8d7067a8d472affa6db928b Mon Sep 17 00:00:00 2001 From: Roman Valls Guimera Date: Wed, 9 Nov 2022 21:58:56 +0100 Subject: [PATCH 02/20] Make bors grin ;) --- embassy-nrf/src/i2s.rs | 47 +++++++++++++++++++++---------------- examples/nrf/src/bin/i2s.rs | 17 +++++++------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 0199ac615..e0fe6a6eb 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -2,18 +2,19 @@ //! I2S -use core::future::poll_fn; -use core::sync::atomic::{compiler_fence, Ordering}; -use core::task::Poll; +//use core::future::poll_fn; +//use core::sync::atomic::{compiler_fence, Ordering}; +//use core::task::Poll; -use embassy_hal_common::drop::OnDrop; +//use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -use pac::i2s::config::mcken; -use crate::{pac, Peripheral}; -use crate::interrupt::{Interrupt, InterruptExt}; -use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; -use crate::gpio::sealed::Pin as _; +//use crate::pac::i2s::config::mcken; + +//use crate::gpio::sealed::Pin as _; +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::Interrupt; +use crate::Peripheral; // TODO: Define those in lib.rs somewhere else // @@ -161,13 +162,12 @@ pub enum Mode { // _32MDiv125 = 0x020C0000, // } - /// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. /// /// For more details about EasyDMA, consult the module documentation. pub struct I2s<'d, T: Instance> { output: I2sOutput<'d, T>, - input: I2sInput<'d, T>, + _input: I2sInput<'d, T>, } /// Transmitter interface to the UARTE peripheral obtained @@ -198,7 +198,13 @@ impl<'d, T: Instance> I2s<'d, T> { Self::new_inner( i2s, // irq, - mck.map_into(), sck.map_into(), lrck.map_into(), sdin.map_into(), sdout.map_into(), config) + mck.map_into(), + sck.map_into(), + lrck.map_into(), + sdin.map_into(), + sdout.map_into(), + config, + ) } fn new_inner( @@ -209,12 +215,12 @@ impl<'d, T: Instance> I2s<'d, T> { lrck: PeripheralRef<'d, AnyPin>, sdin: PeripheralRef<'d, AnyPin>, sdout: PeripheralRef<'d, AnyPin>, - config: Config, + _config: Config, ) -> Self { into_ref!( - i2s, - // irq, - mck, sck, lrck, sdin, sdout); + i2s, // irq, + mck, sck, lrck, sdin, sdout + ); let r = T::regs(); @@ -260,7 +266,7 @@ impl<'d, T: Instance> I2s<'d, T> { output: I2sOutput { _p: unsafe { i2s.clone_unchecked() }, }, - input: I2sInput { _p: i2s }, + _input: I2sInput { _p: i2s }, } } @@ -357,7 +363,7 @@ pub(crate) mod sealed { use embassy_sync::waitqueue::AtomicWaker; - use super::*; + //use super::*; pub struct State { pub input_waker: AtomicWaker, @@ -375,7 +381,7 @@ pub(crate) mod sealed { } pub trait Instance { - fn regs() -> &'static pac::i2s::RegisterBlock; + fn regs() -> &'static crate::pac::i2s::RegisterBlock; fn state() -> &'static State; } } @@ -384,6 +390,8 @@ pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { type Interrupt: Interrupt; } +// TODO: Unsure why this macro is flagged as unused by CI when in fact it's used elsewhere? +#[allow(unused_macros)] macro_rules! impl_i2s { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::i2s::sealed::Instance for peripherals::$type { @@ -400,4 +408,3 @@ macro_rules! impl_i2s { } }; } - diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index 556f6b2e2..60cde3b65 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -4,9 +4,9 @@ #![no_main] #![feature(type_alias_impl_trait)] -use defmt::*; +//use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::{i2s}; +use embassy_nrf::i2s; use {defmt_rtt as _, panic_probe as _}; #[repr(align(4))] @@ -18,7 +18,7 @@ async fn main(_spawner: Spawner) { let config = i2s::Config::default(); let mut i2s = i2s::I2s::new(p.I2S, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); - + let mut signal_buf: Aligned<[i16; 32]> = Aligned([0i16; 32]); let len = signal_buf.0.len() / 2; for x in 0..len { @@ -31,18 +31,19 @@ async fn main(_spawner: Spawner) { i2s.start(); i2s.set_tx_enabled(true); - + loop { - i2s.tx(ptr, len).await; + match i2s.tx(ptr, len).await { + Ok(_) => todo!(), + Err(_) => todo!(), + }; } } 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) + - ((2 * periods * (x + phase + length / (4 * periods)) * amplitude / length) % (2 * amplitude) - amplitude) .abs() - amplitude / 2 } From 356beabc3b11d78612c6958d1cfe542209e43558 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Wed, 9 Nov 2022 22:47:55 +0100 Subject: [PATCH 03/20] Apply config --- embassy-nrf/src/i2s.rs | 85 ++++++++++++++++++++++++++++--------- examples/nrf/src/bin/i2s.rs | 2 +- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index e0fe6a6eb..8752dfdef 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -9,11 +9,10 @@ //use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -//use crate::pac::i2s::config::mcken; - //use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::Interrupt; +use crate::pac::i2s::CONFIG; use crate::Peripheral; // TODO: Define those in lib.rs somewhere else @@ -40,7 +39,7 @@ pub enum Error { #[non_exhaustive] pub struct Config { pub ratio: Ratio, - pub sample_width: SampleWidth, + pub swidth: SampleWidth, pub align: Align, pub format: Format, pub channels: Channels, @@ -50,7 +49,7 @@ impl Default for Config { fn default() -> Self { Self { ratio: Ratio::_32x, - sample_width: SampleWidth::_16bit, + swidth: SampleWidth::_16bit, align: Align::Left, format: Format::I2S, channels: Channels::Stereo, @@ -165,7 +164,7 @@ pub enum Mode { /// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. /// /// For more details about EasyDMA, consult the module documentation. -pub struct I2s<'d, T: Instance> { +pub struct I2S<'d, T: Instance> { output: I2sOutput<'d, T>, _input: I2sInput<'d, T>, } @@ -182,7 +181,7 @@ pub struct I2sInput<'d, T: Instance> { _p: PeripheralRef<'d, T>, } -impl<'d, T: Instance> I2s<'d, T> { +impl<'d, T: Instance> I2S<'d, T> { /// Create a new I2S pub fn new( i2s: impl Peripheral

+ 'd, @@ -215,25 +214,13 @@ impl<'d, T: Instance> I2s<'d, T> { lrck: PeripheralRef<'d, AnyPin>, sdin: PeripheralRef<'d, AnyPin>, sdout: PeripheralRef<'d, AnyPin>, - _config: Config, + config: Config, ) -> Self { - into_ref!( - i2s, // irq, - mck, sck, lrck, sdin, sdout - ); + into_ref!(i2s, /* irq, */ mck, sck, lrck, sdin, sdout); let r = T::regs(); - // TODO get configuration rather than hardcoding ratio, swidth, align, format, channels - - r.config.mcken.write(|w| w.mcken().enabled()); - r.config.mckfreq.write(|w| w.mckfreq()._32mdiv16()); - r.config.ratio.write(|w| w.ratio()._192x()); - r.config.mode.write(|w| w.mode().master()); - r.config.swidth.write(|w| w.swidth()._16bit()); - r.config.align.write(|w| w.align().left()); - r.config.format.write(|w| w.format().i2s()); - r.config.channels.write(|w| w.channels().stereo()); + Self::apply_config(&r.config, &config); r.psel.mck.write(|w| { unsafe { w.bits(mck.psel_bits()) }; @@ -325,6 +312,62 @@ impl<'d, T: Instance> I2s<'d, T> { pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { self.output.tx(ptr, len).await } + + fn apply_config(c: &CONFIG, config: &Config) { + // TODO support slave too + c.mcken.write(|w| w.mcken().enabled()); + c.mckfreq.write(|w| w.mckfreq()._32mdiv16()); + c.mode.write(|w| w.mode().master()); + + c.ratio.write(|w| { + let ratio = w.ratio(); + match config.ratio { + Ratio::_32x => ratio._32x(), + Ratio::_48x => ratio._48x(), + Ratio::_64x => ratio._64x(), + Ratio::_96x => ratio._96x(), + Ratio::_128x => ratio._128x(), + Ratio::_192x => ratio._192x(), + Ratio::_256x => ratio._256x(), + Ratio::_384x => ratio._384x(), + Ratio::_512x => ratio._512x(), + } + }); + + c.swidth.write(|w| { + let swidth = w.swidth(); + match config.swidth { + SampleWidth::_8bit => swidth._8bit(), + SampleWidth::_16bit => swidth._16bit(), + SampleWidth::_24bit => swidth._24bit(), + } + }); + + c.align.write(|w| { + let align = w.align(); + match config.align { + Align::Left => align.left(), + Align::Right => align.right(), + } + }); + + c.format.write(|w| { + let format = w.format(); + match config.format { + Format::I2S => format.i2s(), + Format::Aligned => format.aligned(), + } + }); + + c.channels.write(|w| { + let channels = w.channels(); + match config.channels { + Channels::Stereo => channels.stereo(), + Channels::Left => channels.left(), + Channels::Right => channels.right(), + } + }); + } } impl<'d, T: Instance> I2sOutput<'d, T> { diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index 60cde3b65..a395c7141 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); let config = i2s::Config::default(); - let mut i2s = i2s::I2s::new(p.I2S, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); + let mut i2s = i2s::I2S::new(p.I2S, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); let mut signal_buf: Aligned<[i16; 32]> = Aligned([0i16; 32]); let len = signal_buf.0.len() / 2; From 5a64bf651c66f2da16cd3ae20ed9ba2489f40d7a Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Thu, 10 Nov 2022 00:10:42 +0100 Subject: [PATCH 04/20] Buffer trait. Simpler config. --- embassy-nrf/src/i2s.rs | 122 +++++++++++++++++++----------------- examples/nrf/src/bin/i2s.rs | 7 +-- 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 8752dfdef..3f5491ee3 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -284,7 +284,7 @@ impl<'d, T: Instance> I2S<'d, T> { /// Stops the I2S transfer and waits until it has stopped. #[inline(always)] - pub fn stop(&self) -> &Self { + pub async fn stop(&self) -> &Self { todo!() } @@ -307,10 +307,12 @@ impl<'d, T: Instance> I2S<'d, T> { /// Transmits the given `tx_buffer`. /// Buffer address must be 4 byte aligned and located in RAM. /// Returns a value that represents the in-progress DMA transfer. - // TODO Define a better interface for the input buffer #[allow(unused_mut)] - pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { - self.output.tx(ptr, len).await + pub async fn tx(&mut self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + self.output.tx(buffer).await } fn apply_config(c: &CONFIG, config: &Config) { @@ -319,54 +321,12 @@ impl<'d, T: Instance> I2S<'d, T> { c.mckfreq.write(|w| w.mckfreq()._32mdiv16()); c.mode.write(|w| w.mode().master()); - c.ratio.write(|w| { - let ratio = w.ratio(); - match config.ratio { - Ratio::_32x => ratio._32x(), - Ratio::_48x => ratio._48x(), - Ratio::_64x => ratio._64x(), - Ratio::_96x => ratio._96x(), - Ratio::_128x => ratio._128x(), - Ratio::_192x => ratio._192x(), - Ratio::_256x => ratio._256x(), - Ratio::_384x => ratio._384x(), - Ratio::_512x => ratio._512x(), - } - }); - - c.swidth.write(|w| { - let swidth = w.swidth(); - match config.swidth { - SampleWidth::_8bit => swidth._8bit(), - SampleWidth::_16bit => swidth._16bit(), - SampleWidth::_24bit => swidth._24bit(), - } - }); - - c.align.write(|w| { - let align = w.align(); - match config.align { - Align::Left => align.left(), - Align::Right => align.right(), - } - }); - - c.format.write(|w| { - let format = w.format(); - match config.format { - Format::I2S => format.i2s(), - Format::Aligned => format.aligned(), - } - }); - - c.channels.write(|w| { - let channels = w.channels(); - match config.channels { - Channels::Stereo => channels.stereo(), - Channels::Left => channels.left(), - Channels::Right => channels.right(), - } - }); + c.ratio.write(|w| unsafe { w.ratio().bits(config.ratio.into()) }); + c.swidth.write(|w| unsafe { w.swidth().bits(config.swidth.into()) }); + c.align.write(|w| w.align().bit(config.align.into())); + c.format.write(|w| w.format().bit(config.format.into())); + c.channels + .write(|w| unsafe { w.channels().bits(config.channels.into()) }); } } @@ -374,18 +334,23 @@ impl<'d, T: Instance> I2sOutput<'d, T> { /// Transmits the given `tx_buffer`. /// Buffer address must be 4 byte aligned and located in RAM. /// Returns a value that represents the in-progress DMA transfer. - // TODO Define a better interface for the input buffer - pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { + pub async fn tx(&mut self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + let ptr = buffer.bytes_ptr(); + let len = buffer.bytes_len(); + if ptr as u32 % 4 != 0 { return Err(Error::BufferMisaligned); } - let maxcnt = (len / (core::mem::size_of::() / core::mem::size_of::())) as u32; - if maxcnt > MAX_DMA_MAXCNT { - return Err(Error::BufferTooLong); - } if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { return Err(Error::DMABufferNotInDataMemory); } + let maxcnt = ((len + core::mem::size_of::() - 1) / core::mem::size_of::()) as u32; + if maxcnt > MAX_DMA_MAXCNT { + return Err(Error::BufferTooLong); + } let r = T::regs(); let _s = T::state(); @@ -401,6 +366,47 @@ impl<'d, T: Instance> I2sOutput<'d, T> { } } +pub trait Buffer: Sized { + fn bytes_ptr(&self) -> *const u8; + fn bytes_len(&self) -> usize; +} + +impl Buffer for &[u8] { + #[inline] + fn bytes_ptr(&self) -> *const u8 { + self.as_ptr() + } + + #[inline] + fn bytes_len(&self) -> usize { + self.len() + } +} + +impl Buffer for &[i16] { + #[inline] + fn bytes_ptr(&self) -> *const u8 { + self.as_ptr() as *const u8 + } + + #[inline] + fn bytes_len(&self) -> usize { + self.len() * core::mem::size_of::() + } +} + +impl Buffer for &[i32] { + #[inline] + fn bytes_ptr(&self) -> *const u8 { + self.as_ptr() as *const u8 + } + + #[inline] + fn bytes_len(&self) -> usize { + self.len() * core::mem::size_of::() + } +} + pub(crate) mod sealed { use core::sync::atomic::AtomicU8; diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index a395c7141..e8ddb4a40 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -26,14 +26,11 @@ async fn main(_spawner: Spawner) { signal_buf.0[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; } - let ptr = &signal_buf.0 as *const i16 as *const u8; - let len = signal_buf.0.len() * core::mem::size_of::(); - - i2s.start(); i2s.set_tx_enabled(true); + i2s.start(); loop { - match i2s.tx(ptr, len).await { + match i2s.tx(signal_buf.0.as_slice()).await { Ok(_) => todo!(), Err(_) => todo!(), }; From f22f36f51ba4466dd15df78df0ad86ac96f9051c Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Thu, 10 Nov 2022 00:24:49 +0100 Subject: [PATCH 05/20] Add input rx --- embassy-nrf/src/i2s.rs | 59 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 3f5491ee3..fb6fa4bdf 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -166,7 +166,7 @@ pub enum Mode { /// For more details about EasyDMA, consult the module documentation. pub struct I2S<'d, T: Instance> { output: I2sOutput<'d, T>, - _input: I2sInput<'d, T>, + input: I2sInput<'d, T>, } /// Transmitter interface to the UARTE peripheral obtained @@ -253,7 +253,7 @@ impl<'d, T: Instance> I2S<'d, T> { output: I2sOutput { _p: unsafe { i2s.clone_unchecked() }, }, - _input: I2sInput { _p: i2s }, + input: I2sInput { _p: i2s }, } } @@ -284,7 +284,7 @@ impl<'d, T: Instance> I2S<'d, T> { /// Stops the I2S transfer and waits until it has stopped. #[inline(always)] - pub async fn stop(&self) -> &Self { + pub async fn stop(&self) { todo!() } @@ -304,10 +304,8 @@ impl<'d, T: Instance> I2S<'d, T> { self } - /// Transmits the given `tx_buffer`. + /// Transmits the given `buffer`. /// Buffer address must be 4 byte aligned and located in RAM. - /// Returns a value that represents the in-progress DMA transfer. - #[allow(unused_mut)] pub async fn tx(&mut self, buffer: B) -> Result<(), Error> where B: Buffer, @@ -315,6 +313,15 @@ impl<'d, T: Instance> I2S<'d, T> { self.output.tx(buffer).await } + /// Receives data into the given `buffer` until it's filled. + /// Buffer address must be 4 byte aligned and located in RAM. + pub async fn rx(&mut self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + self.input.rx(buffer).await + } + fn apply_config(c: &CONFIG, config: &Config) { // TODO support slave too c.mcken.write(|w| w.mcken().enabled()); @@ -331,9 +338,9 @@ impl<'d, T: Instance> I2S<'d, T> { } impl<'d, T: Instance> I2sOutput<'d, T> { - /// Transmits the given `tx_buffer`. + /// Transmits the given `buffer`. /// Buffer address must be 4 byte aligned and located in RAM. - /// Returns a value that represents the in-progress DMA transfer. + #[allow(unused_mut)] pub async fn tx(&mut self, buffer: B) -> Result<(), Error> where B: Buffer, @@ -366,6 +373,42 @@ impl<'d, T: Instance> I2sOutput<'d, T> { } } +impl<'d, T: Instance> I2sInput<'d, T> { + /// Receives into the given `buffer`. + /// Buffer address must be 4 byte aligned and located in RAM. + #[allow(unused_mut)] + pub async fn rx(&mut self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + let ptr = buffer.bytes_ptr(); + let len = buffer.bytes_len(); + + if ptr as u32 % 4 != 0 { + return Err(Error::BufferMisaligned); + } + if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { + return Err(Error::DMABufferNotInDataMemory); + } + let maxcnt = ((len + core::mem::size_of::() - 1) / core::mem::size_of::()) as u32; + if maxcnt > MAX_DMA_MAXCNT { + return Err(Error::BufferTooLong); + } + + let r = T::regs(); + let _s = T::state(); + + // TODO we can not progress until the last buffer written in RXD.PTR + // has started the transmission. + // We can use some sync primitive from `embassy-sync`. + + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + + Ok(()) + } +} + pub trait Buffer: Sized { fn bytes_ptr(&self) -> *const u8; fn bytes_len(&self) -> usize; From 4a2e810485a996014999ad630a604c3fe4fc81a4 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Thu, 10 Nov 2022 23:13:01 +0100 Subject: [PATCH 06/20] Restrict to pacs supporting i2s --- embassy-nrf/Cargo.toml | 1 - embassy-nrf/src/lib.rs | 6 +++++- examples/nrf/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index aa1576fd4..67b6bec40 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -48,7 +48,6 @@ nrf9160-s = ["_nrf9160"] nrf9160-ns = ["_nrf9160"] gpiote = [] -i2s = [] time-driver-rtc1 = ["_time-driver"] # Features starting with `_` are for internal use only. They're not intended diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index ac797db9b..95bd5831b 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -74,7 +74,11 @@ pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -// #[cfg(all(feature = "i2s", feature = "nrf52840"))] +#[cfg(any( + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", +))] pub mod i2s; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod nvmc; diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index a79044e8e..c633f82f5 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -14,7 +14,7 @@ embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "i2s", "unstable-pac"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } embedded-io = "0.3.1" From 10e3c3f2ec358da6d81f2bb9c05936c2ab6da567 Mon Sep 17 00:00:00 2001 From: Roman Valls Guimera Date: Fri, 11 Nov 2022 23:49:20 +0100 Subject: [PATCH 07/20] Cargo fmt --- embassy-nrf/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 95bd5831b..bdb911ecd 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -74,11 +74,7 @@ pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -#[cfg(any( - feature = "nrf52832", - feature = "nrf52833", - feature = "nrf52840", -))] +#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840",))] pub mod i2s; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod nvmc; From 122a31d20877005c7201d4e7c98da5544666dd1d Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sat, 12 Nov 2022 18:48:57 +0100 Subject: [PATCH 08/20] Interrupts, async, sine oscillator --- embassy-nrf/src/i2s.rs | 298 ++++++++++++++++++++++++++---------- embassy-nrf/src/lib.rs | 2 +- examples/nrf/src/bin/i2s.rs | 132 +++++++++++++--- 3 files changed, 330 insertions(+), 102 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index fb6fa4bdf..f5e36f0dd 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -2,17 +2,18 @@ //! I2S -//use core::future::poll_fn; -//use core::sync::atomic::{compiler_fence, Ordering}; -//use core::task::Poll; +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; -//use embassy_hal_common::drop::OnDrop; +use embassy_cortex_m::interrupt::{InterruptExt, Priority}; +use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; //use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::Interrupt; -use crate::pac::i2s::CONFIG; +use crate::pac::i2s::{RegisterBlock, CONFIG, PSEL}; use crate::Peripheral; // TODO: Define those in lib.rs somewhere else @@ -35,10 +36,39 @@ pub enum Error { // TODO: add other error variants. } +pub const MODE_MASTER_8000: Mode = Mode::Master { + freq: MckFreq::_32MDiv125, + ratio: Ratio::_32x, +}; // error = 0 +pub const MODE_MASTER_11025: Mode = Mode::Master { + freq: MckFreq::_32MDiv15, + ratio: Ratio::_192x, +}; // error = 86 +pub const MODE_MASTER_16000: Mode = Mode::Master { + freq: MckFreq::_32MDiv21, + ratio: Ratio::_96x, +}; // error = 127 +pub const MODE_MASTER_22050: Mode = Mode::Master { + freq: MckFreq::_32MDiv15, + ratio: Ratio::_96x, +}; // error = 172 +pub const MODE_MASTER_32000: Mode = Mode::Master { + freq: MckFreq::_32MDiv21, + ratio: Ratio::_48x, +}; // error = 254 +pub const MODE_MASTER_44100: Mode = Mode::Master { + freq: MckFreq::_32MDiv15, + ratio: Ratio::_48x, +}; // error = 344 +pub const MODE_MASTER_48000: Mode = Mode::Master { + freq: MckFreq::_32MDiv21, + ratio: Ratio::_32x, +}; // error = 381 + #[derive(Clone)] #[non_exhaustive] pub struct Config { - pub ratio: Ratio, + pub mode: Mode, pub swidth: SampleWidth, pub align: Align, pub format: Format, @@ -48,7 +78,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { - ratio: Ratio::_32x, + mode: MODE_MASTER_32000, swidth: SampleWidth::_16bit, align: Align::Left, format: Format::I2S, @@ -57,6 +87,66 @@ impl Default for Config { } } +/// I2S Mode +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Mode { + Master { freq: MckFreq, ratio: Ratio }, + Slave, +} + +impl Mode { + pub fn sample_rate(&self) -> Option { + match self { + Mode::Master { freq, ratio } => Some(freq.to_frequency() / ratio.to_divisor()), + Mode::Slave => None, + } + } +} + +/// 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 MckFreq { + const REGISTER_VALUES: &[u32] = &[ + 0x20000000, 0x18000000, 0x16000000, 0x11000000, 0x10000000, 0x0C000000, 0x0B000000, 0x08800000, 0x08400000, + 0x08000000, 0x06000000, 0x04100000, 0x020C0000, + ]; + + const FREQUENCIES: &[u32] = &[ + 4000000, 3200000, 2909090, 2133333, 2000000, 1523809, 1391304, 1066666, 1032258, 1000000, 761904, 507936, + 256000, + ]; + + pub fn to_register_value(&self) -> u32 { + Self::REGISTER_VALUES[usize::from(*self)] + } + + pub fn to_frequency(&self) -> u32 { + Self::FREQUENCIES[usize::from(*self)] + } +} + +impl From for usize { + fn from(variant: MckFreq) -> Self { + variant as _ + } +} + /// MCK / LRCK ratio. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Ratio { @@ -71,6 +161,14 @@ pub enum Ratio { _512x, } +impl Ratio { + const RATIOS: &[u32] = &[32, 48, 64, 96, 128, 192, 256, 384, 512]; + + pub fn to_divisor(&self) -> u32 { + Self::RATIOS[u8::from(*self) as usize] + } +} + impl From for u8 { fn from(variant: Ratio) -> Self { variant as _ @@ -136,31 +234,6 @@ impl From for u8 { } } -/// 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 = 0x20000000, -// _32MDiv10 = 0x18000000, -// _32MDiv11 = 0x16000000, -// _32MDiv15 = 0x11000000, -// _32MDiv16 = 0x10000000, -// _32MDiv21 = 0x0C000000, -// _32MDiv23 = 0x0B000000, -// _32MDiv30 = 0x08800000, -// _32MDiv31 = 0x08400000, -// _32MDiv32 = 0x08000000, -// _32MDiv42 = 0x06000000, -// _32MDiv63 = 0x04100000, -// _32MDiv125 = 0x020C0000, -// } - /// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. /// /// For more details about EasyDMA, consult the module documentation. @@ -185,7 +258,7 @@ impl<'d, T: Instance> I2S<'d, T> { /// Create a new I2S pub fn new( i2s: impl Peripheral

+ 'd, - // irq: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, mck: impl Peripheral

+ 'd, sck: impl Peripheral

+ 'd, lrck: impl Peripheral

+ 'd, @@ -196,7 +269,7 @@ impl<'d, T: Instance> I2S<'d, T> { into_ref!(mck, sck, lrck, sdin, sdout); Self::new_inner( i2s, - // irq, + irq, mck.map_into(), sck.map_into(), lrck.map_into(), @@ -208,7 +281,7 @@ impl<'d, T: Instance> I2S<'d, T> { fn new_inner( i2s: impl Peripheral

+ 'd, - // irq: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, mck: PeripheralRef<'d, AnyPin>, sck: PeripheralRef<'d, AnyPin>, lrck: PeripheralRef<'d, AnyPin>, @@ -216,36 +289,12 @@ impl<'d, T: Instance> I2S<'d, T> { sdout: PeripheralRef<'d, AnyPin>, config: Config, ) -> Self { - into_ref!(i2s, /* irq, */ mck, sck, lrck, sdin, sdout); + into_ref!(i2s, irq, mck, sck, lrck, sdin, sdout); let r = T::regs(); - Self::apply_config(&r.config, &config); - - r.psel.mck.write(|w| { - unsafe { w.bits(mck.psel_bits()) }; - w.connect().connected() - }); - - r.psel.sck.write(|w| { - unsafe { w.bits(sck.psel_bits()) }; - w.connect().connected() - }); - - r.psel.lrck.write(|w| { - unsafe { w.bits(lrck.psel_bits()) }; - w.connect().connected() - }); - - r.psel.sdin.write(|w| { - unsafe { w.bits(sdin.psel_bits()) }; - w.connect().connected() - }); - - r.psel.sdout.write(|w| { - unsafe { w.bits(sdout.psel_bits()) }; - w.connect().connected() - }); + Self::select_pins(&r.psel, mck, sck, lrck, sdin, sdout); + Self::setup_interrupt(irq, r); r.enable.write(|w| w.enable().enabled()); @@ -322,19 +371,87 @@ impl<'d, T: Instance> I2S<'d, T> { self.input.rx(buffer).await } - fn apply_config(c: &CONFIG, config: &Config) { - // TODO support slave too - c.mcken.write(|w| w.mcken().enabled()); - c.mckfreq.write(|w| w.mckfreq()._32mdiv16()); - c.mode.write(|w| w.mode().master()); + fn on_interrupt(_: *mut ()) { + let r = T::regs(); + let s = T::state(); + + if r.events_txptrupd.read().bits() != 0 { + s.tx_waker.wake(); + r.intenclr.write(|w| w.txptrupd().clear()); + } + + if r.events_rxptrupd.read().bits() != 0 { + s.rx_waker.wake(); + r.intenclr.write(|w| w.rxptrupd().clear()); + } + } + + fn apply_config(c: &CONFIG, config: &Config) { + match config.mode { + Mode::Master { freq, ratio } => { + c.mode.write(|w| w.mode().master()); + c.mcken.write(|w| w.mcken().enabled()); + c.mckfreq + .write(|w| unsafe { w.mckfreq().bits(freq.to_register_value()) }); + c.ratio.write(|w| unsafe { w.ratio().bits(ratio.into()) }); + } + Mode::Slave => { + c.mode.write(|w| w.mode().slave()); + } + }; - c.ratio.write(|w| unsafe { w.ratio().bits(config.ratio.into()) }); c.swidth.write(|w| unsafe { w.swidth().bits(config.swidth.into()) }); c.align.write(|w| w.align().bit(config.align.into())); c.format.write(|w| w.format().bit(config.format.into())); c.channels .write(|w| unsafe { w.channels().bits(config.channels.into()) }); } + + fn select_pins( + psel: &PSEL, + mck: PeripheralRef<'d, AnyPin>, + sck: PeripheralRef<'d, AnyPin>, + lrck: PeripheralRef<'d, AnyPin>, + sdin: PeripheralRef<'d, AnyPin>, + sdout: PeripheralRef<'d, AnyPin>, + ) { + psel.mck.write(|w| { + unsafe { w.bits(mck.psel_bits()) }; + w.connect().connected() + }); + + psel.sck.write(|w| { + unsafe { w.bits(sck.psel_bits()) }; + w.connect().connected() + }); + + psel.lrck.write(|w| { + unsafe { w.bits(lrck.psel_bits()) }; + w.connect().connected() + }); + + psel.sdin.write(|w| { + unsafe { w.bits(sdin.psel_bits()) }; + w.connect().connected() + }); + + psel.sdout.write(|w| { + unsafe { w.bits(sdout.psel_bits()) }; + w.connect().connected() + }); + } + + fn setup_interrupt(irq: PeripheralRef<'d, T::Interrupt>, r: &RegisterBlock) { + irq.set_handler(Self::on_interrupt); + irq.set_priority(Priority::P1); // TODO review priorities + irq.unpend(); + irq.enable(); + + r.intenclr.write(|w| w.rxptrupd().clear()); + r.intenclr.write(|w| w.txptrupd().clear()); + r.events_rxptrupd.reset(); + r.events_txptrupd.reset(); + } } impl<'d, T: Instance> I2sOutput<'d, T> { @@ -360,15 +477,40 @@ impl<'d, T: Instance> I2sOutput<'d, T> { } let r = T::regs(); - let _s = T::state(); + let s = T::state(); - // TODO we can not progress until the last buffer written in TXD.PTR - // has started the transmission. - // We can use some sync primitive from `embassy-sync`. + let drop = OnDrop::new(move || { + trace!("write drop: stopping"); + + r.intenclr.write(|w| w.txptrupd().clear()); + r.events_txptrupd.reset(); + r.config.txen.write(|w| w.txen().disabled()); + + // TX is stopped almost instantly, spinning is fine. + while r.events_txptrupd.read().bits() == 0 {} + trace!("write drop: stopped"); + }); r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + r.intenset.write(|w| w.txptrupd().set()); + + compiler_fence(Ordering::SeqCst); + + poll_fn(|cx| { + s.tx_waker.register(cx.waker()); + if r.events_txptrupd.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + compiler_fence(Ordering::SeqCst); + drop.defuse(); + Ok(()) } } @@ -451,23 +593,19 @@ impl Buffer for &[i32] { } pub(crate) mod sealed { - use core::sync::atomic::AtomicU8; - use embassy_sync::waitqueue::AtomicWaker; //use super::*; pub struct State { - pub input_waker: AtomicWaker, - pub output_waker: AtomicWaker, - pub buffers_refcount: AtomicU8, + pub rx_waker: AtomicWaker, + pub tx_waker: AtomicWaker, } impl State { pub const fn new() -> Self { Self { - input_waker: AtomicWaker::new(), - output_waker: AtomicWaker::new(), - buffers_refcount: AtomicU8::new(0), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), } } } diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index bdb911ecd..dc018e08e 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -74,7 +74,7 @@ pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840",))] +#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod nvmc; diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index e8ddb4a40..53ccb3b85 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -4,43 +4,133 @@ #![no_main] #![feature(type_alias_impl_trait)] -//use defmt::*; +use core::f32::consts::PI; + +use defmt::{error, info}; use embassy_executor::Spawner; -use embassy_nrf::i2s; +use embassy_nrf::i2s::{MckFreq, Mode, Ratio, MODE_MASTER_16000, MODE_MASTER_8000}; +use embassy_nrf::{i2s, interrupt}; use {defmt_rtt as _, panic_probe as _}; #[repr(align(4))] -pub struct Aligned(T); +pub struct AlignedBuffer(T); + +impl AsRef for AlignedBuffer { + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for AlignedBuffer { + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let config = i2s::Config::default(); + let mut config = i2s::Config::default(); + // config.mode = MODE_MASTER_16000; + config.mode = Mode::Master { + freq: MckFreq::_32MDiv10, + ratio: Ratio::_256x, + }; // 12500 Hz + let sample_rate = config.mode.sample_rate().expect("I2S Master"); + let inv_sample_rate = 1.0 / sample_rate as f32; - let mut i2s = i2s::I2S::new(p.I2S, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); + info!("Sample rate: {}", sample_rate); - let mut signal_buf: Aligned<[i16; 32]> = Aligned([0i16; 32]); - let len = signal_buf.0.len() / 2; - for x in 0..len { - signal_buf.0[2 * x] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; - signal_buf.0[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16; - } + let irq = interrupt::take!(I2S); + let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); + + const BUF_SAMPLES: usize = 250; + const BUF_SIZE: usize = BUF_SAMPLES * 2; + let mut buf = AlignedBuffer([0i16; BUF_SIZE]); + + let mut carrier = SineOsc::new(); + carrier.set_frequency(300.0, inv_sample_rate); + + let mut modulator = SineOsc::new(); + modulator.set_frequency(0.01, inv_sample_rate); + modulator.set_amplitude(0.2); i2s.set_tx_enabled(true); i2s.start(); loop { - match i2s.tx(signal_buf.0.as_slice()).await { - Ok(_) => todo!(), - Err(_) => todo!(), - }; + for sample in buf.as_mut().chunks_mut(2) { + let signal = carrier.generate(); + // let modulation = bipolar_to_unipolar(modulator.generate()); + // carrier.set_frequency(200.0 + 100.0 * modulation, inv_sample_rate); + // carrier.set_amplitude((modulation); + let value = (i16::MAX as f32 * signal) as i16; + sample[0] = value; + sample[1] = value; + // info!("{}", signal); + } + + if let Err(err) = i2s.tx(buf.as_ref().as_slice()).await { + error!("{}", err); + } } } -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 +struct SineOsc { + amplitude: f32, + modulo: f32, + phase_inc: f32, +} + +impl SineOsc { + const B: f32 = 4.0 / PI; + const C: f32 = -4.0 / (PI * PI); + const P: f32 = 0.225; + + pub fn new() -> Self { + Self { + amplitude: 1.0, + modulo: 0.0, + phase_inc: 0.0, + } + } + + pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { + self.phase_inc = freq * inv_sample_rate; + } + + pub fn set_amplitude(&mut self, amplitude: f32) { + self.amplitude = amplitude; + } + + pub fn generate(&mut self) -> f32 { + let signal = self.parabolic_sin(self.modulo); + self.modulo += self.phase_inc; + if self.modulo < 0.0 { + self.modulo += 1.0; + } else if self.modulo > 1.0 { + self.modulo -= 1.0; + } + signal * self.amplitude + } + + fn parabolic_sin(&mut self, modulo: f32) -> f32 { + let angle = PI - modulo * 2.0 * PI; + let y = Self::B * angle + Self::C * angle * abs(angle); + Self::P * (y * abs(y) - y) + y + } +} + +#[inline] +fn abs(value: f32) -> f32 { + if value < 0.0 { + -value + } else { + value + } +} + +#[inline] +fn bipolar_to_unipolar(value: f32) -> f32 { + (value + 1.0) / 2.0 } From d2e8794f29d3d0afef7a6bc610b2ee4a4d680643 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sun, 13 Nov 2022 01:41:32 +0100 Subject: [PATCH 09/20] Investigating discontinuities in the signal --- embassy-nrf/src/i2s.rs | 68 ++++++++++++++++++++++++++----------- examples/nrf/src/bin/i2s.rs | 27 ++++++++++----- 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index f5e36f0dd..9a8f29e78 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -371,21 +371,6 @@ impl<'d, T: Instance> I2S<'d, T> { self.input.rx(buffer).await } - fn on_interrupt(_: *mut ()) { - let r = T::regs(); - let s = T::state(); - - if r.events_txptrupd.read().bits() != 0 { - s.tx_waker.wake(); - r.intenclr.write(|w| w.txptrupd().clear()); - } - - if r.events_rxptrupd.read().bits() != 0 { - s.rx_waker.wake(); - r.intenclr.write(|w| w.rxptrupd().clear()); - } - } - fn apply_config(c: &CONFIG, config: &Config) { match config.mode { Mode::Master { freq, ratio } => { @@ -443,14 +428,36 @@ impl<'d, T: Instance> I2S<'d, T> { fn setup_interrupt(irq: PeripheralRef<'d, T::Interrupt>, r: &RegisterBlock) { irq.set_handler(Self::on_interrupt); - irq.set_priority(Priority::P1); // TODO review priorities + // irq.set_priority(Priority::P1); // TODO review priorities irq.unpend(); irq.enable(); r.intenclr.write(|w| w.rxptrupd().clear()); r.intenclr.write(|w| w.txptrupd().clear()); + r.events_rxptrupd.reset(); r.events_txptrupd.reset(); + + r.intenset.write(|w| w.rxptrupd().set()); + r.intenset.write(|w| w.txptrupd().set()); + } + + fn on_interrupt(_: *mut ()) { + let r = T::regs(); + let s = T::state(); + + if r.events_txptrupd.read().bits() != 0 { + trace!("[{}] INT", s.seq.load(Ordering::Relaxed)); + s.tx_waker.wake(); + r.intenclr.write(|w| w.txptrupd().clear()); + } + + if r.events_rxptrupd.read().bits() != 0 { + s.rx_waker.wake(); + r.intenclr.write(|w| w.rxptrupd().clear()); + } + + s.overruns.fetch_add(1, Ordering::Relaxed); } } @@ -479,6 +486,12 @@ impl<'d, T: Instance> I2sOutput<'d, T> { let r = T::regs(); let s = T::state(); + let seq = s.seq.fetch_add(1, Ordering::Relaxed); + if r.events_txptrupd.read().bits() != 0 && seq > 0 { + info!("XRUN!"); + loop {} + } + let drop = OnDrop::new(move || { trace!("write drop: stopping"); @@ -491,18 +504,26 @@ impl<'d, T: Instance> I2sOutput<'d, T> { trace!("write drop: stopped"); }); + trace!("[{}] PTR", s.seq.load(Ordering::Relaxed)); r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); - r.intenset.write(|w| w.txptrupd().set()); - compiler_fence(Ordering::SeqCst); poll_fn(|cx| { s.tx_waker.register(cx.waker()); - if r.events_txptrupd.read().bits() != 0 { + if r.events_txptrupd.read().bits() != 0 || seq == 0 { + trace!("[{}] POLL Ready", s.seq.load(Ordering::Relaxed)); + r.events_txptrupd.reset(); + r.intenset.write(|w| w.txptrupd().set()); + let overruns = s.overruns.fetch_sub(1, Ordering::Relaxed); + if overruns - 1 != 0 { + warn!("XRUN: {}", overruns); + s.overruns.store(0, Ordering::Relaxed) + } Poll::Ready(()) } else { + trace!("[{}] POLL Pending", s.seq.load(Ordering::Relaxed)); Poll::Pending } }) @@ -593,19 +614,26 @@ impl Buffer for &[i32] { } pub(crate) mod sealed { + use core::sync::atomic::AtomicI32; + use embassy_sync::waitqueue::AtomicWaker; - //use super::*; + use super::*; pub struct State { pub rx_waker: AtomicWaker, pub tx_waker: AtomicWaker, + pub overruns: AtomicI32, + pub seq: AtomicI32, } + impl State { pub const fn new() -> Self { Self { rx_waker: AtomicWaker::new(), tx_waker: AtomicWaker::new(), + overruns: AtomicI32::new(0), + seq: AtomicI32::new(0), } } } diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index 53ccb3b85..7fb1ecb84 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -9,6 +9,7 @@ use core::f32::consts::PI; use defmt::{error, info}; use embassy_executor::Spawner; use embassy_nrf::i2s::{MckFreq, Mode, Ratio, MODE_MASTER_16000, MODE_MASTER_8000}; +use embassy_nrf::pac::ficr::info; use embassy_nrf::{i2s, interrupt}; use {defmt_rtt as _, panic_probe as _}; @@ -49,16 +50,14 @@ async fn main(_spawner: Spawner) { let mut buf = AlignedBuffer([0i16; BUF_SIZE]); let mut carrier = SineOsc::new(); - carrier.set_frequency(300.0, inv_sample_rate); + carrier.set_frequency(240.0, inv_sample_rate); - let mut modulator = SineOsc::new(); - modulator.set_frequency(0.01, inv_sample_rate); - modulator.set_amplitude(0.2); + // let mut modulator = SineOsc::new(); + // modulator.set_frequency(0.01, inv_sample_rate); + // modulator.set_amplitude(0.2); - i2s.set_tx_enabled(true); - i2s.start(); - - loop { + let mut lastf = 0.0; + let mut generate = |buf: &mut [i16]| { for sample in buf.as_mut().chunks_mut(2) { let signal = carrier.generate(); // let modulation = bipolar_to_unipolar(modulator.generate()); @@ -67,8 +66,18 @@ async fn main(_spawner: Spawner) { let value = (i16::MAX as f32 * signal) as i16; sample[0] = value; sample[1] = value; - // info!("{}", signal); } + }; + + generate(buf.as_mut().as_mut_slice()); + + i2s.set_tx_enabled(true); + i2s.start(); + + loop { + // info!("--"); + + generate(buf.as_mut().as_mut_slice()); if let Err(err) = i2s.tx(buf.as_ref().as_slice()).await { error!("{}", err); From 17857bc18fee95be07ee0c51687d2eb109e5aea6 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sun, 13 Nov 2022 02:12:58 +0100 Subject: [PATCH 10/20] Minor changes --- embassy-nrf/src/i2s.rs | 9 +++++---- examples/nrf/src/bin/i2s.rs | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 9a8f29e78..eed9e1956 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -327,6 +327,7 @@ impl<'d, T: Instance> I2S<'d, T> { pub fn start(&self) -> &Self { let r = T::regs(); self.enable(); + trace!("START"); r.tasks_start.write(|w| unsafe { w.bits(1) }); self } @@ -487,8 +488,8 @@ impl<'d, T: Instance> I2sOutput<'d, T> { let s = T::state(); let seq = s.seq.fetch_add(1, Ordering::Relaxed); - if r.events_txptrupd.read().bits() != 0 && seq > 0 { - info!("XRUN!"); + if r.events_txptrupd.read().bits() != 0 && seq > 1 { + warn!("XRUN!"); loop {} } @@ -505,8 +506,8 @@ impl<'d, T: Instance> I2sOutput<'d, T> { }); trace!("[{}] PTR", s.seq.load(Ordering::Relaxed)); - r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); compiler_fence(Ordering::SeqCst); @@ -517,7 +518,7 @@ impl<'d, T: Instance> I2sOutput<'d, T> { r.events_txptrupd.reset(); r.intenset.write(|w| w.txptrupd().set()); let overruns = s.overruns.fetch_sub(1, Ordering::Relaxed); - if overruns - 1 != 0 { + if overruns != 0 { warn!("XRUN: {}", overruns); s.overruns.store(0, Ordering::Relaxed) } diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index 7fb1ecb84..33b5398d9 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -45,7 +45,7 @@ async fn main(_spawner: Spawner) { let irq = interrupt::take!(I2S); let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); - const BUF_SAMPLES: usize = 250; + const BUF_SAMPLES: usize = 500; const BUF_SIZE: usize = BUF_SAMPLES * 2; let mut buf = AlignedBuffer([0i16; BUF_SIZE]); @@ -56,7 +56,6 @@ async fn main(_spawner: Spawner) { // modulator.set_frequency(0.01, inv_sample_rate); // modulator.set_amplitude(0.2); - let mut lastf = 0.0; let mut generate = |buf: &mut [i16]| { for sample in buf.as_mut().chunks_mut(2) { let signal = carrier.generate(); @@ -71,12 +70,14 @@ async fn main(_spawner: Spawner) { generate(buf.as_mut().as_mut_slice()); + if let Err(err) = i2s.tx(buf.as_ref().as_slice()).await { + error!("{}", err); + } + i2s.set_tx_enabled(true); i2s.start(); loop { - // info!("--"); - generate(buf.as_mut().as_mut_slice()); if let Err(err) = i2s.tx(buf.as_ref().as_slice()).await { From 4fe834db2f491179edf28105faf79d6fc89785c6 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sun, 13 Nov 2022 02:48:07 +0100 Subject: [PATCH 11/20] Mono channels --- examples/nrf/src/bin/i2s.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index 33b5398d9..4b6f8ab2d 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -8,7 +8,7 @@ use core::f32::consts::PI; use defmt::{error, info}; use embassy_executor::Spawner; -use embassy_nrf::i2s::{MckFreq, Mode, Ratio, MODE_MASTER_16000, MODE_MASTER_8000}; +use embassy_nrf::i2s::{MckFreq, Mode, Ratio, MODE_MASTER_16000, MODE_MASTER_8000, Channels}; use embassy_nrf::pac::ficr::info; use embassy_nrf::{i2s, interrupt}; use {defmt_rtt as _, panic_probe as _}; @@ -37,6 +37,7 @@ async fn main(_spawner: Spawner) { freq: MckFreq::_32MDiv10, ratio: Ratio::_256x, }; // 12500 Hz + config.channels = Channels::Left; let sample_rate = config.mode.sample_rate().expect("I2S Master"); let inv_sample_rate = 1.0 / sample_rate as f32; @@ -46,25 +47,24 @@ async fn main(_spawner: Spawner) { let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); const BUF_SAMPLES: usize = 500; - const BUF_SIZE: usize = BUF_SAMPLES * 2; + const BUF_SIZE: usize = BUF_SAMPLES; let mut buf = AlignedBuffer([0i16; BUF_SIZE]); let mut carrier = SineOsc::new(); - carrier.set_frequency(240.0, inv_sample_rate); + carrier.set_frequency(16.0, inv_sample_rate); // let mut modulator = SineOsc::new(); // modulator.set_frequency(0.01, inv_sample_rate); // modulator.set_amplitude(0.2); let mut generate = |buf: &mut [i16]| { - for sample in buf.as_mut().chunks_mut(2) { + for sample in buf.as_mut() { let signal = carrier.generate(); // let modulation = bipolar_to_unipolar(modulator.generate()); // carrier.set_frequency(200.0 + 100.0 * modulation, inv_sample_rate); // carrier.set_amplitude((modulation); let value = (i16::MAX as f32 * signal) as i16; - sample[0] = value; - sample[1] = value; + *sample = value; } }; From 1ed260b1055fad6ddd89053ae3e1997ec34c6332 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Thu, 17 Nov 2022 00:19:22 +0100 Subject: [PATCH 12/20] Fix buffer overruns --- embassy-nrf/src/i2s.rs | 427 ++++++++++++++++++++---------------- examples/nrf/src/bin/i2s.rs | 88 +++++--- 2 files changed, 294 insertions(+), 221 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index eed9e1956..e6dfb690c 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -3,6 +3,7 @@ //! I2S use core::future::poll_fn; +use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -10,7 +11,6 @@ use embassy_cortex_m::interrupt::{InterruptExt, Priority}; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -//use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::Interrupt; use crate::pac::i2s::{RegisterBlock, CONFIG, PSEL}; @@ -31,9 +31,9 @@ pub const SRAM_UPPER: usize = 0x3000_0000; pub enum Error { BufferTooLong, BufferZeroLength, - DMABufferNotInDataMemory, + BufferNotInDataMemory, BufferMisaligned, - // TODO: add other error variants. + BufferLengthMisaligned, } pub const MODE_MASTER_8000: Mode = Mode::Master { @@ -122,12 +122,12 @@ pub enum MckFreq { } impl MckFreq { - const REGISTER_VALUES: &[u32] = &[ + const REGISTER_VALUES: &'static [u32] = &[ 0x20000000, 0x18000000, 0x16000000, 0x11000000, 0x10000000, 0x0C000000, 0x0B000000, 0x08800000, 0x08400000, 0x08000000, 0x06000000, 0x04100000, 0x020C0000, ]; - const FREQUENCIES: &[u32] = &[ + const FREQUENCIES: &'static [u32] = &[ 4000000, 3200000, 2909090, 2133333, 2000000, 1523809, 1391304, 1066666, 1032258, 1000000, 761904, 507936, 256000, ]; @@ -162,7 +162,7 @@ pub enum Ratio { } impl Ratio { - const RATIOS: &[u32] = &[32, 48, 64, 96, 128, 192, 256, 384, 512]; + const RATIOS: &'static [u32] = &[32, 48, 64, 96, 128, 192, 256, 384, 512]; pub fn to_divisor(&self) -> u32 { Self::RATIOS[u8::from(*self) as usize] @@ -234,23 +234,10 @@ impl From for u8 { } } -/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. +/// Interface to the I2S peripheral using EasyDMA to offload the transmission and reception workload. /// /// For more details about EasyDMA, consult the module documentation. pub struct I2S<'d, T: Instance> { - output: I2sOutput<'d, T>, - input: I2sInput<'d, T>, -} - -/// Transmitter interface to the UARTE peripheral obtained -/// via [Uarte]::split. -pub struct I2sOutput<'d, T: Instance> { - _p: PeripheralRef<'d, T>, -} - -/// Receiver interface to the UARTE peripheral obtained -/// via [Uarte]::split. -pub struct I2sInput<'d, T: Instance> { _p: PeripheralRef<'d, T>, } @@ -298,78 +285,19 @@ impl<'d, T: Instance> I2S<'d, T> { r.enable.write(|w| w.enable().enabled()); - Self { - output: I2sOutput { - _p: unsafe { i2s.clone_unchecked() }, - }, - input: I2sInput { _p: i2s }, - } + Self { _p: i2s } } - /// Enables the I2S module. - #[inline(always)] - pub fn enable(&self) -> &Self { - let r = T::regs(); - r.enable.write(|w| w.enable().enabled()); - self + pub fn output(self) -> Output<'d, T> { + Output { _p: self._p } } - /// Disables the I2S module. - #[inline(always)] - pub fn disable(&self) -> &Self { - let r = T::regs(); - r.enable.write(|w| w.enable().disabled()); - self + pub fn input(self) -> Input<'d, T> { + Input { _p: self._p } } - /// Starts I2S transfer. - #[inline(always)] - pub fn start(&self) -> &Self { - let r = T::regs(); - self.enable(); - trace!("START"); - r.tasks_start.write(|w| unsafe { w.bits(1) }); - self - } - - /// Stops the I2S transfer and waits until it has stopped. - #[inline(always)] - pub async fn stop(&self) { - todo!() - } - - /// Enables/disables I2S transmission (TX). - #[inline(always)] - pub fn set_tx_enabled(&self, enabled: bool) -> &Self { - let r = T::regs(); - r.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 { - let r = T::regs(); - r.config.rxen.write(|w| w.rxen().bit(enabled)); - self - } - - /// Transmits the given `buffer`. - /// Buffer address must be 4 byte aligned and located in RAM. - pub async fn tx(&mut self, buffer: B) -> Result<(), Error> - where - B: Buffer, - { - self.output.tx(buffer).await - } - - /// Receives data into the given `buffer` until it's filled. - /// Buffer address must be 4 byte aligned and located in RAM. - pub async fn rx(&mut self, buffer: B) -> Result<(), Error> - where - B: Buffer, - { - self.input.rx(buffer).await + pub fn full_duplex(self) -> FullDuplex<'d, T> { + FullDuplex { _p: self._p } } fn apply_config(c: &CONFIG, config: &Config) { @@ -433,103 +361,94 @@ impl<'d, T: Instance> I2S<'d, T> { irq.unpend(); irq.enable(); - r.intenclr.write(|w| w.rxptrupd().clear()); - r.intenclr.write(|w| w.txptrupd().clear()); + let device = Device::::new(); + device.disable_tx_ptr_interrupt(); + device.disable_rx_ptr_interrupt(); - r.events_rxptrupd.reset(); - r.events_txptrupd.reset(); + device.reset_tx_ptr_event(); + device.reset_rx_ptr_event(); - r.intenset.write(|w| w.rxptrupd().set()); - r.intenset.write(|w| w.txptrupd().set()); + device.enable_tx_ptr_interrupt(); + device.enable_rx_ptr_interrupt(); } fn on_interrupt(_: *mut ()) { - let r = T::regs(); + let device = Device::::new(); let s = T::state(); - if r.events_txptrupd.read().bits() != 0 { - trace!("[{}] INT", s.seq.load(Ordering::Relaxed)); + if device.is_tx_ptr_updated() { + trace!("TX INT"); s.tx_waker.wake(); - r.intenclr.write(|w| w.txptrupd().clear()); + device.disable_tx_ptr_interrupt(); } - if r.events_rxptrupd.read().bits() != 0 { + if device.is_rx_ptr_updated() { + trace!("RX INT"); s.rx_waker.wake(); - r.intenclr.write(|w| w.rxptrupd().clear()); + device.disable_rx_ptr_interrupt(); } - - s.overruns.fetch_add(1, Ordering::Relaxed); } } -impl<'d, T: Instance> I2sOutput<'d, T> { - /// Transmits the given `buffer`. - /// Buffer address must be 4 byte aligned and located in RAM. - #[allow(unused_mut)] - pub async fn tx(&mut self, buffer: B) -> Result<(), Error> +pub struct Output<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Output<'d, T> { + /// Starts I2S transfer. + #[inline(always)] + pub fn start(&self, buffer: B) -> Result<(), Error> where B: Buffer, { - let ptr = buffer.bytes_ptr(); - let len = buffer.bytes_len(); + // TODO what to do if it is started already? - if ptr as u32 % 4 != 0 { - return Err(Error::BufferMisaligned); - } - if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { - return Err(Error::DMABufferNotInDataMemory); - } - let maxcnt = ((len + core::mem::size_of::() - 1) / core::mem::size_of::()) as u32; - if maxcnt > MAX_DMA_MAXCNT { - return Err(Error::BufferTooLong); - } + let device = Device::::new(); + device.enable(); + device.set_tx_buffer(buffer)?; + device.enable_tx(); + device.start(); - let r = T::regs(); - let s = T::state(); + Ok(()) + } - let seq = s.seq.fetch_add(1, Ordering::Relaxed); - if r.events_txptrupd.read().bits() != 0 && seq > 1 { - warn!("XRUN!"); - loop {} - } + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + todo!() + } - let drop = OnDrop::new(move || { - trace!("write drop: stopping"); + /// Transmits the given `buffer`. + /// Buffer address must be 4 byte aligned and located in RAM. + #[allow(unused_mut)] + pub async fn send(&mut self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + trace!("SEND: {}", buffer.bytes_ptr() as u32); - r.intenclr.write(|w| w.txptrupd().clear()); - r.events_txptrupd.reset(); - r.config.txen.write(|w| w.txen().disabled()); - - // TX is stopped almost instantly, spinning is fine. - while r.events_txptrupd.read().bits() == 0 {} - trace!("write drop: stopped"); - }); - - trace!("[{}] PTR", s.seq.load(Ordering::Relaxed)); - r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); - r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + let device = Device::::new(); + let drop = device.on_tx_drop(); compiler_fence(Ordering::SeqCst); poll_fn(|cx| { - s.tx_waker.register(cx.waker()); - if r.events_txptrupd.read().bits() != 0 || seq == 0 { - trace!("[{}] POLL Ready", s.seq.load(Ordering::Relaxed)); - r.events_txptrupd.reset(); - r.intenset.write(|w| w.txptrupd().set()); - let overruns = s.overruns.fetch_sub(1, Ordering::Relaxed); - if overruns != 0 { - warn!("XRUN: {}", overruns); - s.overruns.store(0, Ordering::Relaxed) - } + T::state().tx_waker.register(cx.waker()); + + if device.is_tx_ptr_updated() { + trace!("TX POLL: Ready"); + device.reset_tx_ptr_event(); + device.enable_tx_ptr_interrupt(); Poll::Ready(()) } else { - trace!("[{}] POLL Pending", s.seq.load(Ordering::Relaxed)); + trace!("TX POLL: Pending"); Poll::Pending } }) .await; + device.set_tx_buffer(buffer)?; + compiler_fence(Ordering::SeqCst); drop.defuse(); @@ -537,40 +456,180 @@ impl<'d, T: Instance> I2sOutput<'d, T> { } } -impl<'d, T: Instance> I2sInput<'d, T> { - /// Receives into the given `buffer`. - /// Buffer address must be 4 byte aligned and located in RAM. - #[allow(unused_mut)] - pub async fn rx(&mut self, buffer: B) -> Result<(), Error> +pub struct Input<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Input<'d, T> { + // TODO +} + +pub struct FullDuplex<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> FullDuplex<'d, T> { + // TODO +} + +struct Device(&'static RegisterBlock, PhantomData); + +impl Device { + fn new() -> Self { + Self(T::regs(), PhantomData) + } + + #[inline(always)] + pub fn enable(&self) { + trace!("ENABLED"); + self.0.enable.write(|w| w.enable().enabled()); + } + + #[inline(always)] + pub fn disable(&self) { + trace!("DISABLED"); + self.0.enable.write(|w| w.enable().disabled()); + } + + #[inline(always)] + fn enable_tx(&self) { + trace!("TX ENABLED"); + self.0.config.txen.write(|w| w.txen().enabled()); + } + + #[inline(always)] + fn disable_tx(&self) { + trace!("TX DISABLED"); + self.0.config.txen.write(|w| w.txen().disabled()); + } + + #[inline(always)] + fn enable_rx(&self) { + trace!("RX ENABLED"); + self.0.config.rxen.write(|w| w.rxen().enabled()); + } + + #[inline(always)] + fn disable_rx(&self) { + trace!("RX DISABLED"); + self.0.config.rxen.write(|w| w.rxen().disabled()); + } + + #[inline(always)] + fn start(&self) { + trace!("START"); + self.0.tasks_start.write(|w| unsafe { w.bits(1) }); + } + + #[inline] + fn set_tx_buffer(&self, buffer: B) -> Result<(), Error> where B: Buffer, { - let ptr = buffer.bytes_ptr(); - let len = buffer.bytes_len(); - - if ptr as u32 % 4 != 0 { - return Err(Error::BufferMisaligned); - } - if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { - return Err(Error::DMABufferNotInDataMemory); - } - let maxcnt = ((len + core::mem::size_of::() - 1) / core::mem::size_of::()) as u32; - if maxcnt > MAX_DMA_MAXCNT { - return Err(Error::BufferTooLong); - } - - let r = T::regs(); - let _s = T::state(); - - // TODO we can not progress until the last buffer written in RXD.PTR - // has started the transmission. - // We can use some sync primitive from `embassy-sync`. - - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); - + let (ptr, maxcnt) = Self::validate_buffer(buffer)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); Ok(()) } + + #[inline] + fn set_rx_buffer(&self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + let (ptr, maxcnt) = Self::validate_buffer(buffer)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + Ok(()) + } + + #[inline(always)] + fn is_tx_ptr_updated(&self) -> bool { + self.0.events_txptrupd.read().bits() != 0 + } + + #[inline(always)] + fn is_rx_ptr_updated(&self) -> bool { + self.0.events_rxptrupd.read().bits() != 0 + } + + #[inline(always)] + fn reset_tx_ptr_event(&self) { + trace!("TX PTR EVENT: Reset"); + self.0.events_txptrupd.reset(); + } + + #[inline(always)] + fn reset_rx_ptr_event(&self) { + trace!("RX PTR EVENT: Reset"); + self.0.events_rxptrupd.reset(); + } + + #[inline(always)] + fn disable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.txptrupd().clear()); + } + + #[inline(always)] + fn disable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.rxptrupd().clear()); + } + + #[inline(always)] + fn enable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.txptrupd().set()); + } + + #[inline(always)] + fn enable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Enabled"); + self.0.intenclr.write(|w| w.rxptrupd().clear()); + } + + #[inline] + fn on_tx_drop(&self) -> OnDrop { + OnDrop::new(move || { + trace!("TX DROP: Stopping"); + + let device = Device::::new(); + device.disable_tx_ptr_interrupt(); + device.reset_tx_ptr_event(); + device.disable_tx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_tx_ptr_updated() {} + + trace!("TX DROP: Stopped"); + }) + } + + fn validate_buffer(buffer: B) -> Result<(u32, u32), Error> + where + B: Buffer, + { + let ptr = buffer.bytes_ptr() as u32; + let len = buffer.bytes_len(); + let maxcnt = ((len + core::mem::size_of::() - 1) / core::mem::size_of::()) as u32; + + trace!("PTR={}, MAXCNT={}", ptr, maxcnt); + + // TODO can we avoid repeating all those runtime checks for the same buffer again and again? + + if ptr % 4 != 0 { + Err(Error::BufferMisaligned) + } else if len % 4 != 0 { + Err(Error::BufferLengthMisaligned) + } else if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { + Err(Error::BufferNotInDataMemory) + } else if maxcnt > MAX_DMA_MAXCNT { + Err(Error::BufferTooLong) + } else { + Ok((ptr, maxcnt)) + } + } } pub trait Buffer: Sized { @@ -578,10 +637,10 @@ pub trait Buffer: Sized { fn bytes_len(&self) -> usize; } -impl Buffer for &[u8] { +impl Buffer for &[i8] { #[inline] fn bytes_ptr(&self) -> *const u8 { - self.as_ptr() + self.as_ptr() as *const u8 } #[inline] @@ -610,7 +669,7 @@ impl Buffer for &[i32] { #[inline] fn bytes_len(&self) -> usize { - self.len() * core::mem::size_of::() + self.len() * core::mem::size_of::() } } @@ -624,8 +683,6 @@ pub(crate) mod sealed { pub struct State { pub rx_waker: AtomicWaker, pub tx_waker: AtomicWaker, - pub overruns: AtomicI32, - pub seq: AtomicI32, } impl State { @@ -633,8 +690,6 @@ pub(crate) mod sealed { Self { rx_waker: AtomicWaker::new(), tx_waker: AtomicWaker::new(), - overruns: AtomicI32::new(0), - seq: AtomicI32::new(0), } } } @@ -654,7 +709,7 @@ pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { macro_rules! impl_i2s { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::i2s::sealed::Instance for peripherals::$type { - fn regs() -> &'static pac::i2s::RegisterBlock { + fn regs() -> &'static crate::pac::i2s::RegisterBlock { unsafe { &*pac::$pac_type::ptr() } } fn state() -> &'static crate::i2s::sealed::State { diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s.rs index 4b6f8ab2d..9b3144f24 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s.rs @@ -1,14 +1,13 @@ -// Example inspired by RTIC's I2S demo: https://github.com/nrf-rs/nrf-hal/blob/master/examples/i2s-controller-demo/src/main.rs - #![no_std] #![no_main] #![feature(type_alias_impl_trait)] use core::f32::consts::PI; -use defmt::{error, info}; +use defmt::{error, info, trace}; use embassy_executor::Spawner; -use embassy_nrf::i2s::{MckFreq, Mode, Ratio, MODE_MASTER_16000, MODE_MASTER_8000, Channels}; +use embassy_nrf::gpio::{Input, Pin, Pull}; +use embassy_nrf::i2s::{Channels, MckFreq, Mode, Ratio, SampleWidth, MODE_MASTER_32000}; use embassy_nrf::pac::ficr::info; use embassy_nrf::{i2s, interrupt}; use {defmt_rtt as _, panic_probe as _}; @@ -32,60 +31,79 @@ impl AsMut for AlignedBuffer { async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); let mut config = i2s::Config::default(); - // config.mode = MODE_MASTER_16000; - config.mode = Mode::Master { - freq: MckFreq::_32MDiv10, - ratio: Ratio::_256x, - }; // 12500 Hz + config.mode = MODE_MASTER_32000; + // config.mode = Mode::Master { + // freq: MckFreq::_32MDiv10, + // ratio: Ratio::_256x, + // }; // 12500 Hz config.channels = Channels::Left; + config.swidth = SampleWidth::_16bit; let sample_rate = config.mode.sample_rate().expect("I2S Master"); let inv_sample_rate = 1.0 / sample_rate as f32; info!("Sample rate: {}", sample_rate); - let irq = interrupt::take!(I2S); - let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config); + // Wait for a button press + // let mut btn1 = Input::new(p.P1_00.degrade(), Pull::Up); + // btn1.wait_for_low().await; - const BUF_SAMPLES: usize = 500; - const BUF_SIZE: usize = BUF_SAMPLES; - let mut buf = AlignedBuffer([0i16; BUF_SIZE]); + let irq = interrupt::take!(I2S); + let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config).output(); + + type Sample = i16; + const MAX_UNIPOLAR_VALUE: Sample = (1 << 15) as Sample; + const NUM_SAMPLES: usize = 2000; + let mut buffers: [AlignedBuffer<[Sample; NUM_SAMPLES]>; 3] = [ + AlignedBuffer([0; NUM_SAMPLES]), + AlignedBuffer([0; NUM_SAMPLES]), + AlignedBuffer([0; NUM_SAMPLES]), + ]; let mut carrier = SineOsc::new(); - carrier.set_frequency(16.0, inv_sample_rate); - // let mut modulator = SineOsc::new(); - // modulator.set_frequency(0.01, inv_sample_rate); - // modulator.set_amplitude(0.2); + let mut freq_mod = SineOsc::new(); + freq_mod.set_frequency(8.0, inv_sample_rate); + freq_mod.set_amplitude(1.0); - let mut generate = |buf: &mut [i16]| { - for sample in buf.as_mut() { + let mut amp_mod = SineOsc::new(); + amp_mod.set_frequency(4.0, inv_sample_rate); + amp_mod.set_amplitude(0.5); + + let mut generate = |buf: &mut [Sample]| { + let ptr = buf as *const [Sample] as *const Sample as u32; + trace!("GEN: {}", ptr); + + for sample in &mut buf.as_mut().chunks_mut(1) { let signal = carrier.generate(); - // let modulation = bipolar_to_unipolar(modulator.generate()); - // carrier.set_frequency(200.0 + 100.0 * modulation, inv_sample_rate); - // carrier.set_amplitude((modulation); - let value = (i16::MAX as f32 * signal) as i16; - *sample = value; + let freq_modulation = bipolar_to_unipolar(freq_mod.generate()); + carrier.set_frequency(220.0 + 220.0 * freq_modulation, inv_sample_rate); + let amp_modulation = bipolar_to_unipolar(amp_mod.generate()); + carrier.set_amplitude(amp_modulation); + let value = (MAX_UNIPOLAR_VALUE as f32 * signal) as Sample; + sample[0] = value; } }; - generate(buf.as_mut().as_mut_slice()); + generate(buffers[0].as_mut().as_mut_slice()); + generate(buffers[1].as_mut().as_mut_slice()); - if let Err(err) = i2s.tx(buf.as_ref().as_slice()).await { - error!("{}", err); - } - - i2s.set_tx_enabled(true); - i2s.start(); + i2s.start(buffers[0].as_ref().as_slice()).expect("I2S Start"); + let mut index = 1; loop { - generate(buf.as_mut().as_mut_slice()); - - if let Err(err) = i2s.tx(buf.as_ref().as_slice()).await { + if let Err(err) = i2s.send(buffers[index].as_ref().as_slice()).await { error!("{}", err); } + + index += 1; + if index >= 3 { + index = 0; + } + generate(buffers[index].as_mut().as_mut_slice()); } } +#[derive(Clone)] struct SineOsc { amplitude: f32, modulo: f32, From 6b88057aef79e32a66c9c99cf048f905d10c2d3a Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sat, 19 Nov 2022 00:29:05 +0100 Subject: [PATCH 13/20] Add missing parts and Cleanup --- embassy-nrf/src/i2s.rs | 551 +++++++++++++++--- .../nrf/src/bin/{i2s.rs => i2s-generate.rs} | 73 +-- 2 files changed, 493 insertions(+), 131 deletions(-) rename examples/nrf/src/bin/{i2s.rs => i2s-generate.rs} (62%) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index e6dfb690c..53d9f9a1b 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -1,27 +1,27 @@ #![macro_use] -//! I2S +//! Support for I2S audio use core::future::poll_fn; use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; -use embassy_cortex_m::interrupt::{InterruptExt, Priority}; +use embassy_cortex_m::interrupt::InterruptExt; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::Interrupt; -use crate::pac::i2s::{RegisterBlock, CONFIG, PSEL}; +use crate::pac::i2s::RegisterBlock; use crate::Peripheral; // TODO: Define those in lib.rs somewhere else -// -// I2S EasyDMA MAXCNT bit length = 14 + +/// I2S EasyDMA MAXCNT bit length = 14 const MAX_DMA_MAXCNT: u32 = 1 << 14; -// Limits for Easy DMA - it can only read from data ram +/// Limits for Easy DMA - it can only read from data ram pub const SRAM_LOWER: usize = 0x2000_0000; pub const SRAM_UPPER: usize = 0x3000_0000; @@ -36,35 +36,144 @@ pub enum Error { BufferLengthMisaligned, } -pub const MODE_MASTER_8000: Mode = Mode::Master { - freq: MckFreq::_32MDiv125, - ratio: Ratio::_32x, -}; // error = 0 -pub const MODE_MASTER_11025: Mode = Mode::Master { - freq: MckFreq::_32MDiv15, - ratio: Ratio::_192x, -}; // error = 86 -pub const MODE_MASTER_16000: Mode = Mode::Master { - freq: MckFreq::_32MDiv21, - ratio: Ratio::_96x, -}; // error = 127 -pub const MODE_MASTER_22050: Mode = Mode::Master { - freq: MckFreq::_32MDiv15, - ratio: Ratio::_96x, -}; // error = 172 -pub const MODE_MASTER_32000: Mode = Mode::Master { - freq: MckFreq::_32MDiv21, - ratio: Ratio::_48x, -}; // error = 254 -pub const MODE_MASTER_44100: Mode = Mode::Master { - freq: MckFreq::_32MDiv15, - ratio: Ratio::_48x, -}; // error = 344 -pub const MODE_MASTER_48000: Mode = Mode::Master { - freq: MckFreq::_32MDiv21, - ratio: Ratio::_32x, -}; // error = 381 +/// Approximate sample rates. +/// +/// Those are common sample rates that can not be configured without an small error. +/// +/// For custom master clock configuration, please refer to [Mode]. +#[derive(Clone, Copy)] +pub enum ApproxSampleRate { + _11025, + _16000, + _22050, + _32000, + _44100, + _48000, +} +impl From for Mode { + fn from(value: ApproxSampleRate) -> Self { + match value { + // error = 86 + ApproxSampleRate::_11025 => Mode::Master { + freq: MckFreq::_32MDiv15, + ratio: Ratio::_192x, + }, + // error = 127 + ApproxSampleRate::_16000 => Mode::Master { + freq: MckFreq::_32MDiv21, + ratio: Ratio::_96x, + }, + // error = 172 + ApproxSampleRate::_22050 => Mode::Master { + freq: MckFreq::_32MDiv15, + ratio: Ratio::_96x, + }, + // error = 254 + ApproxSampleRate::_32000 => Mode::Master { + freq: MckFreq::_32MDiv21, + ratio: Ratio::_48x, + }, + // error = 344 + ApproxSampleRate::_44100 => Mode::Master { + freq: MckFreq::_32MDiv15, + ratio: Ratio::_48x, + }, + // error = 381 + ApproxSampleRate::_48000 => Mode::Master { + freq: MckFreq::_32MDiv21, + ratio: Ratio::_32x, + }, + } + } +} + +impl ApproxSampleRate { + pub fn sample_rate(&self) -> u32 { + // This will always provide a Master mode, so it is safe to unwrap. + Mode::from(*self).sample_rate().unwrap() + } +} + +/// Exact sample rates. +/// +/// Those are non standard sample rates that can be configured without error. +/// +/// For custom master clock configuration, please refer to [Mode]. +#[derive(Clone, Copy)] +pub enum ExactSampleRate { + _8000, + _10582, + _12500, + _15625, + _15873, + _25000, + _31250, + _50000, + _62500, + _100000, + _125000, +} + +impl ExactSampleRate { + pub fn sample_rate(&self) -> u32 { + // This will always provide a Master mode, so it is safe to unwrap. + Mode::from(*self).sample_rate().unwrap() + } +} + +impl From for Mode { + fn from(value: ExactSampleRate) -> Self { + match value { + ExactSampleRate::_8000 => Mode::Master { + freq: MckFreq::_32MDiv125, + ratio: Ratio::_32x, + }, + ExactSampleRate::_10582 => Mode::Master { + freq: MckFreq::_32MDiv63, + ratio: Ratio::_48x, + }, + ExactSampleRate::_12500 => Mode::Master { + freq: MckFreq::_32MDiv10, + ratio: Ratio::_256x, + }, + ExactSampleRate::_15625 => Mode::Master { + freq: MckFreq::_32MDiv32, + ratio: Ratio::_64x, + }, + ExactSampleRate::_15873 => Mode::Master { + freq: MckFreq::_32MDiv63, + ratio: Ratio::_32x, + }, + ExactSampleRate::_25000 => Mode::Master { + freq: MckFreq::_32MDiv10, + ratio: Ratio::_128x, + }, + ExactSampleRate::_31250 => Mode::Master { + freq: MckFreq::_32MDiv32, + ratio: Ratio::_32x, + }, + ExactSampleRate::_50000 => Mode::Master { + freq: MckFreq::_32MDiv10, + ratio: Ratio::_64x, + }, + ExactSampleRate::_62500 => Mode::Master { + freq: MckFreq::_32MDiv16, + ratio: Ratio::_32x, + }, + ExactSampleRate::_100000 => Mode::Master { + freq: MckFreq::_32MDiv10, + ratio: Ratio::_32x, + }, + ExactSampleRate::_125000 => Mode::Master { + freq: MckFreq::_32MDiv8, + ratio: Ratio::_32x, + }, + } + } +} + +/// I2S configuration. #[derive(Clone)] #[non_exhaustive] pub struct Config { @@ -78,7 +187,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { - mode: MODE_MASTER_32000, + mode: ExactSampleRate::_31250.into(), swidth: SampleWidth::_16bit, align: Align::Left, format: Format::I2S, @@ -132,10 +241,12 @@ impl MckFreq { 256000, ]; + /// Return the value that needs to be written to the register. pub fn to_register_value(&self) -> u32 { Self::REGISTER_VALUES[usize::from(*self)] } + /// Return the master clock frequency. pub fn to_frequency(&self) -> u32 { Self::FREQUENCIES[usize::from(*self)] } @@ -147,7 +258,10 @@ impl From for usize { } } -/// MCK / LRCK ratio. +/// Master clock frequency ratio +/// +/// Sample Rate = LRCK = MCK / Ratio +/// #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Ratio { _32x, @@ -175,6 +289,7 @@ impl From for u8 { } } +/// Sample width. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SampleWidth { _8bit, @@ -188,7 +303,7 @@ impl From for u8 { } } -/// Alignment of sample within a frame. +/// Channel used for the most significant sample value in a frame. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Align { Left, @@ -220,11 +335,13 @@ impl From for bool { } } -/// Enable channels. +/// Channels #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Channels { Stereo, + /// Mono left Left, + /// Mono right Right, } @@ -235,8 +352,6 @@ impl From for u8 { } /// Interface to the I2S peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. pub struct I2S<'d, T: Instance> { _p: PeripheralRef<'d, T>, } @@ -278,29 +393,32 @@ impl<'d, T: Instance> I2S<'d, T> { ) -> Self { into_ref!(i2s, irq, mck, sck, lrck, sdin, sdout); - let r = T::regs(); - Self::apply_config(&r.config, &config); - Self::select_pins(&r.psel, mck, sck, lrck, sdin, sdout); - Self::setup_interrupt(irq, r); + Self::apply_config(&config); + Self::select_pins(mck, sck, lrck, sdin, sdout); + Self::setup_interrupt(irq); - r.enable.write(|w| w.enable().enabled()); + T::regs().enable.write(|w| w.enable().enabled()); Self { _p: i2s } } + /// I2S output only pub fn output(self) -> Output<'d, T> { Output { _p: self._p } } + /// I2S input only pub fn input(self) -> Input<'d, T> { Input { _p: self._p } } + /// I2S full duplex (input and output) pub fn full_duplex(self) -> FullDuplex<'d, T> { FullDuplex { _p: self._p } } - fn apply_config(c: &CONFIG, config: &Config) { + fn apply_config(config: &Config) { + let c = &T::regs().config; match config.mode { Mode::Master { freq, ratio } => { c.mode.write(|w| w.mode().master()); @@ -322,13 +440,14 @@ impl<'d, T: Instance> I2S<'d, T> { } fn select_pins( - psel: &PSEL, mck: PeripheralRef<'d, AnyPin>, sck: PeripheralRef<'d, AnyPin>, lrck: PeripheralRef<'d, AnyPin>, sdin: PeripheralRef<'d, AnyPin>, sdout: PeripheralRef<'d, AnyPin>, ) { + let psel = &T::regs().psel; + psel.mck.write(|w| { unsafe { w.bits(mck.psel_bits()) }; w.connect().connected() @@ -355,21 +474,23 @@ impl<'d, T: Instance> I2S<'d, T> { }); } - fn setup_interrupt(irq: PeripheralRef<'d, T::Interrupt>, r: &RegisterBlock) { + fn setup_interrupt(irq: PeripheralRef<'d, T::Interrupt>) { irq.set_handler(Self::on_interrupt); - // irq.set_priority(Priority::P1); // TODO review priorities irq.unpend(); irq.enable(); let device = Device::::new(); device.disable_tx_ptr_interrupt(); device.disable_rx_ptr_interrupt(); + device.disable_stopped_interrupt(); device.reset_tx_ptr_event(); device.reset_rx_ptr_event(); + device.reset_stopped_event(); device.enable_tx_ptr_interrupt(); device.enable_rx_ptr_interrupt(); + device.enable_stopped_interrupt(); } fn on_interrupt(_: *mut ()) { @@ -387,41 +508,40 @@ impl<'d, T: Instance> I2S<'d, T> { s.rx_waker.wake(); device.disable_rx_ptr_interrupt(); } + + if device.is_stopped() { + trace!("STOPPED INT"); + s.stop_waker.wake(); + device.disable_stopped_interrupt(); + } } -} -pub struct Output<'d, T: Instance> { - _p: PeripheralRef<'d, T>, -} - -impl<'d, T: Instance> Output<'d, T> { - /// Starts I2S transfer. - #[inline(always)] - pub fn start(&self, buffer: B) -> Result<(), Error> - where - B: Buffer, - { - // TODO what to do if it is started already? + async fn stop() { + compiler_fence(Ordering::SeqCst); let device = Device::::new(); - device.enable(); - device.set_tx_buffer(buffer)?; - device.enable_tx(); - device.start(); + device.stop(); - Ok(()) + T::state().started.store(false, Ordering::Relaxed); + + poll_fn(|cx| { + T::state().stop_waker.register(cx.waker()); + + if device.is_stopped() { + trace!("STOP: Ready"); + device.reset_stopped_event(); + Poll::Ready(()) + } else { + trace!("STOP: Pending"); + Poll::Pending + } + }) + .await; + + device.disable(); } - /// Stops the I2S transfer and waits until it has stopped. - #[inline(always)] - pub async fn stop(&self) { - todo!() - } - - /// Transmits the given `buffer`. - /// Buffer address must be 4 byte aligned and located in RAM. - #[allow(unused_mut)] - pub async fn send(&mut self, buffer: B) -> Result<(), Error> + async fn send(buffer: B) -> Result<(), Error> where B: Buffer, { @@ -454,24 +574,191 @@ impl<'d, T: Instance> Output<'d, T> { Ok(()) } + + async fn receive(buffer: B) -> Result<(), Error> + where + B: Buffer, + { + trace!("RECEIVE: {}", buffer.bytes_ptr() as u32); + + let device = Device::::new(); + let drop = device.on_rx_drop(); + + compiler_fence(Ordering::SeqCst); + + poll_fn(|cx| { + T::state().rx_waker.register(cx.waker()); + + if device.is_rx_ptr_updated() { + trace!("RX POLL: Ready"); + device.reset_rx_ptr_event(); + device.enable_rx_ptr_interrupt(); + Poll::Ready(()) + } else { + trace!("RX POLL: Pending"); + Poll::Pending + } + }) + .await; + + device.set_rx_buffer(buffer)?; + + compiler_fence(Ordering::SeqCst); + drop.defuse(); + + Ok(()) + } } +/// I2S output +pub struct Output<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Output<'d, T> { + /// Prepare the initial buffer and start the I2S transfer. + pub async fn start(&self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_tx(); + device.set_tx_buffer(buffer)?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the given `buffer` for transmission in the DMA. + /// Buffer address must be 4 byte aligned and located in RAM. + /// The buffer must not be written while being used by the DMA, + /// which takes two other `send`s being awaited. + #[allow(unused_mut)] + pub async fn send(&mut self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + I2S::::send(buffer).await + } +} + +/// I2S input pub struct Input<'d, T: Instance> { _p: PeripheralRef<'d, T>, } impl<'d, T: Instance> Input<'d, T> { - // TODO + /// Prepare the initial buffer and start the I2S transfer. + pub async fn start(&self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_rx(); + device.set_rx_buffer(buffer)?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the given `buffer` for reception from the DMA. + /// Buffer address must be 4 byte aligned and located in RAM. + /// The buffer must not be read while being used by the DMA, + /// which takes two other `receive`s being awaited. + #[allow(unused_mut)] + pub async fn receive(&mut self, buffer: B) -> Result<(), Error> + where + B: Buffer, + { + I2S::::receive(buffer).await + } } +/// I2S ful duplex (input & output) pub struct FullDuplex<'d, T: Instance> { _p: PeripheralRef<'d, T>, } impl<'d, T: Instance> FullDuplex<'d, T> { - // TODO + /// Prepare the initial buffers and start the I2S transfer. + pub async fn start(&self, buffer_out: B, buffer_in: B) -> Result<(), Error> + where + B: Buffer, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_tx(); + device.enable_rx(); + device.set_tx_buffer(buffer_out)?; + device.set_rx_buffer(buffer_in)?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the given `buffer_out` and `buffer_in` for transmission/reception from the DMA. + /// Buffer address must be 4 byte aligned and located in RAM. + /// The buffers must not be written/read while being used by the DMA, + /// which takes two other `send_and_receive` operations being awaited. + #[allow(unused_mut)] + pub async fn send_and_receive(&mut self, buffer_out: B, buffer_in: B) -> Result<(), Error> + where + B: Buffer, + { + I2S::::send(buffer_out).await?; + I2S::::receive(buffer_in).await?; + Ok(()) + } } +/// Helper encapsulating common I2S device operations. struct Device(&'static RegisterBlock, PhantomData); impl Device { @@ -521,6 +808,34 @@ impl Device { self.0.tasks_start.write(|w| unsafe { w.bits(1) }); } + #[inline(always)] + fn stop(&self) { + self.0.tasks_stop.write(|w| unsafe { w.bits(1) }); + } + + #[inline(always)] + fn is_stopped(&self) -> bool { + self.0.events_stopped.read().bits() != 0 + } + + #[inline(always)] + fn reset_stopped_event(&self) { + trace!("STOPPED EVENT: Reset"); + self.0.events_stopped.reset(); + } + + #[inline(always)] + fn disable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.stopped().clear()); + } + + #[inline(always)] + fn enable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.stopped().set()); + } + #[inline] fn set_tx_buffer(&self, buffer: B) -> Result<(), Error> where @@ -606,6 +921,23 @@ impl Device { }) } + #[inline] + fn on_rx_drop(&self) -> OnDrop { + OnDrop::new(move || { + trace!("RX DROP: Stopping"); + + let device = Device::::new(); + device.disable_rx_ptr_interrupt(); + device.reset_rx_ptr_event(); + device.disable_rx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_rx_ptr_updated() {} + + trace!("RX DROP: Stopped"); + }) + } + fn validate_buffer(buffer: B) -> Result<(u32, u32), Error> where B: Buffer, @@ -632,6 +964,56 @@ impl Device { } } +/// Sample details +pub trait Sample: Sized + Copy + Default { + const WIDTH: usize; + const SCALE: Self; +} + +impl Sample for i8 { + const WIDTH: usize = 8; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +impl Sample for i16 { + const WIDTH: usize = 16; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +impl Sample for i32 { + const WIDTH: usize = 24; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +/// A 4-bytes aligned [Buffer]. +#[repr(align(4))] +pub struct AlignedBuffer([T; N]); + +impl AlignedBuffer { + pub fn new(array: [T; N]) -> Self { + Self(array) + } +} + +impl Default for AlignedBuffer { + fn default() -> Self { + Self([T::default(); N]) + } +} + +impl AsRef<[T]> for AlignedBuffer { + fn as_ref(&self) -> &[T] { + self.0.as_slice() + } +} + +impl AsMut<[T]> for AlignedBuffer { + fn as_mut(&mut self) -> &mut [T] { + self.0.as_mut_slice() + } +} + +/// Common operations required for a buffer to be used by the DMA pub trait Buffer: Sized { fn bytes_ptr(&self) -> *const u8; fn bytes_len(&self) -> usize; @@ -674,22 +1056,25 @@ impl Buffer for &[i32] { } pub(crate) mod sealed { - use core::sync::atomic::AtomicI32; + use core::sync::atomic::AtomicBool; use embassy_sync::waitqueue::AtomicWaker; - use super::*; - + /// Peripheral static state pub struct State { + pub started: AtomicBool, pub rx_waker: AtomicWaker, pub tx_waker: AtomicWaker, + pub stop_waker: AtomicWaker, } impl State { pub const fn new() -> Self { Self { + started: AtomicBool::new(false), rx_waker: AtomicWaker::new(), tx_waker: AtomicWaker::new(), + stop_waker: AtomicWaker::new(), } } } @@ -704,8 +1089,6 @@ pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { type Interrupt: Interrupt; } -// TODO: Unsure why this macro is flagged as unused by CI when in fact it's used elsewhere? -#[allow(unused_macros)] macro_rules! impl_i2s { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::i2s::sealed::Instance for peripherals::$type { diff --git a/examples/nrf/src/bin/i2s.rs b/examples/nrf/src/bin/i2s-generate.rs similarity index 62% rename from examples/nrf/src/bin/i2s.rs rename to examples/nrf/src/bin/i2s-generate.rs index 9b3144f24..f59b63ce6 100644 --- a/examples/nrf/src/bin/i2s.rs +++ b/examples/nrf/src/bin/i2s-generate.rs @@ -4,59 +4,41 @@ use core::f32::consts::PI; -use defmt::{error, info, trace}; +use defmt::{error, info}; use embassy_executor::Spawner; -use embassy_nrf::gpio::{Input, Pin, Pull}; -use embassy_nrf::i2s::{Channels, MckFreq, Mode, Ratio, SampleWidth, MODE_MASTER_32000}; -use embassy_nrf::pac::ficr::info; -use embassy_nrf::{i2s, interrupt}; +use embassy_nrf::i2s::{self, Sample as _}; +use embassy_nrf::interrupt; use {defmt_rtt as _, panic_probe as _}; -#[repr(align(4))] -pub struct AlignedBuffer(T); - -impl AsRef for AlignedBuffer { - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl AsMut for AlignedBuffer { - fn as_mut(&mut self) -> &mut T { - &mut self.0 - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); + let mut config = i2s::Config::default(); - config.mode = MODE_MASTER_32000; - // config.mode = Mode::Master { - // freq: MckFreq::_32MDiv10, - // ratio: Ratio::_256x, - // }; // 12500 Hz - config.channels = Channels::Left; - config.swidth = SampleWidth::_16bit; + config.mode = i2s::ExactSampleRate::_50000.into(); + config.channels = i2s::Channels::Left; + config.swidth = i2s::SampleWidth::_16bit; let sample_rate = config.mode.sample_rate().expect("I2S Master"); let inv_sample_rate = 1.0 / sample_rate as f32; info!("Sample rate: {}", sample_rate); // Wait for a button press + // use embassy_nrf::gpio::{Input, Pin, Pull}; // let mut btn1 = Input::new(p.P1_00.degrade(), Pull::Up); // btn1.wait_for_low().await; let irq = interrupt::take!(I2S); - let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config).output(); + let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_27, p.P0_30, config) + .output(); type Sample = i16; - const MAX_UNIPOLAR_VALUE: Sample = (1 << 15) as Sample; - const NUM_SAMPLES: usize = 2000; - let mut buffers: [AlignedBuffer<[Sample; NUM_SAMPLES]>; 3] = [ - AlignedBuffer([0; NUM_SAMPLES]), - AlignedBuffer([0; NUM_SAMPLES]), - AlignedBuffer([0; NUM_SAMPLES]), + const NUM_SAMPLES: usize = 6000; + + let mut buffers: [i2s::AlignedBuffer; 3] = [ + i2s::AlignedBuffer::default(), + i2s::AlignedBuffer::default(), + i2s::AlignedBuffer::default(), ]; let mut carrier = SineOsc::new(); @@ -66,32 +48,29 @@ async fn main(_spawner: Spawner) { freq_mod.set_amplitude(1.0); let mut amp_mod = SineOsc::new(); - amp_mod.set_frequency(4.0, inv_sample_rate); + amp_mod.set_frequency(16.0, inv_sample_rate); amp_mod.set_amplitude(0.5); let mut generate = |buf: &mut [Sample]| { - let ptr = buf as *const [Sample] as *const Sample as u32; - trace!("GEN: {}", ptr); - - for sample in &mut buf.as_mut().chunks_mut(1) { - let signal = carrier.generate(); + for sample in &mut buf.chunks_mut(1) { let freq_modulation = bipolar_to_unipolar(freq_mod.generate()); - carrier.set_frequency(220.0 + 220.0 * freq_modulation, inv_sample_rate); + carrier.set_frequency(220.0 + 440.0 * freq_modulation, inv_sample_rate); let amp_modulation = bipolar_to_unipolar(amp_mod.generate()); carrier.set_amplitude(amp_modulation); - let value = (MAX_UNIPOLAR_VALUE as f32 * signal) as Sample; + let signal = carrier.generate(); + let value = (Sample::SCALE as f32 * signal) as Sample; sample[0] = value; } }; - generate(buffers[0].as_mut().as_mut_slice()); - generate(buffers[1].as_mut().as_mut_slice()); + generate(buffers[0].as_mut()); + generate(buffers[1].as_mut()); - i2s.start(buffers[0].as_ref().as_slice()).expect("I2S Start"); + i2s.start(buffers[0].as_ref()).await.expect("I2S Start"); let mut index = 1; loop { - if let Err(err) = i2s.send(buffers[index].as_ref().as_slice()).await { + if let Err(err) = i2s.send(buffers[index].as_ref()).await { error!("{}", err); } @@ -99,7 +78,7 @@ async fn main(_spawner: Spawner) { if index >= 3 { index = 0; } - generate(buffers[index].as_mut().as_mut_slice()); + generate(buffers[index].as_mut()); } } From 16838f8a66d8a3b74f0fe1ab9124532226e7166a Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sat, 19 Nov 2022 00:32:09 +0100 Subject: [PATCH 14/20] Fix format --- examples/nrf/src/bin/i2s-generate.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/nrf/src/bin/i2s-generate.rs b/examples/nrf/src/bin/i2s-generate.rs index f59b63ce6..c2b5578f3 100644 --- a/examples/nrf/src/bin/i2s-generate.rs +++ b/examples/nrf/src/bin/i2s-generate.rs @@ -29,8 +29,7 @@ async fn main(_spawner: Spawner) { // btn1.wait_for_low().await; let irq = interrupt::take!(I2S); - let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_27, p.P0_30, config) - .output(); + let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_27, p.P0_30, config).output(); type Sample = i16; const NUM_SAMPLES: usize = 6000; From 64e8cfef8e53293b35d2c5ea2b822bdc3a12111e Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sat, 19 Nov 2022 01:38:03 +0100 Subject: [PATCH 15/20] Fix build --- embassy-nrf/src/chips/nrf52832.rs | 7 ++++++- embassy-nrf/src/chips/nrf52833.rs | 7 ++++++- embassy-nrf/src/chips/nrf52840.rs | 2 +- embassy-nrf/src/i2s.rs | 7 ++----- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 81e66c193..a0aaba9df 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -138,6 +138,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // I2S + I2S, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -234,6 +237,8 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); +impl_i2s!(I2S, I2S, I2S); + pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; @@ -274,6 +279,6 @@ pub mod irqs { declare!(PWM2); declare!(SPIM2_SPIS2_SPI2); declare!(RTC2); - declare!(I2S); declare!(FPU); + declare!(I2S); } diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 92499e3c9..9063f486a 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -161,6 +161,9 @@ embassy_hal_common::peripherals! { // PDM PDM, + + // I2S + I2S, } #[cfg(feature = "nightly")] @@ -280,6 +283,8 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); +impl_i2s!(I2S, I2S, I2S); + pub mod irqs { use embassy_cortex_m::interrupt::_export::declare; @@ -320,10 +325,10 @@ pub mod irqs { declare!(PWM2); declare!(SPIM2_SPIS2_SPI2); declare!(RTC2); - declare!(I2S); declare!(FPU); declare!(USBD); declare!(UARTE1); declare!(PWM3); declare!(SPIM3); + declare!(I2S); } diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index cf800c7b4..4047ee0ac 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -330,7 +330,6 @@ pub mod irqs { declare!(PWM2); declare!(SPIM2_SPIS2_SPI2); declare!(RTC2); - declare!(I2S); declare!(FPU); declare!(USBD); declare!(UARTE1); @@ -338,4 +337,5 @@ pub mod irqs { declare!(CRYPTOCELL); declare!(PWM3); declare!(SPIM3); + declare!(I2S); } diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 53d9f9a1b..52b72df27 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -14,13 +14,10 @@ use embassy_hal_common::{into_ref, PeripheralRef}; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::Interrupt; use crate::pac::i2s::RegisterBlock; -use crate::Peripheral; +use crate::{EASY_DMA_SIZE, Peripheral}; // TODO: Define those in lib.rs somewhere else -/// I2S EasyDMA MAXCNT bit length = 14 -const MAX_DMA_MAXCNT: u32 = 1 << 14; - /// Limits for Easy DMA - it can only read from data ram pub const SRAM_LOWER: usize = 0x2000_0000; pub const SRAM_UPPER: usize = 0x3000_0000; @@ -956,7 +953,7 @@ impl Device { Err(Error::BufferLengthMisaligned) } else if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { Err(Error::BufferNotInDataMemory) - } else if maxcnt > MAX_DMA_MAXCNT { + } else if maxcnt as usize > EASY_DMA_SIZE { Err(Error::BufferTooLong) } else { Ok((ptr, maxcnt)) From f5391efe22374e0c55005f20c6a644bda3acd50c Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sat, 19 Nov 2022 02:17:58 +0100 Subject: [PATCH 16/20] Fix fmt --- embassy-nrf/src/i2s.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 52b72df27..11c09a229 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -14,7 +14,7 @@ use embassy_hal_common::{into_ref, PeripheralRef}; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::Interrupt; use crate::pac::i2s::RegisterBlock; -use crate::{EASY_DMA_SIZE, Peripheral}; +use crate::{Peripheral, EASY_DMA_SIZE}; // TODO: Define those in lib.rs somewhere else From 15a93246d6bb3e0bea268ff919bca073a9890247 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sat, 19 Nov 2022 19:18:20 +0100 Subject: [PATCH 17/20] Buffer management in line with other peripherals. Constructor and config redesign --- embassy-nrf/src/i2s.rs | 721 ++++++++---------- .../bin/{i2s-generate.rs => i2s_waveform.rs} | 103 ++- 2 files changed, 395 insertions(+), 429 deletions(-) rename examples/nrf/src/bin/{i2s-generate.rs => i2s_waveform.rs} (50%) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 11c09a229..d5815160a 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -4,6 +4,8 @@ use core::future::poll_fn; use core::marker::PhantomData; +use core::mem::size_of; +use core::ops::{Deref, DerefMut}; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; @@ -14,14 +16,9 @@ use embassy_hal_common::{into_ref, PeripheralRef}; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::Interrupt; use crate::pac::i2s::RegisterBlock; +use crate::util::{slice_in_ram_or, slice_ptr_parts}; use crate::{Peripheral, EASY_DMA_SIZE}; -// TODO: Define those in lib.rs somewhere else - -/// Limits for Easy DMA - it can only read from data ram -pub const SRAM_LOWER: usize = 0x2000_0000; -pub const SRAM_UPPER: usize = 0x3000_0000; - #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -33,159 +30,42 @@ pub enum Error { BufferLengthMisaligned, } -/// Approximate sample rates. -/// -/// Those are common sample rates that can not be configured without an small error. -/// -/// For custom master clock configuration, please refer to [Mode]. -#[derive(Clone, Copy)] -pub enum ApproxSampleRate { - _11025, - _16000, - _22050, - _32000, - _44100, - _48000, -} - -impl From for Mode { - fn from(value: ApproxSampleRate) -> Self { - match value { - // error = 86 - ApproxSampleRate::_11025 => Mode::Master { - freq: MckFreq::_32MDiv15, - ratio: Ratio::_192x, - }, - // error = 127 - ApproxSampleRate::_16000 => Mode::Master { - freq: MckFreq::_32MDiv21, - ratio: Ratio::_96x, - }, - // error = 172 - ApproxSampleRate::_22050 => Mode::Master { - freq: MckFreq::_32MDiv15, - ratio: Ratio::_96x, - }, - // error = 254 - ApproxSampleRate::_32000 => Mode::Master { - freq: MckFreq::_32MDiv21, - ratio: Ratio::_48x, - }, - // error = 344 - ApproxSampleRate::_44100 => Mode::Master { - freq: MckFreq::_32MDiv15, - ratio: Ratio::_48x, - }, - // error = 381 - ApproxSampleRate::_48000 => Mode::Master { - freq: MckFreq::_32MDiv21, - ratio: Ratio::_32x, - }, - } - } -} - -impl ApproxSampleRate { - pub fn sample_rate(&self) -> u32 { - // This will always provide a Master mode, so it is safe to unwrap. - Mode::from(*self).sample_rate().unwrap() - } -} - -/// Exact sample rates. -/// -/// Those are non standard sample rates that can be configured without error. -/// -/// For custom master clock configuration, please refer to [Mode]. -#[derive(Clone, Copy)] -pub enum ExactSampleRate { - _8000, - _10582, - _12500, - _15625, - _15873, - _25000, - _31250, - _50000, - _62500, - _100000, - _125000, -} - -impl ExactSampleRate { - pub fn sample_rate(&self) -> u32 { - // This will always provide a Master mode, so it is safe to unwrap. - Mode::from(*self).sample_rate().unwrap() - } -} - -impl From for Mode { - fn from(value: ExactSampleRate) -> Self { - match value { - ExactSampleRate::_8000 => Mode::Master { - freq: MckFreq::_32MDiv125, - ratio: Ratio::_32x, - }, - ExactSampleRate::_10582 => Mode::Master { - freq: MckFreq::_32MDiv63, - ratio: Ratio::_48x, - }, - ExactSampleRate::_12500 => Mode::Master { - freq: MckFreq::_32MDiv10, - ratio: Ratio::_256x, - }, - ExactSampleRate::_15625 => Mode::Master { - freq: MckFreq::_32MDiv32, - ratio: Ratio::_64x, - }, - ExactSampleRate::_15873 => Mode::Master { - freq: MckFreq::_32MDiv63, - ratio: Ratio::_32x, - }, - ExactSampleRate::_25000 => Mode::Master { - freq: MckFreq::_32MDiv10, - ratio: Ratio::_128x, - }, - ExactSampleRate::_31250 => Mode::Master { - freq: MckFreq::_32MDiv32, - ratio: Ratio::_32x, - }, - ExactSampleRate::_50000 => Mode::Master { - freq: MckFreq::_32MDiv10, - ratio: Ratio::_64x, - }, - ExactSampleRate::_62500 => Mode::Master { - freq: MckFreq::_32MDiv16, - ratio: Ratio::_32x, - }, - ExactSampleRate::_100000 => Mode::Master { - freq: MckFreq::_32MDiv10, - ratio: Ratio::_32x, - }, - ExactSampleRate::_125000 => Mode::Master { - freq: MckFreq::_32MDiv8, - ratio: Ratio::_32x, - }, - } - } -} - /// I2S configuration. #[derive(Clone)] #[non_exhaustive] pub struct Config { - pub mode: Mode, - pub swidth: SampleWidth, + pub sample_width: SampleWidth, pub align: Align, pub format: Format, pub channels: Channels, } +impl Config { + pub fn sample_width(mut self, sample_width: SampleWidth) -> Self { + self.sample_width = sample_width; + self + } + + pub fn align(mut self, align: Align) -> Self { + self.align = align; + self + } + + pub fn format(mut self, format: Format) -> Self { + self.format = format; + self + } + + pub fn channels(mut self, channels: Channels) -> Self { + self.channels = channels; + self + } +} + impl Default for Config { fn default() -> Self { Self { - mode: ExactSampleRate::_31250.into(), - swidth: SampleWidth::_16bit, + sample_width: SampleWidth::_16bit, align: Align::Left, format: Format::I2S, channels: Channels::Stereo, @@ -195,17 +75,20 @@ impl Default for Config { /// I2S Mode #[derive(Debug, Eq, PartialEq, Clone, Copy)] -pub enum Mode { - Master { freq: MckFreq, ratio: Ratio }, - Slave, +pub struct MasterClock { + freq: MckFreq, + ratio: Ratio, } -impl Mode { - pub fn sample_rate(&self) -> Option { - match self { - Mode::Master { freq, ratio } => Some(freq.to_frequency() / ratio.to_divisor()), - Mode::Slave => None, - } +impl MasterClock { + pub fn new(freq: MckFreq, ratio: Ratio) -> Self { + Self { freq, ratio } + } +} + +impl MasterClock { + pub fn sample_rate(&self) -> u32 { + self.freq.to_frequency() / self.ratio.to_divisor() } } @@ -275,17 +158,106 @@ pub enum Ratio { impl Ratio { const RATIOS: &'static [u32] = &[32, 48, 64, 96, 128, 192, 256, 384, 512]; + /// Return the value that needs to be written to the register. + pub fn to_register_value(&self) -> u8 { + usize::from(*self) as u8 + } + pub fn to_divisor(&self) -> u32 { - Self::RATIOS[u8::from(*self) as usize] + Self::RATIOS[usize::from(*self)] } } -impl From for u8 { +impl From for usize { fn from(variant: Ratio) -> Self { variant as _ } } +/// Approximate sample rates. +/// +/// Those are common sample rates that can not be configured without an small error. +/// +/// For custom master clock configuration, please refer to [MasterClock]. +#[derive(Clone, Copy)] +pub enum ApproxSampleRate { + _11025, + _16000, + _22050, + _32000, + _44100, + _48000, +} + +impl From for MasterClock { + fn from(value: ApproxSampleRate) -> Self { + match value { + // error = 86 + ApproxSampleRate::_11025 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_192x), + // error = 127 + ApproxSampleRate::_16000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_96x), + // error = 172 + ApproxSampleRate::_22050 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_96x), + // error = 254 + ApproxSampleRate::_32000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_48x), + // error = 344 + ApproxSampleRate::_44100 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_48x), + // error = 381 + ApproxSampleRate::_48000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_32x), + } + } +} + +impl ApproxSampleRate { + pub fn sample_rate(&self) -> u32 { + MasterClock::from(*self).sample_rate() + } +} + +/// Exact sample rates. +/// +/// Those are non standard sample rates that can be configured without error. +/// +/// For custom master clock configuration, please refer to [Mode]. +#[derive(Clone, Copy)] +pub enum ExactSampleRate { + _8000, + _10582, + _12500, + _15625, + _15873, + _25000, + _31250, + _50000, + _62500, + _100000, + _125000, +} + +impl ExactSampleRate { + pub fn sample_rate(&self) -> u32 { + MasterClock::from(*self).sample_rate() + } +} + +impl From for MasterClock { + fn from(value: ExactSampleRate) -> Self { + match value { + ExactSampleRate::_8000 => MasterClock::new(MckFreq::_32MDiv125, Ratio::_32x), + ExactSampleRate::_10582 => MasterClock::new(MckFreq::_32MDiv63, Ratio::_48x), + ExactSampleRate::_12500 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_256x), + ExactSampleRate::_15625 => MasterClock::new(MckFreq::_32MDiv32, Ratio::_64x), + ExactSampleRate::_15873 => MasterClock::new(MckFreq::_32MDiv63, Ratio::_32x), + ExactSampleRate::_25000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_128x), + ExactSampleRate::_31250 => MasterClock::new(MckFreq::_32MDiv32, Ratio::_32x), + ExactSampleRate::_50000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_64x), + ExactSampleRate::_62500 => MasterClock::new(MckFreq::_32MDiv16, Ratio::_32x), + ExactSampleRate::_100000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_32x), + ExactSampleRate::_125000 => MasterClock::new(MckFreq::_32MDiv8, Ratio::_32x), + } + } +} + /// Sample width. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SampleWidth { @@ -336,10 +308,8 @@ impl From for bool { #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Channels { Stereo, - /// Mono left - Left, - /// Mono right - Right, + MonoLeft, + MonoRight, } impl From for u8 { @@ -350,131 +320,160 @@ impl From for u8 { /// Interface to the I2S peripheral using EasyDMA to offload the transmission and reception workload. pub struct I2S<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + i2s: PeripheralRef<'d, T>, + irq: PeripheralRef<'d, T::Interrupt>, + mck: Option>, + sck: PeripheralRef<'d, AnyPin>, + lrck: PeripheralRef<'d, AnyPin>, + sdin: Option>, + sdout: Option>, + master_clock: Option, + config: Config, } impl<'d, T: Instance> I2S<'d, T> { - /// Create a new I2S - pub fn new( + /// Create a new I2S in master mode + pub fn master( i2s: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, mck: impl Peripheral

+ 'd, sck: impl Peripheral

+ 'd, lrck: impl Peripheral

+ 'd, - sdin: impl Peripheral

+ 'd, - sdout: impl Peripheral

+ 'd, + master_clock: MasterClock, config: Config, ) -> Self { - into_ref!(mck, sck, lrck, sdin, sdout); - Self::new_inner( + into_ref!(i2s, irq, mck, sck, lrck); + Self { i2s, irq, - mck.map_into(), - sck.map_into(), - lrck.map_into(), - sdin.map_into(), - sdout.map_into(), + mck: Some(mck.map_into()), + sck: sck.map_into(), + lrck: lrck.map_into(), + sdin: None, + sdout: None, + master_clock: Some(master_clock), config, - ) + } } - fn new_inner( + /// Create a new I2S in slave mode + pub fn slave( i2s: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, - mck: PeripheralRef<'d, AnyPin>, - sck: PeripheralRef<'d, AnyPin>, - lrck: PeripheralRef<'d, AnyPin>, - sdin: PeripheralRef<'d, AnyPin>, - sdout: PeripheralRef<'d, AnyPin>, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(i2s, irq, mck, sck, lrck, sdin, sdout); - - Self::apply_config(&config); - Self::select_pins(mck, sck, lrck, sdin, sdout); - Self::setup_interrupt(irq); - - T::regs().enable.write(|w| w.enable().enabled()); - - Self { _p: i2s } + into_ref!(i2s, irq, sck, lrck); + Self { + i2s, + irq, + mck: None, + sck: sck.map_into(), + lrck: lrck.map_into(), + sdin: None, + sdout: None, + master_clock: None, + config, + } } /// I2S output only - pub fn output(self) -> Output<'d, T> { - Output { _p: self._p } + pub fn output(mut self, sdout: impl Peripheral

+ 'd) -> OutputStream<'d, T> { + self.sdout = Some(sdout.into_ref().map_into()); + OutputStream { _p: self.build() } } /// I2S input only - pub fn input(self) -> Input<'d, T> { - Input { _p: self._p } + pub fn input(mut self, sdin: impl Peripheral

+ 'd) -> InputStream<'d, T> { + self.sdin = Some(sdin.into_ref().map_into()); + InputStream { _p: self.build() } } /// I2S full duplex (input and output) - pub fn full_duplex(self) -> FullDuplex<'d, T> { - FullDuplex { _p: self._p } + pub fn full_duplex( + mut self, + sdin: impl Peripheral

+ 'd, + sdout: impl Peripheral

+ 'd, + ) -> FullDuplexStream<'d, T> { + self.sdout = Some(sdout.into_ref().map_into()); + self.sdin = Some(sdin.into_ref().map_into()); + FullDuplexStream { _p: self.build() } } - fn apply_config(config: &Config) { + fn build(self) -> PeripheralRef<'d, T> { + self.apply_config(); + self.select_pins(); + self.setup_interrupt(); + + let device = Device::::new(); + device.enable(); + + self.i2s + } + + fn apply_config(&self) { let c = &T::regs().config; - match config.mode { - Mode::Master { freq, ratio } => { + match &self.master_clock { + Some(MasterClock { freq, ratio }) => { c.mode.write(|w| w.mode().master()); c.mcken.write(|w| w.mcken().enabled()); c.mckfreq .write(|w| unsafe { w.mckfreq().bits(freq.to_register_value()) }); - c.ratio.write(|w| unsafe { w.ratio().bits(ratio.into()) }); + c.ratio.write(|w| unsafe { w.ratio().bits(ratio.to_register_value()) }); } - Mode::Slave => { + None => { c.mode.write(|w| w.mode().slave()); } }; - c.swidth.write(|w| unsafe { w.swidth().bits(config.swidth.into()) }); - c.align.write(|w| w.align().bit(config.align.into())); - c.format.write(|w| w.format().bit(config.format.into())); + c.swidth + .write(|w| unsafe { w.swidth().bits(self.config.sample_width.into()) }); + c.align.write(|w| w.align().bit(self.config.align.into())); + c.format.write(|w| w.format().bit(self.config.format.into())); c.channels - .write(|w| unsafe { w.channels().bits(config.channels.into()) }); + .write(|w| unsafe { w.channels().bits(self.config.channels.into()) }); } - fn select_pins( - mck: PeripheralRef<'d, AnyPin>, - sck: PeripheralRef<'d, AnyPin>, - lrck: PeripheralRef<'d, AnyPin>, - sdin: PeripheralRef<'d, AnyPin>, - sdout: PeripheralRef<'d, AnyPin>, - ) { + fn select_pins(&self) { let psel = &T::regs().psel; - psel.mck.write(|w| { - unsafe { w.bits(mck.psel_bits()) }; - w.connect().connected() - }); + if let Some(mck) = &self.mck { + psel.mck.write(|w| { + unsafe { w.bits(mck.psel_bits()) }; + w.connect().connected() + }); + } psel.sck.write(|w| { - unsafe { w.bits(sck.psel_bits()) }; + unsafe { w.bits(self.sck.psel_bits()) }; w.connect().connected() }); psel.lrck.write(|w| { - unsafe { w.bits(lrck.psel_bits()) }; + unsafe { w.bits(self.lrck.psel_bits()) }; w.connect().connected() }); - psel.sdin.write(|w| { - unsafe { w.bits(sdin.psel_bits()) }; - w.connect().connected() - }); + if let Some(sdin) = &self.sdin { + psel.sdin.write(|w| { + unsafe { w.bits(sdin.psel_bits()) }; + w.connect().connected() + }); + } - psel.sdout.write(|w| { - unsafe { w.bits(sdout.psel_bits()) }; - w.connect().connected() - }); + if let Some(sdout) = &self.sdout { + psel.sdout.write(|w| { + unsafe { w.bits(sdout.psel_bits()) }; + w.connect().connected() + }); + } } - fn setup_interrupt(irq: PeripheralRef<'d, T::Interrupt>) { - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + fn setup_interrupt(&self) { + self.irq.set_handler(Self::on_interrupt); + self.irq.unpend(); + self.irq.enable(); let device = Device::::new(); device.disable_tx_ptr_interrupt(); @@ -538,17 +537,32 @@ impl<'d, T: Instance> I2S<'d, T> { device.disable(); } - async fn send(buffer: B) -> Result<(), Error> + async fn send_from_ram(buffer_ptr: *const [S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { - trace!("SEND: {}", buffer.bytes_ptr() as u32); + trace!("SEND: {}", buffer_ptr as *const S as u32); - let device = Device::::new(); - let drop = device.on_tx_drop(); + slice_in_ram_or(buffer_ptr, Error::BufferNotInDataMemory)?; compiler_fence(Ordering::SeqCst); + let device = Device::::new(); + + let drop = OnDrop::new(move || { + trace!("TX DROP: Stopping"); + + let device = Device::::new(); + device.disable_tx_ptr_interrupt(); + device.reset_tx_ptr_event(); + device.disable_tx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_tx_ptr_updated() {} + + trace!("TX DROP: Stopped"); + }); + poll_fn(|cx| { T::state().tx_waker.register(cx.waker()); @@ -564,7 +578,7 @@ impl<'d, T: Instance> I2S<'d, T> { }) .await; - device.set_tx_buffer(buffer)?; + device.update_tx(buffer_ptr)?; compiler_fence(Ordering::SeqCst); drop.defuse(); @@ -572,17 +586,33 @@ impl<'d, T: Instance> I2S<'d, T> { Ok(()) } - async fn receive(buffer: B) -> Result<(), Error> + async fn receive_from_ram(buffer_ptr: *mut [S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { - trace!("RECEIVE: {}", buffer.bytes_ptr() as u32); + trace!("RECEIVE: {}", buffer_ptr as *const S as u32); - let device = Device::::new(); - let drop = device.on_rx_drop(); + // NOTE: RAM slice check for rx is not necessary, as a mutable + // slice can only be built from data located in RAM. compiler_fence(Ordering::SeqCst); + let device = Device::::new(); + + let drop = OnDrop::new(move || { + trace!("RX DROP: Stopping"); + + let device = Device::::new(); + device.disable_rx_ptr_interrupt(); + device.reset_rx_ptr_event(); + device.disable_rx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_rx_ptr_updated() {} + + trace!("RX DROP: Stopped"); + }); + poll_fn(|cx| { T::state().rx_waker.register(cx.waker()); @@ -598,9 +628,10 @@ impl<'d, T: Instance> I2S<'d, T> { }) .await; - device.set_rx_buffer(buffer)?; + device.update_rx(buffer_ptr)?; compiler_fence(Ordering::SeqCst); + drop.defuse(); Ok(()) @@ -608,15 +639,15 @@ impl<'d, T: Instance> I2S<'d, T> { } /// I2S output -pub struct Output<'d, T: Instance> { +pub struct OutputStream<'d, T: Instance> { _p: PeripheralRef<'d, T>, } -impl<'d, T: Instance> Output<'d, T> { +impl<'d, T: Instance> OutputStream<'d, T> { /// Prepare the initial buffer and start the I2S transfer. - pub async fn start(&self, buffer: B) -> Result<(), Error> + pub async fn start(&self, buffer: &[S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { let device = Device::::new(); @@ -627,7 +658,8 @@ impl<'d, T: Instance> Output<'d, T> { device.enable(); device.enable_tx(); - device.set_tx_buffer(buffer)?; + + device.update_tx(buffer as *const [S])?; s.started.store(true, Ordering::Relaxed); @@ -647,24 +679,24 @@ impl<'d, T: Instance> Output<'d, T> { /// The buffer must not be written while being used by the DMA, /// which takes two other `send`s being awaited. #[allow(unused_mut)] - pub async fn send(&mut self, buffer: B) -> Result<(), Error> + pub async fn send_from_ram(&mut self, buffer: &[S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { - I2S::::send(buffer).await + I2S::::send_from_ram(buffer as *const [S]).await } } /// I2S input -pub struct Input<'d, T: Instance> { +pub struct InputStream<'d, T: Instance> { _p: PeripheralRef<'d, T>, } -impl<'d, T: Instance> Input<'d, T> { +impl<'d, T: Instance> InputStream<'d, T> { /// Prepare the initial buffer and start the I2S transfer. - pub async fn start(&self, buffer: B) -> Result<(), Error> + pub async fn start(&self, buffer: &mut [S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { let device = Device::::new(); @@ -675,7 +707,8 @@ impl<'d, T: Instance> Input<'d, T> { device.enable(); device.enable_rx(); - device.set_rx_buffer(buffer)?; + + device.update_rx(buffer as *mut [S])?; s.started.store(true, Ordering::Relaxed); @@ -695,24 +728,24 @@ impl<'d, T: Instance> Input<'d, T> { /// The buffer must not be read while being used by the DMA, /// which takes two other `receive`s being awaited. #[allow(unused_mut)] - pub async fn receive(&mut self, buffer: B) -> Result<(), Error> + pub async fn receive_from_ram(&mut self, buffer: &mut [S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { - I2S::::receive(buffer).await + I2S::::receive_from_ram(buffer as *mut [S]).await } } -/// I2S ful duplex (input & output) -pub struct FullDuplex<'d, T: Instance> { +/// I2S full duplex stream (input & output) +pub struct FullDuplexStream<'d, T: Instance> { _p: PeripheralRef<'d, T>, } -impl<'d, T: Instance> FullDuplex<'d, T> { +impl<'d, T: Instance> FullDuplexStream<'d, T> { /// Prepare the initial buffers and start the I2S transfer. - pub async fn start(&self, buffer_out: B, buffer_in: B) -> Result<(), Error> + pub async fn start(&self, buffer_out: &[S], buffer_in: &mut [S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { let device = Device::::new(); @@ -724,8 +757,9 @@ impl<'d, T: Instance> FullDuplex<'d, T> { device.enable(); device.enable_tx(); device.enable_rx(); - device.set_tx_buffer(buffer_out)?; - device.set_rx_buffer(buffer_in)?; + + device.update_tx(buffer_out as *const [S])?; + device.update_rx(buffer_in as *mut [S])?; s.started.store(true, Ordering::Relaxed); @@ -745,12 +779,12 @@ impl<'d, T: Instance> FullDuplex<'d, T> { /// The buffers must not be written/read while being used by the DMA, /// which takes two other `send_and_receive` operations being awaited. #[allow(unused_mut)] - pub async fn send_and_receive(&mut self, buffer_out: B, buffer_in: B) -> Result<(), Error> + pub async fn send_and_receive_from_ram(&mut self, buffer_out: &[S], buffer_in: &mut [S]) -> Result<(), Error> where - B: Buffer, + S: Sample, { - I2S::::send(buffer_out).await?; - I2S::::receive(buffer_in).await?; + I2S::::send_from_ram(buffer_out as *const [S]).await?; + I2S::::receive_from_ram(buffer_in as *mut [S]).await?; Ok(()) } } @@ -833,38 +867,6 @@ impl Device { self.0.intenset.write(|w| w.stopped().set()); } - #[inline] - fn set_tx_buffer(&self, buffer: B) -> Result<(), Error> - where - B: Buffer, - { - let (ptr, maxcnt) = Self::validate_buffer(buffer)?; - self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); - self.0.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); - Ok(()) - } - - #[inline] - fn set_rx_buffer(&self, buffer: B) -> Result<(), Error> - where - B: Buffer, - { - let (ptr, maxcnt) = Self::validate_buffer(buffer)?; - self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); - self.0.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); - Ok(()) - } - - #[inline(always)] - fn is_tx_ptr_updated(&self) -> bool { - self.0.events_txptrupd.read().bits() != 0 - } - - #[inline(always)] - fn is_rx_ptr_updated(&self) -> bool { - self.0.events_rxptrupd.read().bits() != 0 - } - #[inline(always)] fn reset_tx_ptr_event(&self) { trace!("TX PTR EVENT: Reset"); @@ -901,58 +903,44 @@ impl Device { self.0.intenclr.write(|w| w.rxptrupd().clear()); } - #[inline] - fn on_tx_drop(&self) -> OnDrop { - OnDrop::new(move || { - trace!("TX DROP: Stopping"); + #[inline(always)] + fn is_tx_ptr_updated(&self) -> bool { + self.0.events_txptrupd.read().bits() != 0 + } - let device = Device::::new(); - device.disable_tx_ptr_interrupt(); - device.reset_tx_ptr_event(); - device.disable_tx(); - - // TX is stopped almost instantly, spinning is fine. - while !device.is_tx_ptr_updated() {} - - trace!("TX DROP: Stopped"); - }) + #[inline(always)] + fn is_rx_ptr_updated(&self) -> bool { + self.0.events_rxptrupd.read().bits() != 0 } #[inline] - fn on_rx_drop(&self) -> OnDrop { - OnDrop::new(move || { - trace!("RX DROP: Stopping"); - - let device = Device::::new(); - device.disable_rx_ptr_interrupt(); - device.reset_rx_ptr_event(); - device.disable_rx(); - - // TX is stopped almost instantly, spinning is fine. - while !device.is_rx_ptr_updated() {} - - trace!("RX DROP: Stopped"); - }) + fn update_tx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + Ok(()) } - fn validate_buffer(buffer: B) -> Result<(u32, u32), Error> - where - B: Buffer, - { - let ptr = buffer.bytes_ptr() as u32; - let len = buffer.bytes_len(); - let maxcnt = ((len + core::mem::size_of::() - 1) / core::mem::size_of::()) as u32; + #[inline] + fn update_rx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + Ok(()) + } + + fn validated_dma_parts(buffer_ptr: *const [S]) -> Result<(u32, u32), Error> { + let (ptr, len) = slice_ptr_parts(buffer_ptr); + let ptr = ptr as u32; + let bytes_len = len * size_of::(); + let maxcnt = (bytes_len / size_of::()) as u32; trace!("PTR={}, MAXCNT={}", ptr, maxcnt); - // TODO can we avoid repeating all those runtime checks for the same buffer again and again? - if ptr % 4 != 0 { Err(Error::BufferMisaligned) - } else if len % 4 != 0 { + } else if bytes_len % 4 != 0 { Err(Error::BufferLengthMisaligned) - } else if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { - Err(Error::BufferNotInDataMemory) } else if maxcnt as usize > EASY_DMA_SIZE { Err(Error::BufferTooLong) } else { @@ -998,60 +986,19 @@ impl Default for AlignedBuffer { } } -impl AsRef<[T]> for AlignedBuffer { - fn as_ref(&self) -> &[T] { +impl Deref for AlignedBuffer { + type Target = [T]; + fn deref(&self) -> &Self::Target { self.0.as_slice() } } -impl AsMut<[T]> for AlignedBuffer { - fn as_mut(&mut self) -> &mut [T] { +impl DerefMut for AlignedBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { self.0.as_mut_slice() } } -/// Common operations required for a buffer to be used by the DMA -pub trait Buffer: Sized { - fn bytes_ptr(&self) -> *const u8; - fn bytes_len(&self) -> usize; -} - -impl Buffer for &[i8] { - #[inline] - fn bytes_ptr(&self) -> *const u8 { - self.as_ptr() as *const u8 - } - - #[inline] - fn bytes_len(&self) -> usize { - self.len() - } -} - -impl Buffer for &[i16] { - #[inline] - fn bytes_ptr(&self) -> *const u8 { - self.as_ptr() as *const u8 - } - - #[inline] - fn bytes_len(&self) -> usize { - self.len() * core::mem::size_of::() - } -} - -impl Buffer for &[i32] { - #[inline] - fn bytes_ptr(&self) -> *const u8 { - self.as_ptr() as *const u8 - } - - #[inline] - fn bytes_len(&self) -> usize { - self.len() * core::mem::size_of::() - } -} - pub(crate) mod sealed { use core::sync::atomic::AtomicBool; diff --git a/examples/nrf/src/bin/i2s-generate.rs b/examples/nrf/src/bin/i2s_waveform.rs similarity index 50% rename from examples/nrf/src/bin/i2s-generate.rs rename to examples/nrf/src/bin/i2s_waveform.rs index c2b5578f3..81858ff59 100644 --- a/examples/nrf/src/bin/i2s-generate.rs +++ b/examples/nrf/src/bin/i2s_waveform.rs @@ -6,33 +6,29 @@ use core::f32::consts::PI; use defmt::{error, info}; use embassy_executor::Spawner; -use embassy_nrf::i2s::{self, Sample as _}; +use embassy_nrf::i2s::{self, Channels, Config, MasterClock, Sample as _, SampleWidth, I2S}; use embassy_nrf::interrupt; use {defmt_rtt as _, panic_probe as _}; +type Sample = i16; + +const NUM_SAMPLES: usize = 6000; + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let mut config = i2s::Config::default(); - config.mode = i2s::ExactSampleRate::_50000.into(); - config.channels = i2s::Channels::Left; - config.swidth = i2s::SampleWidth::_16bit; - let sample_rate = config.mode.sample_rate().expect("I2S Master"); - let inv_sample_rate = 1.0 / sample_rate as f32; + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + let sample_rate = master_clock.sample_rate(); info!("Sample rate: {}", sample_rate); - // Wait for a button press - // use embassy_nrf::gpio::{Input, Pin, Pull}; - // let mut btn1 = Input::new(p.P1_00.degrade(), Pull::Up); - // btn1.wait_for_low().await; + let config = Config::default() + .sample_width(SampleWidth::_16bit) + .channels(Channels::MonoLeft); let irq = interrupt::take!(I2S); - let mut i2s = i2s::I2S::new(p.I2S, irq, p.P0_28, p.P0_29, p.P0_31, p.P0_27, p.P0_30, config).output(); - - type Sample = i16; - const NUM_SAMPLES: usize = 6000; + let mut output_stream = I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).output(p.P0_28); let mut buffers: [i2s::AlignedBuffer; 3] = [ i2s::AlignedBuffer::default(), @@ -40,36 +36,16 @@ async fn main(_spawner: Spawner) { i2s::AlignedBuffer::default(), ]; - let mut carrier = SineOsc::new(); + let mut waveform = Waveform::new(1.0 / sample_rate as f32); - let mut freq_mod = SineOsc::new(); - freq_mod.set_frequency(8.0, inv_sample_rate); - freq_mod.set_amplitude(1.0); + waveform.process(&mut buffers[0]); + waveform.process(&mut buffers[1]); - let mut amp_mod = SineOsc::new(); - amp_mod.set_frequency(16.0, inv_sample_rate); - amp_mod.set_amplitude(0.5); - - let mut generate = |buf: &mut [Sample]| { - for sample in &mut buf.chunks_mut(1) { - let freq_modulation = bipolar_to_unipolar(freq_mod.generate()); - carrier.set_frequency(220.0 + 440.0 * freq_modulation, inv_sample_rate); - let amp_modulation = bipolar_to_unipolar(amp_mod.generate()); - carrier.set_amplitude(amp_modulation); - let signal = carrier.generate(); - let value = (Sample::SCALE as f32 * signal) as Sample; - sample[0] = value; - } - }; - - generate(buffers[0].as_mut()); - generate(buffers[1].as_mut()); - - i2s.start(buffers[0].as_ref()).await.expect("I2S Start"); + output_stream.start(&buffers[0]).await.expect("I2S Start"); let mut index = 1; loop { - if let Err(err) = i2s.send(buffers[index].as_ref()).await { + if let Err(err) = output_stream.send_from_ram(&buffers[index]).await { error!("{}", err); } @@ -77,11 +53,54 @@ async fn main(_spawner: Spawner) { if index >= 3 { index = 0; } - generate(buffers[index].as_mut()); + + waveform.process(&mut buffers[index]); + } +} + +struct Waveform { + inv_sample_rate: f32, + carrier: SineOsc, + freq_mod: SineOsc, + amp_mod: SineOsc, +} + +impl Waveform { + fn new(inv_sample_rate: f32) -> Self { + let carrier = SineOsc::new(); + + let mut freq_mod = SineOsc::new(); + freq_mod.set_frequency(8.0, inv_sample_rate); + freq_mod.set_amplitude(1.0); + + let mut amp_mod = SineOsc::new(); + amp_mod.set_frequency(16.0, inv_sample_rate); + amp_mod.set_amplitude(0.5); + + Self { + inv_sample_rate, + carrier, + freq_mod, + amp_mod, + } + } + + fn process(&mut self, buf: &mut [Sample]) { + for sample in buf.chunks_mut(1) { + let freq_modulation = bipolar_to_unipolar(self.freq_mod.generate()); + self.carrier + .set_frequency(110.0 + 440.0 * freq_modulation, self.inv_sample_rate); + + let amp_modulation = bipolar_to_unipolar(self.amp_mod.generate()); + self.carrier.set_amplitude(amp_modulation); + + let signal = self.carrier.generate(); + + sample[0] = (Sample::SCALE as f32 * signal) as Sample; + } } } -#[derive(Clone)] struct SineOsc { amplitude: f32, modulo: f32, From 6b8ab32536bf2e831ec424b5aaf489bb1f53d017 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Sat, 26 Nov 2022 15:22:31 +0100 Subject: [PATCH 18/20] Use &mut self for start methods --- embassy-nrf/src/i2s.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index d5815160a..bc90dbc98 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -645,7 +645,8 @@ pub struct OutputStream<'d, T: Instance> { impl<'d, T: Instance> OutputStream<'d, T> { /// Prepare the initial buffer and start the I2S transfer. - pub async fn start(&self, buffer: &[S]) -> Result<(), Error> + #[allow(unused_mut)] + pub async fn start(&mut self, buffer: &[S]) -> Result<(), Error> where S: Sample, { @@ -694,7 +695,8 @@ pub struct InputStream<'d, T: Instance> { impl<'d, T: Instance> InputStream<'d, T> { /// Prepare the initial buffer and start the I2S transfer. - pub async fn start(&self, buffer: &mut [S]) -> Result<(), Error> + #[allow(unused_mut)] + pub async fn start(&mut self, buffer: &mut [S]) -> Result<(), Error> where S: Sample, { @@ -743,7 +745,8 @@ pub struct FullDuplexStream<'d, T: Instance> { impl<'d, T: Instance> FullDuplexStream<'d, T> { /// Prepare the initial buffers and start the I2S transfer. - pub async fn start(&self, buffer_out: &[S], buffer_in: &mut [S]) -> Result<(), Error> + #[allow(unused_mut)] + pub async fn start(&mut self, buffer_out: &[S], buffer_in: &mut [S]) -> Result<(), Error> where S: Sample, { From 199504be564b231154e07c58bcc52b11afdc9fe7 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Tue, 29 Nov 2022 01:09:47 +0100 Subject: [PATCH 19/20] Optimization to be able to work with only 2 buffers --- embassy-nrf/src/i2s.rs | 48 ++++++++++++++++++++-------- examples/nrf/src/bin/i2s_waveform.rs | 20 ++++++------ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index bc90dbc98..08d4093f2 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -398,6 +398,7 @@ impl<'d, T: Instance> I2S<'d, T> { ) -> FullDuplexStream<'d, T> { self.sdout = Some(sdout.into_ref().map_into()); self.sdin = Some(sdin.into_ref().map_into()); + FullDuplexStream { _p: self.build() } } @@ -549,6 +550,16 @@ impl<'d, T: Instance> I2S<'d, T> { let device = Device::::new(); + device.update_tx(buffer_ptr)?; + + Self::wait_tx_ptr_update().await; + + compiler_fence(Ordering::SeqCst); + + Ok(()) + } + + async fn wait_tx_ptr_update() { let drop = OnDrop::new(move || { trace!("TX DROP: Stopping"); @@ -566,6 +577,7 @@ impl<'d, T: Instance> I2S<'d, T> { poll_fn(|cx| { T::state().tx_waker.register(cx.waker()); + let device = Device::::new(); if device.is_tx_ptr_updated() { trace!("TX POLL: Ready"); device.reset_tx_ptr_event(); @@ -578,12 +590,7 @@ impl<'d, T: Instance> I2S<'d, T> { }) .await; - device.update_tx(buffer_ptr)?; - - compiler_fence(Ordering::SeqCst); drop.defuse(); - - Ok(()) } async fn receive_from_ram(buffer_ptr: *mut [S]) -> Result<(), Error> @@ -599,6 +606,16 @@ impl<'d, T: Instance> I2S<'d, T> { let device = Device::::new(); + device.update_rx(buffer_ptr)?; + + Self::wait_rx_ptr_update().await; + + compiler_fence(Ordering::SeqCst); + + Ok(()) + } + + async fn wait_rx_ptr_update() { let drop = OnDrop::new(move || { trace!("RX DROP: Stopping"); @@ -616,6 +633,7 @@ impl<'d, T: Instance> I2S<'d, T> { poll_fn(|cx| { T::state().rx_waker.register(cx.waker()); + let device = Device::::new(); if device.is_rx_ptr_updated() { trace!("RX POLL: Ready"); device.reset_rx_ptr_event(); @@ -628,13 +646,7 @@ impl<'d, T: Instance> I2S<'d, T> { }) .await; - device.update_rx(buffer_ptr)?; - - compiler_fence(Ordering::SeqCst); - drop.defuse(); - - Ok(()) } } @@ -666,6 +678,8 @@ impl<'d, T: Instance> OutputStream<'d, T> { device.start(); + I2S::::wait_tx_ptr_update().await; + Ok(()) } @@ -716,6 +730,8 @@ impl<'d, T: Instance> InputStream<'d, T> { device.start(); + I2S::::wait_rx_ptr_update().await; + Ok(()) } @@ -746,7 +762,7 @@ pub struct FullDuplexStream<'d, T: Instance> { impl<'d, T: Instance> FullDuplexStream<'d, T> { /// Prepare the initial buffers and start the I2S transfer. #[allow(unused_mut)] - pub async fn start(&mut self, buffer_out: &[S], buffer_in: &mut [S]) -> Result<(), Error> + pub async fn start(&mut self, buffer_in: &mut [S], buffer_out: &[S]) -> Result<(), Error> where S: Sample, { @@ -768,6 +784,9 @@ impl<'d, T: Instance> FullDuplexStream<'d, T> { device.start(); + I2S::::wait_tx_ptr_update().await; + I2S::::wait_rx_ptr_update().await; + Ok(()) } @@ -782,7 +801,7 @@ impl<'d, T: Instance> FullDuplexStream<'d, T> { /// The buffers must not be written/read while being used by the DMA, /// which takes two other `send_and_receive` operations being awaited. #[allow(unused_mut)] - pub async fn send_and_receive_from_ram(&mut self, buffer_out: &[S], buffer_in: &mut [S]) -> Result<(), Error> + pub async fn send_and_receive_from_ram(&mut self, buffer_in: &mut [S], buffer_out: &[S]) -> Result<(), Error> where S: Sample, { @@ -903,7 +922,7 @@ impl Device { #[inline(always)] fn enable_rx_ptr_interrupt(&self) { trace!("RX PTR INTERRUPT: Enabled"); - self.0.intenclr.write(|w| w.rxptrupd().clear()); + self.0.intenset.write(|w| w.rxptrupd().set()); } #[inline(always)] @@ -974,6 +993,7 @@ impl Sample for i32 { } /// A 4-bytes aligned [Buffer]. +#[derive(Clone, Copy)] #[repr(align(4))] pub struct AlignedBuffer([T; N]); diff --git a/examples/nrf/src/bin/i2s_waveform.rs b/examples/nrf/src/bin/i2s_waveform.rs index 81858ff59..13b1300ea 100644 --- a/examples/nrf/src/bin/i2s_waveform.rs +++ b/examples/nrf/src/bin/i2s_waveform.rs @@ -12,7 +12,8 @@ use {defmt_rtt as _, panic_probe as _}; type Sample = i16; -const NUM_SAMPLES: usize = 6000; +const NUM_BUFFERS: usize = 2; +const NUM_SAMPLES: usize = 50; #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -30,31 +31,27 @@ async fn main(_spawner: Spawner) { let irq = interrupt::take!(I2S); let mut output_stream = I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).output(p.P0_28); - let mut buffers: [i2s::AlignedBuffer; 3] = [ - i2s::AlignedBuffer::default(), - i2s::AlignedBuffer::default(), - i2s::AlignedBuffer::default(), - ]; + let mut buffers: [i2s::AlignedBuffer; NUM_BUFFERS] = + [i2s::AlignedBuffer::default(); NUM_BUFFERS]; let mut waveform = Waveform::new(1.0 / sample_rate as f32); waveform.process(&mut buffers[0]); - waveform.process(&mut buffers[1]); output_stream.start(&buffers[0]).await.expect("I2S Start"); let mut index = 1; loop { + waveform.process(&mut buffers[index]); + if let Err(err) = output_stream.send_from_ram(&buffers[index]).await { error!("{}", err); } index += 1; - if index >= 3 { + if index >= NUM_BUFFERS { index = 0; } - - waveform.process(&mut buffers[index]); } } @@ -67,7 +64,8 @@ struct Waveform { impl Waveform { fn new(inv_sample_rate: f32) -> Self { - let carrier = SineOsc::new(); + let mut carrier = SineOsc::new(); + carrier.set_frequency(110.0, inv_sample_rate); let mut freq_mod = SineOsc::new(); freq_mod.set_frequency(8.0, inv_sample_rate); From 5fdd521a767fd8825a2d55d6b833fd99627353d7 Mon Sep 17 00:00:00 2001 From: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Date: Thu, 8 Dec 2022 20:22:50 +0100 Subject: [PATCH 20/20] Move the responsibility to manage buffers to the I2S stream --- embassy-nrf/src/i2s.rs | 157 +++++++++++++++++++-------- examples/nrf/src/bin/i2s_effect.rs | 117 ++++++++++++++++++++ examples/nrf/src/bin/i2s_monitor.rs | 115 ++++++++++++++++++++ examples/nrf/src/bin/i2s_waveform.rs | 26 ++--- 4 files changed, 353 insertions(+), 62 deletions(-) create mode 100644 examples/nrf/src/bin/i2s_effect.rs create mode 100644 examples/nrf/src/bin/i2s_monitor.rs diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 08d4093f2..7e9507751 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -19,6 +19,8 @@ use crate::pac::i2s::RegisterBlock; use crate::util::{slice_in_ram_or, slice_ptr_parts}; use crate::{Peripheral, EASY_DMA_SIZE}; +pub type DoubleBuffering = MultiBuffering; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -379,27 +381,47 @@ impl<'d, T: Instance> I2S<'d, T> { } /// I2S output only - pub fn output(mut self, sdout: impl Peripheral

+ 'd) -> OutputStream<'d, T> { + pub fn output( + mut self, + sdout: impl Peripheral

+ 'd, + buffers: MultiBuffering, + ) -> OutputStream<'d, T, S, NB, NS> { self.sdout = Some(sdout.into_ref().map_into()); - OutputStream { _p: self.build() } + OutputStream { + _p: self.build(), + buffers, + } } /// I2S input only - pub fn input(mut self, sdin: impl Peripheral

+ 'd) -> InputStream<'d, T> { + pub fn input( + mut self, + sdin: impl Peripheral

+ 'd, + buffers: MultiBuffering, + ) -> InputStream<'d, T, S, NB, NS> { self.sdin = Some(sdin.into_ref().map_into()); - InputStream { _p: self.build() } + InputStream { + _p: self.build(), + buffers, + } } /// I2S full duplex (input and output) - pub fn full_duplex( + pub fn full_duplex( mut self, sdin: impl Peripheral

+ 'd, sdout: impl Peripheral

+ 'd, - ) -> FullDuplexStream<'d, T> { + buffers_out: MultiBuffering, + buffers_in: MultiBuffering, + ) -> FullDuplexStream<'d, T, S, NB, NS> { self.sdout = Some(sdout.into_ref().map_into()); self.sdin = Some(sdin.into_ref().map_into()); - FullDuplexStream { _p: self.build() } + FullDuplexStream { + _p: self.build(), + buffers_out, + buffers_in, + } } fn build(self) -> PeripheralRef<'d, T> { @@ -651,14 +673,19 @@ impl<'d, T: Instance> I2S<'d, T> { } /// I2S output -pub struct OutputStream<'d, T: Instance> { +pub struct OutputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, } -impl<'d, T: Instance> OutputStream<'d, T> { +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> OutputStream<'d, T, S, NB, NS> { + /// Get a mutable reference to the current buffer. + pub fn buffer(&mut self) -> &mut [S] { + self.buffers.get_mut() + } + /// Prepare the initial buffer and start the I2S transfer. - #[allow(unused_mut)] - pub async fn start(&mut self, buffer: &[S]) -> Result<(), Error> + pub async fn start(&mut self) -> Result<(), Error> where S: Sample, { @@ -672,7 +699,7 @@ impl<'d, T: Instance> OutputStream<'d, T> { device.enable(); device.enable_tx(); - device.update_tx(buffer as *const [S])?; + device.update_tx(self.buffers.switch())?; s.started.store(true, Ordering::Relaxed); @@ -689,28 +716,30 @@ impl<'d, T: Instance> OutputStream<'d, T> { I2S::::stop().await } - /// Sets the given `buffer` for transmission in the DMA. - /// Buffer address must be 4 byte aligned and located in RAM. - /// The buffer must not be written while being used by the DMA, - /// which takes two other `send`s being awaited. - #[allow(unused_mut)] - pub async fn send_from_ram(&mut self, buffer: &[S]) -> Result<(), Error> + /// Sends the current buffer for transmission in the DMA. + /// Switches to use the next available buffer. + pub async fn send(&mut self) -> Result<(), Error> where S: Sample, { - I2S::::send_from_ram(buffer as *const [S]).await + I2S::::send_from_ram(self.buffers.switch()).await } } /// I2S input -pub struct InputStream<'d, T: Instance> { +pub struct InputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, } -impl<'d, T: Instance> InputStream<'d, T> { +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> InputStream<'d, T, S, NB, NS> { + /// Get a mutable reference to the current buffer. + pub fn buffer(&mut self) -> &mut [S] { + self.buffers.get_mut() + } + /// Prepare the initial buffer and start the I2S transfer. - #[allow(unused_mut)] - pub async fn start(&mut self, buffer: &mut [S]) -> Result<(), Error> + pub async fn start(&mut self) -> Result<(), Error> where S: Sample, { @@ -724,7 +753,7 @@ impl<'d, T: Instance> InputStream<'d, T> { device.enable(); device.enable_rx(); - device.update_rx(buffer as *mut [S])?; + device.update_rx(self.buffers.switch())?; s.started.store(true, Ordering::Relaxed); @@ -741,28 +770,32 @@ impl<'d, T: Instance> InputStream<'d, T> { I2S::::stop().await } - /// Sets the given `buffer` for reception from the DMA. - /// Buffer address must be 4 byte aligned and located in RAM. - /// The buffer must not be read while being used by the DMA, - /// which takes two other `receive`s being awaited. + /// Sets the current buffer for reception from the DMA. + /// Switches to use the next available buffer. #[allow(unused_mut)] - pub async fn receive_from_ram(&mut self, buffer: &mut [S]) -> Result<(), Error> + pub async fn receive(&mut self) -> Result<(), Error> where S: Sample, { - I2S::::receive_from_ram(buffer as *mut [S]).await + I2S::::receive_from_ram(self.buffers.switch_mut()).await } } /// I2S full duplex stream (input & output) -pub struct FullDuplexStream<'d, T: Instance> { +pub struct FullDuplexStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { _p: PeripheralRef<'d, T>, + buffers_out: MultiBuffering, + buffers_in: MultiBuffering, } -impl<'d, T: Instance> FullDuplexStream<'d, T> { +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> FullDuplexStream<'d, T, S, NB, NS> { + /// Get the current output and input buffers. + pub fn buffers(&mut self) -> (&mut [S], &[S]) { + (self.buffers_out.get_mut(), self.buffers_in.get()) + } + /// Prepare the initial buffers and start the I2S transfer. - #[allow(unused_mut)] - pub async fn start(&mut self, buffer_in: &mut [S], buffer_out: &[S]) -> Result<(), Error> + pub async fn start(&mut self) -> Result<(), Error> where S: Sample, { @@ -777,8 +810,8 @@ impl<'d, T: Instance> FullDuplexStream<'d, T> { device.enable_tx(); device.enable_rx(); - device.update_tx(buffer_out as *const [S])?; - device.update_rx(buffer_in as *mut [S])?; + device.update_tx(self.buffers_out.switch())?; + device.update_rx(self.buffers_in.switch_mut())?; s.started.store(true, Ordering::Relaxed); @@ -796,17 +829,14 @@ impl<'d, T: Instance> FullDuplexStream<'d, T> { I2S::::stop().await } - /// Sets the given `buffer_out` and `buffer_in` for transmission/reception from the DMA. - /// Buffer address must be 4 byte aligned and located in RAM. - /// The buffers must not be written/read while being used by the DMA, - /// which takes two other `send_and_receive` operations being awaited. - #[allow(unused_mut)] - pub async fn send_and_receive_from_ram(&mut self, buffer_in: &mut [S], buffer_out: &[S]) -> Result<(), Error> + /// Sets the current buffers for output and input for transmission/reception from the DMA. + /// Switch to use the next available buffers for output/input. + pub async fn send_and_receive(&mut self) -> Result<(), Error> where S: Sample, { - I2S::::send_from_ram(buffer_out as *const [S]).await?; - I2S::::receive_from_ram(buffer_in as *mut [S]).await?; + I2S::::send_from_ram(self.buffers_out.switch()).await?; + I2S::::receive_from_ram(self.buffers_in.switch_mut()).await?; Ok(()) } } @@ -992,7 +1022,7 @@ impl Sample for i32 { const SCALE: Self = 1 << (Self::WIDTH - 1); } -/// A 4-bytes aligned [Buffer]. +/// A 4-bytes aligned buffer. #[derive(Clone, Copy)] #[repr(align(4))] pub struct AlignedBuffer([T; N]); @@ -1022,6 +1052,43 @@ impl DerefMut for AlignedBuffer { } } +pub struct MultiBuffering { + buffers: [AlignedBuffer; NB], + index: usize, +} + +impl MultiBuffering { + pub fn new() -> Self { + assert!(NB > 1); + Self { + buffers: [AlignedBuffer::::default(); NB], + index: 0, + } + } + + fn get(&self) -> &[S] { + &self.buffers[self.index] + } + + fn get_mut(&mut self) -> &mut [S] { + &mut self.buffers[self.index] + } + + /// Advance to use the next buffer and return a non mutable pointer to the previous one. + fn switch(&mut self) -> *const [S] { + let prev_index = self.index; + self.index = (self.index + 1) % NB; + self.buffers[prev_index].deref() as *const [S] + } + + /// Advance to use the next buffer and return a mutable pointer to the previous one. + fn switch_mut(&mut self) -> *mut [S] { + let prev_index = self.index; + self.index = (self.index + 1) % NB; + self.buffers[prev_index].deref_mut() as *mut [S] + } +} + pub(crate) mod sealed { use core::sync::atomic::AtomicBool; diff --git a/examples/nrf/src/bin/i2s_effect.rs b/examples/nrf/src/bin/i2s_effect.rs new file mode 100644 index 000000000..3cca005b1 --- /dev/null +++ b/examples/nrf/src/bin/i2s_effect.rs @@ -0,0 +1,117 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::f32::consts::PI; + +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, MasterClock, MultiBuffering, Sample as _, SampleWidth, I2S}; +use embassy_nrf::interrupt; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_BUFFERS: usize = 2; +const NUM_SAMPLES: usize = 4; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let config = Config::default() + .sample_width(SampleWidth::_16bit) + .channels(Channels::MonoLeft); + + let irq = interrupt::take!(I2S); + let buffers_out = MultiBuffering::::new(); + let buffers_in = MultiBuffering::::new(); + let mut full_duplex_stream = I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).full_duplex( + p.P0_29, + p.P0_28, + buffers_out, + buffers_in, + ); + + let mut modulator = SineOsc::new(); + modulator.set_frequency(8.0, 1.0 / sample_rate as f32); + modulator.set_amplitude(1.0); + + full_duplex_stream.start().await.expect("I2S Start"); + + loop { + let (buff_out, buff_in) = full_duplex_stream.buffers(); + for i in 0..NUM_SAMPLES { + let modulation = (Sample::SCALE as f32 * bipolar_to_unipolar(modulator.generate())) as Sample; + buff_out[i] = buff_in[i] * modulation; + } + + if let Err(err) = full_duplex_stream.send_and_receive().await { + error!("{}", err); + } + } +} + +struct SineOsc { + amplitude: f32, + modulo: f32, + phase_inc: f32, +} + +impl SineOsc { + const B: f32 = 4.0 / PI; + const C: f32 = -4.0 / (PI * PI); + const P: f32 = 0.225; + + pub fn new() -> Self { + Self { + amplitude: 1.0, + modulo: 0.0, + phase_inc: 0.0, + } + } + + pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { + self.phase_inc = freq * inv_sample_rate; + } + + pub fn set_amplitude(&mut self, amplitude: f32) { + self.amplitude = amplitude; + } + + pub fn generate(&mut self) -> f32 { + let signal = self.parabolic_sin(self.modulo); + self.modulo += self.phase_inc; + if self.modulo < 0.0 { + self.modulo += 1.0; + } else if self.modulo > 1.0 { + self.modulo -= 1.0; + } + signal * self.amplitude + } + + fn parabolic_sin(&mut self, modulo: f32) -> f32 { + let angle = PI - modulo * 2.0 * PI; + let y = Self::B * angle + Self::C * angle * abs(angle); + Self::P * (y * abs(y) - y) + y + } +} + +#[inline] +fn abs(value: f32) -> f32 { + if value < 0.0 { + -value + } else { + value + } +} + +#[inline] +fn bipolar_to_unipolar(value: f32) -> f32 { + (value + 1.0) / 2.0 +} diff --git a/examples/nrf/src/bin/i2s_monitor.rs b/examples/nrf/src/bin/i2s_monitor.rs new file mode 100644 index 000000000..48eb7d581 --- /dev/null +++ b/examples/nrf/src/bin/i2s_monitor.rs @@ -0,0 +1,115 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{debug, error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; +use embassy_nrf::interrupt; +use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 500; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let config = Config::default() + .sample_width(SampleWidth::_16bit) + .channels(Channels::MonoLeft); + + let irq = interrupt::take!(I2S); + let buffers = DoubleBuffering::::new(); + let mut input_stream = + I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).input(p.P0_29, buffers); + + // Configure the PWM to use the pins corresponding to the RGB leds + let mut pwm = SimplePwm::new_3ch(p.PWM0, p.P0_23, p.P0_22, p.P0_24); + pwm.set_prescaler(Prescaler::Div1); + pwm.set_max_duty(255); + + let mut rms_online = RmsOnline::::default(); + + input_stream.start().await.expect("I2S Start"); + + loop { + let rms = rms_online.process(input_stream.buffer()); + let rgb = rgb_from_rms(rms); + + debug!("RMS: {}, RGB: {:?}", rms, rgb); + for i in 0..3 { + pwm.set_duty(i, rgb[i].into()); + } + + if let Err(err) = input_stream.receive().await { + error!("{}", err); + } + } +} + +/// RMS from 0.0 until 0.75 will give green with a proportional intensity +/// RMS from 0.75 until 0.9 will give a blend between orange and red proportionally to the intensity +/// RMS above 0.9 will give a red with a proportional intensity +fn rgb_from_rms(rms: f32) -> [u8; 3] { + if rms < 0.75 { + let intensity = rms / 0.75; + [0, (intensity * 165.0) as u8, 0] + } else if rms < 0.9 { + let intensity = (rms - 0.75) / 0.15; + [200, 165 - (165.0 * intensity) as u8, 0] + } else { + let intensity = (rms - 0.9) / 0.1; + [200 + (55.0 * intensity) as u8, 0, 0] + } +} + +pub struct RmsOnline { + pub squares: [f32; N], + pub head: usize, +} + +impl Default for RmsOnline { + fn default() -> Self { + RmsOnline { + squares: [0.0; N], + head: 0, + } + } +} + +impl RmsOnline { + pub fn reset(&mut self) { + self.squares = [0.0; N]; + self.head = 0; + } + + pub fn process(&mut self, buf: &[Sample]) -> f32 { + buf.iter() + .for_each(|sample| self.push(*sample as f32 / Sample::SCALE as f32)); + + let sum_of_squares = self.squares.iter().fold(0.0, |acc, v| acc + *v); + Self::approx_sqrt(sum_of_squares / N as f32) + } + + pub fn push(&mut self, signal: f32) { + let square = signal * signal; + self.squares[self.head] = square; + self.head = (self.head + 1) % N; + } + + /// Approximated sqrt taken from [micromath] + /// + /// [micromath]: https://docs.rs/micromath/latest/src/micromath/float/sqrt.rs.html#11-17 + /// + fn approx_sqrt(value: f32) -> f32 { + f32::from_bits((value.to_bits() + 0x3f80_0000) >> 1) + } +} diff --git a/examples/nrf/src/bin/i2s_waveform.rs b/examples/nrf/src/bin/i2s_waveform.rs index 13b1300ea..1b0e8ebc8 100644 --- a/examples/nrf/src/bin/i2s_waveform.rs +++ b/examples/nrf/src/bin/i2s_waveform.rs @@ -6,13 +6,12 @@ use core::f32::consts::PI; use defmt::{error, info}; use embassy_executor::Spawner; -use embassy_nrf::i2s::{self, Channels, Config, MasterClock, Sample as _, SampleWidth, I2S}; +use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; use embassy_nrf::interrupt; use {defmt_rtt as _, panic_probe as _}; type Sample = i16; -const NUM_BUFFERS: usize = 2; const NUM_SAMPLES: usize = 50; #[embassy_executor::main] @@ -29,29 +28,22 @@ async fn main(_spawner: Spawner) { .channels(Channels::MonoLeft); let irq = interrupt::take!(I2S); - let mut output_stream = I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).output(p.P0_28); - - let mut buffers: [i2s::AlignedBuffer; NUM_BUFFERS] = - [i2s::AlignedBuffer::default(); NUM_BUFFERS]; + let buffers = DoubleBuffering::::new(); + let mut output_stream = + I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).output(p.P0_28, buffers); let mut waveform = Waveform::new(1.0 / sample_rate as f32); - waveform.process(&mut buffers[0]); + waveform.process(output_stream.buffer()); - output_stream.start(&buffers[0]).await.expect("I2S Start"); + output_stream.start().await.expect("I2S Start"); - let mut index = 1; loop { - waveform.process(&mut buffers[index]); + waveform.process(output_stream.buffer()); - if let Err(err) = output_stream.send_from_ram(&buffers[index]).await { + if let Err(err) = output_stream.send().await { error!("{}", err); } - - index += 1; - if index >= NUM_BUFFERS { - index = 0; - } } } @@ -68,7 +60,7 @@ impl Waveform { carrier.set_frequency(110.0, inv_sample_rate); let mut freq_mod = SineOsc::new(); - freq_mod.set_frequency(8.0, inv_sample_rate); + freq_mod.set_frequency(1.0, inv_sample_rate); freq_mod.set_amplitude(1.0); let mut amp_mod = SineOsc::new();