diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-12-09 07:49:40 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-12-09 07:49:40 +0000 |
| commit | 58ab82904970f2df3984e54c722955a7b7c81391 (patch) | |
| tree | bb29792ca17a791411182c8e5f3aa6414cefed85 /examples | |
| parent | 1b8c0733e690108994654c43387e438c2f64cfaa (diff) | |
| parent | 5fdd521a767fd8825a2d55d6b833fd99627353d7 (diff) | |
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` <[email protected]>
Co-authored-by: Christian Perez Llamas <[email protected]>
Co-authored-by: Roman Valls Guimera <[email protected]>
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/nrf/src/bin/i2s_effect.rs | 117 | ||||
| -rw-r--r-- | examples/nrf/src/bin/i2s_monitor.rs | 115 | ||||
| -rw-r--r-- | examples/nrf/src/bin/i2s_waveform.rs | 151 |
3 files changed, 383 insertions, 0 deletions
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use core::f32::consts::PI; | ||
| 6 | |||
| 7 | use defmt::{error, info}; | ||
| 8 | use embassy_executor::Spawner; | ||
| 9 | use embassy_nrf::i2s::{self, Channels, Config, MasterClock, MultiBuffering, Sample as _, SampleWidth, I2S}; | ||
| 10 | use embassy_nrf::interrupt; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | type Sample = i16; | ||
| 14 | |||
| 15 | const NUM_BUFFERS: usize = 2; | ||
| 16 | const NUM_SAMPLES: usize = 4; | ||
| 17 | |||
| 18 | #[embassy_executor::main] | ||
| 19 | async fn main(_spawner: Spawner) { | ||
| 20 | let p = embassy_nrf::init(Default::default()); | ||
| 21 | |||
| 22 | let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); | ||
| 23 | |||
| 24 | let sample_rate = master_clock.sample_rate(); | ||
| 25 | info!("Sample rate: {}", sample_rate); | ||
| 26 | |||
| 27 | let config = Config::default() | ||
| 28 | .sample_width(SampleWidth::_16bit) | ||
| 29 | .channels(Channels::MonoLeft); | ||
| 30 | |||
| 31 | let irq = interrupt::take!(I2S); | ||
| 32 | let buffers_out = MultiBuffering::<Sample, NUM_BUFFERS, NUM_SAMPLES>::new(); | ||
| 33 | let buffers_in = MultiBuffering::<Sample, NUM_BUFFERS, NUM_SAMPLES>::new(); | ||
| 34 | let mut full_duplex_stream = I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).full_duplex( | ||
| 35 | p.P0_29, | ||
| 36 | p.P0_28, | ||
| 37 | buffers_out, | ||
| 38 | buffers_in, | ||
| 39 | ); | ||
| 40 | |||
| 41 | let mut modulator = SineOsc::new(); | ||
| 42 | modulator.set_frequency(8.0, 1.0 / sample_rate as f32); | ||
| 43 | modulator.set_amplitude(1.0); | ||
| 44 | |||
| 45 | full_duplex_stream.start().await.expect("I2S Start"); | ||
| 46 | |||
| 47 | loop { | ||
| 48 | let (buff_out, buff_in) = full_duplex_stream.buffers(); | ||
| 49 | for i in 0..NUM_SAMPLES { | ||
| 50 | let modulation = (Sample::SCALE as f32 * bipolar_to_unipolar(modulator.generate())) as Sample; | ||
| 51 | buff_out[i] = buff_in[i] * modulation; | ||
| 52 | } | ||
| 53 | |||
| 54 | if let Err(err) = full_duplex_stream.send_and_receive().await { | ||
| 55 | error!("{}", err); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | struct SineOsc { | ||
| 61 | amplitude: f32, | ||
| 62 | modulo: f32, | ||
| 63 | phase_inc: f32, | ||
| 64 | } | ||
| 65 | |||
| 66 | impl SineOsc { | ||
| 67 | const B: f32 = 4.0 / PI; | ||
| 68 | const C: f32 = -4.0 / (PI * PI); | ||
| 69 | const P: f32 = 0.225; | ||
| 70 | |||
| 71 | pub fn new() -> Self { | ||
| 72 | Self { | ||
| 73 | amplitude: 1.0, | ||
| 74 | modulo: 0.0, | ||
| 75 | phase_inc: 0.0, | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { | ||
| 80 | self.phase_inc = freq * inv_sample_rate; | ||
| 81 | } | ||
| 82 | |||
| 83 | pub fn set_amplitude(&mut self, amplitude: f32) { | ||
| 84 | self.amplitude = amplitude; | ||
| 85 | } | ||
| 86 | |||
| 87 | pub fn generate(&mut self) -> f32 { | ||
| 88 | let signal = self.parabolic_sin(self.modulo); | ||
| 89 | self.modulo += self.phase_inc; | ||
| 90 | if self.modulo < 0.0 { | ||
| 91 | self.modulo += 1.0; | ||
| 92 | } else if self.modulo > 1.0 { | ||
| 93 | self.modulo -= 1.0; | ||
| 94 | } | ||
| 95 | signal * self.amplitude | ||
| 96 | } | ||
| 97 | |||
| 98 | fn parabolic_sin(&mut self, modulo: f32) -> f32 { | ||
| 99 | let angle = PI - modulo * 2.0 * PI; | ||
| 100 | let y = Self::B * angle + Self::C * angle * abs(angle); | ||
| 101 | Self::P * (y * abs(y) - y) + y | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | #[inline] | ||
| 106 | fn abs(value: f32) -> f32 { | ||
| 107 | if value < 0.0 { | ||
| 108 | -value | ||
| 109 | } else { | ||
| 110 | value | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | #[inline] | ||
| 115 | fn bipolar_to_unipolar(value: f32) -> f32 { | ||
| 116 | (value + 1.0) / 2.0 | ||
| 117 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{debug, error, info}; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; | ||
| 8 | use embassy_nrf::interrupt; | ||
| 9 | use embassy_nrf::pwm::{Prescaler, SimplePwm}; | ||
| 10 | use {defmt_rtt as _, panic_probe as _}; | ||
| 11 | |||
| 12 | type Sample = i16; | ||
| 13 | |||
| 14 | const NUM_SAMPLES: usize = 500; | ||
| 15 | |||
| 16 | #[embassy_executor::main] | ||
| 17 | async fn main(_spawner: Spawner) { | ||
| 18 | let p = embassy_nrf::init(Default::default()); | ||
| 19 | |||
| 20 | let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); | ||
| 21 | |||
| 22 | let sample_rate = master_clock.sample_rate(); | ||
| 23 | info!("Sample rate: {}", sample_rate); | ||
| 24 | |||
| 25 | let config = Config::default() | ||
| 26 | .sample_width(SampleWidth::_16bit) | ||
| 27 | .channels(Channels::MonoLeft); | ||
| 28 | |||
| 29 | let irq = interrupt::take!(I2S); | ||
| 30 | let buffers = DoubleBuffering::<Sample, NUM_SAMPLES>::new(); | ||
| 31 | let mut input_stream = | ||
| 32 | I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).input(p.P0_29, buffers); | ||
| 33 | |||
| 34 | // Configure the PWM to use the pins corresponding to the RGB leds | ||
| 35 | let mut pwm = SimplePwm::new_3ch(p.PWM0, p.P0_23, p.P0_22, p.P0_24); | ||
| 36 | pwm.set_prescaler(Prescaler::Div1); | ||
| 37 | pwm.set_max_duty(255); | ||
| 38 | |||
| 39 | let mut rms_online = RmsOnline::<NUM_SAMPLES>::default(); | ||
| 40 | |||
| 41 | input_stream.start().await.expect("I2S Start"); | ||
| 42 | |||
| 43 | loop { | ||
| 44 | let rms = rms_online.process(input_stream.buffer()); | ||
| 45 | let rgb = rgb_from_rms(rms); | ||
| 46 | |||
| 47 | debug!("RMS: {}, RGB: {:?}", rms, rgb); | ||
| 48 | for i in 0..3 { | ||
| 49 | pwm.set_duty(i, rgb[i].into()); | ||
| 50 | } | ||
| 51 | |||
| 52 | if let Err(err) = input_stream.receive().await { | ||
| 53 | error!("{}", err); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | /// RMS from 0.0 until 0.75 will give green with a proportional intensity | ||
| 59 | /// RMS from 0.75 until 0.9 will give a blend between orange and red proportionally to the intensity | ||
| 60 | /// RMS above 0.9 will give a red with a proportional intensity | ||
| 61 | fn rgb_from_rms(rms: f32) -> [u8; 3] { | ||
| 62 | if rms < 0.75 { | ||
| 63 | let intensity = rms / 0.75; | ||
| 64 | [0, (intensity * 165.0) as u8, 0] | ||
| 65 | } else if rms < 0.9 { | ||
| 66 | let intensity = (rms - 0.75) / 0.15; | ||
| 67 | [200, 165 - (165.0 * intensity) as u8, 0] | ||
| 68 | } else { | ||
| 69 | let intensity = (rms - 0.9) / 0.1; | ||
| 70 | [200 + (55.0 * intensity) as u8, 0, 0] | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | pub struct RmsOnline<const N: usize> { | ||
| 75 | pub squares: [f32; N], | ||
| 76 | pub head: usize, | ||
| 77 | } | ||
| 78 | |||
| 79 | impl<const N: usize> Default for RmsOnline<N> { | ||
| 80 | fn default() -> Self { | ||
| 81 | RmsOnline { | ||
| 82 | squares: [0.0; N], | ||
| 83 | head: 0, | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | impl<const N: usize> RmsOnline<N> { | ||
| 89 | pub fn reset(&mut self) { | ||
| 90 | self.squares = [0.0; N]; | ||
| 91 | self.head = 0; | ||
| 92 | } | ||
| 93 | |||
| 94 | pub fn process(&mut self, buf: &[Sample]) -> f32 { | ||
| 95 | buf.iter() | ||
| 96 | .for_each(|sample| self.push(*sample as f32 / Sample::SCALE as f32)); | ||
| 97 | |||
| 98 | let sum_of_squares = self.squares.iter().fold(0.0, |acc, v| acc + *v); | ||
| 99 | Self::approx_sqrt(sum_of_squares / N as f32) | ||
| 100 | } | ||
| 101 | |||
| 102 | pub fn push(&mut self, signal: f32) { | ||
| 103 | let square = signal * signal; | ||
| 104 | self.squares[self.head] = square; | ||
| 105 | self.head = (self.head + 1) % N; | ||
| 106 | } | ||
| 107 | |||
| 108 | /// Approximated sqrt taken from [micromath] | ||
| 109 | /// | ||
| 110 | /// [micromath]: https://docs.rs/micromath/latest/src/micromath/float/sqrt.rs.html#11-17 | ||
| 111 | /// | ||
| 112 | fn approx_sqrt(value: f32) -> f32 { | ||
| 113 | f32::from_bits((value.to_bits() + 0x3f80_0000) >> 1) | ||
| 114 | } | ||
| 115 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use core::f32::consts::PI; | ||
| 6 | |||
| 7 | use defmt::{error, info}; | ||
| 8 | use embassy_executor::Spawner; | ||
| 9 | use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; | ||
| 10 | use embassy_nrf::interrupt; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | type Sample = i16; | ||
| 14 | |||
| 15 | const NUM_SAMPLES: usize = 50; | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async fn main(_spawner: Spawner) { | ||
| 19 | let p = embassy_nrf::init(Default::default()); | ||
| 20 | |||
| 21 | let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); | ||
| 22 | |||
| 23 | let sample_rate = master_clock.sample_rate(); | ||
| 24 | info!("Sample rate: {}", sample_rate); | ||
| 25 | |||
| 26 | let config = Config::default() | ||
| 27 | .sample_width(SampleWidth::_16bit) | ||
| 28 | .channels(Channels::MonoLeft); | ||
| 29 | |||
| 30 | let irq = interrupt::take!(I2S); | ||
| 31 | let buffers = DoubleBuffering::<Sample, NUM_SAMPLES>::new(); | ||
| 32 | let mut output_stream = | ||
| 33 | I2S::master(p.I2S, irq, p.P0_25, p.P0_26, p.P0_27, master_clock, config).output(p.P0_28, buffers); | ||
| 34 | |||
| 35 | let mut waveform = Waveform::new(1.0 / sample_rate as f32); | ||
| 36 | |||
| 37 | waveform.process(output_stream.buffer()); | ||
| 38 | |||
| 39 | output_stream.start().await.expect("I2S Start"); | ||
| 40 | |||
| 41 | loop { | ||
| 42 | waveform.process(output_stream.buffer()); | ||
| 43 | |||
| 44 | if let Err(err) = output_stream.send().await { | ||
| 45 | error!("{}", err); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | struct Waveform { | ||
| 51 | inv_sample_rate: f32, | ||
| 52 | carrier: SineOsc, | ||
| 53 | freq_mod: SineOsc, | ||
| 54 | amp_mod: SineOsc, | ||
| 55 | } | ||
| 56 | |||
| 57 | impl Waveform { | ||
| 58 | fn new(inv_sample_rate: f32) -> Self { | ||
| 59 | let mut carrier = SineOsc::new(); | ||
| 60 | carrier.set_frequency(110.0, inv_sample_rate); | ||
| 61 | |||
| 62 | let mut freq_mod = SineOsc::new(); | ||
| 63 | freq_mod.set_frequency(1.0, inv_sample_rate); | ||
| 64 | freq_mod.set_amplitude(1.0); | ||
| 65 | |||
| 66 | let mut amp_mod = SineOsc::new(); | ||
| 67 | amp_mod.set_frequency(16.0, inv_sample_rate); | ||
| 68 | amp_mod.set_amplitude(0.5); | ||
| 69 | |||
| 70 | Self { | ||
| 71 | inv_sample_rate, | ||
| 72 | carrier, | ||
| 73 | freq_mod, | ||
| 74 | amp_mod, | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | fn process(&mut self, buf: &mut [Sample]) { | ||
| 79 | for sample in buf.chunks_mut(1) { | ||
| 80 | let freq_modulation = bipolar_to_unipolar(self.freq_mod.generate()); | ||
| 81 | self.carrier | ||
| 82 | .set_frequency(110.0 + 440.0 * freq_modulation, self.inv_sample_rate); | ||
| 83 | |||
| 84 | let amp_modulation = bipolar_to_unipolar(self.amp_mod.generate()); | ||
| 85 | self.carrier.set_amplitude(amp_modulation); | ||
| 86 | |||
| 87 | let signal = self.carrier.generate(); | ||
| 88 | |||
| 89 | sample[0] = (Sample::SCALE as f32 * signal) as Sample; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | struct SineOsc { | ||
| 95 | amplitude: f32, | ||
| 96 | modulo: f32, | ||
| 97 | phase_inc: f32, | ||
| 98 | } | ||
| 99 | |||
| 100 | impl SineOsc { | ||
| 101 | const B: f32 = 4.0 / PI; | ||
| 102 | const C: f32 = -4.0 / (PI * PI); | ||
| 103 | const P: f32 = 0.225; | ||
| 104 | |||
| 105 | pub fn new() -> Self { | ||
| 106 | Self { | ||
| 107 | amplitude: 1.0, | ||
| 108 | modulo: 0.0, | ||
| 109 | phase_inc: 0.0, | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { | ||
| 114 | self.phase_inc = freq * inv_sample_rate; | ||
| 115 | } | ||
| 116 | |||
| 117 | pub fn set_amplitude(&mut self, amplitude: f32) { | ||
| 118 | self.amplitude = amplitude; | ||
| 119 | } | ||
| 120 | |||
| 121 | pub fn generate(&mut self) -> f32 { | ||
| 122 | let signal = self.parabolic_sin(self.modulo); | ||
| 123 | self.modulo += self.phase_inc; | ||
| 124 | if self.modulo < 0.0 { | ||
| 125 | self.modulo += 1.0; | ||
| 126 | } else if self.modulo > 1.0 { | ||
| 127 | self.modulo -= 1.0; | ||
| 128 | } | ||
| 129 | signal * self.amplitude | ||
| 130 | } | ||
| 131 | |||
| 132 | fn parabolic_sin(&mut self, modulo: f32) -> f32 { | ||
| 133 | let angle = PI - modulo * 2.0 * PI; | ||
| 134 | let y = Self::B * angle + Self::C * angle * abs(angle); | ||
| 135 | Self::P * (y * abs(y) - y) + y | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | #[inline] | ||
| 140 | fn abs(value: f32) -> f32 { | ||
| 141 | if value < 0.0 { | ||
| 142 | -value | ||
| 143 | } else { | ||
| 144 | value | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | #[inline] | ||
| 149 | fn bipolar_to_unipolar(value: f32) -> f32 { | ||
| 150 | (value + 1.0) / 2.0 | ||
| 151 | } | ||
