Draft: Initial support for I2S with a working example.
Co-authored-by: @brainstorm <brainstorm@nopcode.org>
This commit is contained in:
parent
059610a8de
commit
cecd77938c
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
403
embassy-nrf/src/i2s.rs
Normal file
403
embassy-nrf/src/i2s.rs
Normal file
@ -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<Ratio> for u8 {
|
||||
fn from(variant: Ratio) -> Self {
|
||||
variant as _
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum SampleWidth {
|
||||
_8bit,
|
||||
_16bit,
|
||||
_24bit,
|
||||
}
|
||||
|
||||
impl From<SampleWidth> 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<Align> 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<Format> 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<Channels> 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<P = T> + 'd,
|
||||
// irq: impl Peripheral<P = T::Interrupt> + 'd,
|
||||
mck: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
sck: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
lrck: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
sdin: impl Peripheral<P = impl GpioPin> + 'd,
|
||||
sdout: impl Peripheral<P = impl GpioPin> + '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<P = T> + 'd,
|
||||
// irq: impl Peripheral<P = T::Interrupt> + '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::<u32>() / core::mem::size_of::<u8>())) 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<P = Self> + 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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"
|
||||
|
48
examples/nrf/src/bin/i2s.rs
Normal file
48
examples/nrf/src/bin/i2s.rs
Normal file
@ -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: ?Sized>(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::<i16>();
|
||||
|
||||
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user