Merge #1049
1049: embassy-nrf: Add I2S module r=lulf a=chris-zen This PR adds I2S support for the nrf52 series (`nrf52832`, `nrf52833`, `nrf52840`). We could only test it in a `nrf52840` in master mode for an output stream (see `i2s_waveform` example), using a clone of the [Adafruit I2S Stereo Decoder - UDA1334A](https://learn.adafruit.com/adafruit-i2s-stereo-decoder-uda1334a/overview). We were wondering if this could be a welcome addition to embassy, as we are working on this very informally and don't have much free time for it. <img src="https://user-images.githubusercontent.com/932644/202316127-a8cf90ef-1e1a-4e1d-b796-961b8ad6cef5.png" width="600"> https://user-images.githubusercontent.com/932644/202316609-e53cd912-e463-4e01-839e-0bbdf37020da.mp4 Co-authored-by: `@brainstorm` <brainstorm@nopcode.org> Co-authored-by: Christian Perez Llamas <932644+chris-zen@users.noreply.github.com> Co-authored-by: Roman Valls Guimera <brainstorm@users.noreply.github.com>
This commit is contained in:
commit
58ab829049
8 changed files with 1544 additions and 3 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
1141
embassy-nrf/src/i2s.rs
Normal file
1141
embassy-nrf/src/i2s.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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(
|
||||
|
|
117
examples/nrf/src/bin/i2s_effect.rs
Normal file
117
examples/nrf/src/bin/i2s_effect.rs
Normal file
|
@ -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::<Sample, NUM_BUFFERS, NUM_SAMPLES>::new();
|
||||
let buffers_in = MultiBuffering::<Sample, NUM_BUFFERS, NUM_SAMPLES>::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
|
||||
}
|
115
examples/nrf/src/bin/i2s_monitor.rs
Normal file
115
examples/nrf/src/bin/i2s_monitor.rs
Normal file
|
@ -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::<Sample, NUM_SAMPLES>::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::<NUM_SAMPLES>::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<const N: usize> {
|
||||
pub squares: [f32; N],
|
||||
pub head: usize,
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for RmsOnline<N> {
|
||||
fn default() -> Self {
|
||||
RmsOnline {
|
||||
squares: [0.0; N],
|
||||
head: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> RmsOnline<N> {
|
||||
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)
|
||||
}
|
||||
}
|
151
examples/nrf/src/bin/i2s_waveform.rs
Normal file
151
examples/nrf/src/bin/i2s_waveform.rs
Normal file
|
@ -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::<Sample, NUM_SAMPLES>::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
|
||||
}
|
Loading…
Reference in a new issue