diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 879600adb..152dad4e3 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); @@ -241,6 +244,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; @@ -281,6 +286,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 d406c32c2..a99ca6343 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")] @@ -287,6 +290,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; @@ -327,10 +332,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 f9e271c97..4f7463be2 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")] @@ -292,6 +295,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; @@ -332,7 +337,6 @@ pub mod irqs { declare!(PWM2); declare!(SPIM2_SPIS2_SPI2); declare!(RTC2); - declare!(I2S); declare!(FPU); declare!(USBD); declare!(UARTE1); @@ -340,4 +344,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 new file mode 100644 index 000000000..7e9507751 --- /dev/null +++ b/embassy-nrf/src/i2s.rs @@ -0,0 +1,1141 @@ +#![macro_use] + +//! Support for I2S audio + +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; + +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; +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] +pub enum Error { + BufferTooLong, + BufferZeroLength, + BufferNotInDataMemory, + BufferMisaligned, + BufferLengthMisaligned, +} + +/// I2S configuration. +#[derive(Clone)] +#[non_exhaustive] +pub struct Config { + 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 { + sample_width: SampleWidth::_16bit, + align: Align::Left, + format: Format::I2S, + channels: Channels::Stereo, + } + } +} + +/// I2S Mode +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct MasterClock { + freq: MckFreq, + ratio: Ratio, +} + +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() + } +} + +/// 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: &'static [u32] = &[ + 0x20000000, 0x18000000, 0x16000000, 0x11000000, 0x10000000, 0x0C000000, 0x0B000000, 0x08800000, 0x08400000, + 0x08000000, 0x06000000, 0x04100000, 0x020C0000, + ]; + + const FREQUENCIES: &'static [u32] = &[ + 4000000, 3200000, 2909090, 2133333, 2000000, 1523809, 1391304, 1066666, 1032258, 1000000, 761904, 507936, + 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)] + } +} + +impl From for usize { + fn from(variant: MckFreq) -> Self { + variant as _ + } +} + +/// Master clock frequency ratio +/// +/// Sample Rate = LRCK = MCK / Ratio +/// +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Ratio { + _32x, + _48x, + _64x, + _96x, + _128x, + _192x, + _256x, + _384x, + _512x, +} + +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[usize::from(*self)] + } +} + +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 { + _8bit, + _16bit, + _24bit, +} + +impl From for u8 { + fn from(variant: SampleWidth) -> Self { + variant as _ + } +} + +/// Channel used for the most significant sample value in 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, + } + } +} + +/// Channels +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Channels { + Stereo, + MonoLeft, + MonoRight, +} + +impl From for u8 { + fn from(variant: Channels) -> Self { + variant as _ + } +} + +/// Interface to the I2S peripheral using EasyDMA to offload the transmission and reception workload. +pub struct I2S<'d, T: Instance> { + 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 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, + master_clock: MasterClock, + config: Config, + ) -> Self { + into_ref!(i2s, irq, mck, sck, lrck); + Self { + i2s, + irq, + mck: Some(mck.map_into()), + sck: sck.map_into(), + lrck: lrck.map_into(), + sdin: None, + sdout: None, + master_clock: Some(master_clock), + config, + } + } + + /// Create a new I2S in slave mode + pub fn slave( + i2s: impl Peripheral

+ 'd, + irq: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + 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( + 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(), + buffers, + } + } + + /// I2S input only + 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(), + buffers, + } + } + + /// I2S full duplex (input and output) + pub fn full_duplex( + mut self, + sdin: impl Peripheral

+ 'd, + sdout: impl Peripheral

+ 'd, + 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(), + buffers_out, + buffers_in, + } + } + + 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 &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.to_register_value()) }); + } + None => { + c.mode.write(|w| w.mode().slave()); + } + }; + + 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(self.config.channels.into()) }); + } + + fn select_pins(&self) { + let psel = &T::regs().psel; + + 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(self.sck.psel_bits()) }; + w.connect().connected() + }); + + psel.lrck.write(|w| { + unsafe { w.bits(self.lrck.psel_bits()) }; + w.connect().connected() + }); + + if let Some(sdin) = &self.sdin { + psel.sdin.write(|w| { + unsafe { w.bits(sdin.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(&self) { + self.irq.set_handler(Self::on_interrupt); + self.irq.unpend(); + self.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 ()) { + let device = Device::::new(); + let s = T::state(); + + if device.is_tx_ptr_updated() { + trace!("TX INT"); + s.tx_waker.wake(); + device.disable_tx_ptr_interrupt(); + } + + if device.is_rx_ptr_updated() { + trace!("RX INT"); + s.rx_waker.wake(); + device.disable_rx_ptr_interrupt(); + } + + if device.is_stopped() { + trace!("STOPPED INT"); + s.stop_waker.wake(); + device.disable_stopped_interrupt(); + } + } + + async fn stop() { + compiler_fence(Ordering::SeqCst); + + let device = Device::::new(); + device.stop(); + + 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(); + } + + async fn send_from_ram(buffer_ptr: *const [S]) -> Result<(), Error> + where + S: Sample, + { + trace!("SEND: {}", buffer_ptr as *const S as u32); + + slice_in_ram_or(buffer_ptr, Error::BufferNotInDataMemory)?; + + compiler_fence(Ordering::SeqCst); + + 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"); + + 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()); + + let device = Device::::new(); + if device.is_tx_ptr_updated() { + trace!("TX POLL: Ready"); + device.reset_tx_ptr_event(); + device.enable_tx_ptr_interrupt(); + Poll::Ready(()) + } else { + trace!("TX POLL: Pending"); + Poll::Pending + } + }) + .await; + + drop.defuse(); + } + + async fn receive_from_ram(buffer_ptr: *mut [S]) -> Result<(), Error> + where + S: Sample, + { + trace!("RECEIVE: {}", buffer_ptr as *const S as u32); + + // 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(); + + 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"); + + 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()); + + let device = Device::::new(); + 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; + + drop.defuse(); + } +} + +/// I2S output +pub struct OutputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, +} + +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. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_tx(); + + device.update_tx(self.buffers.switch())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_tx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// 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(self.buffers.switch()).await + } +} + +/// I2S input +pub struct InputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, +} + +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. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_rx(); + + device.update_rx(self.buffers.switch())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_rx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the current buffer for reception from the DMA. + /// Switches to use the next available buffer. + #[allow(unused_mut)] + pub async fn receive(&mut self) -> Result<(), Error> + where + S: Sample, + { + I2S::::receive_from_ram(self.buffers.switch_mut()).await + } +} + +/// I2S full duplex stream (input & output) +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, 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. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + 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.update_tx(self.buffers_out.switch())?; + device.update_rx(self.buffers_in.switch_mut())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_tx_ptr_update().await; + I2S::::wait_rx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// 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(self.buffers_out.switch()).await?; + I2S::::receive_from_ram(self.buffers_in.switch_mut()).await?; + Ok(()) + } +} + +/// Helper encapsulating common I2S device operations. +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(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(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.intenset.write(|w| w.rxptrupd().set()); + } + + #[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] + 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(()) + } + + #[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); + + if ptr % 4 != 0 { + Err(Error::BufferMisaligned) + } else if bytes_len % 4 != 0 { + Err(Error::BufferLengthMisaligned) + } else if maxcnt as usize > EASY_DMA_SIZE { + Err(Error::BufferTooLong) + } else { + Ok((ptr, maxcnt)) + } + } +} + +/// 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. +#[derive(Clone, Copy)] +#[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 Deref for AlignedBuffer { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +impl DerefMut for AlignedBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut_slice() + } +} + +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; + + use embassy_sync::waitqueue::AtomicWaker; + + /// 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(), + } + } + } + + pub trait Instance { + fn regs() -> &'static crate::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 crate::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 9054bc300..4832e143f 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -78,6 +78,8 @@ pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; +#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] +pub mod i2s; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod nvmc; #[cfg(any( 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 new file mode 100644 index 000000000..1b0e8ebc8 --- /dev/null +++ b/examples/nrf/src/bin/i2s_waveform.rs @@ -0,0 +1,151 @@ +#![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, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; +use embassy_nrf::interrupt; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 50; + +#[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 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(output_stream.buffer()); + + output_stream.start().await.expect("I2S Start"); + + loop { + waveform.process(output_stream.buffer()); + + if let Err(err) = output_stream.send().await { + error!("{}", err); + } + } +} + +struct Waveform { + inv_sample_rate: f32, + carrier: SineOsc, + freq_mod: SineOsc, + amp_mod: SineOsc, +} + +impl Waveform { + fn new(inv_sample_rate: f32) -> Self { + let mut carrier = SineOsc::new(); + carrier.set_frequency(110.0, inv_sample_rate); + + let mut freq_mod = SineOsc::new(); + freq_mod.set_frequency(1.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; + } + } +} + +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 +}