aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Perez Llamas <[email protected]>2022-11-09 19:14:43 +0100
committerChristian Perez Llamas <[email protected]>2022-11-09 19:19:01 +0100
commitcecd77938c694ff2bad2a259ff64f2f468dcb04a (patch)
treead4cca7a642cc0a8a2fd2858538560d0a2ba55b1
parent059610a8de49ff2d38311f343d3d1a6f8d90a720 (diff)
Draft: Initial support for I2S with a working example.
Co-authored-by: @brainstorm <[email protected]>
-rw-r--r--embassy-nrf/Cargo.toml1
-rw-r--r--embassy-nrf/src/chips/nrf52840.rs5
-rw-r--r--embassy-nrf/src/i2s.rs403
-rw-r--r--embassy-nrf/src/lib.rs2
-rw-r--r--examples/nrf/Cargo.toml2
-rw-r--r--examples/nrf/src/bin/i2s.rs48
6 files changed, 460 insertions, 1 deletions
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"]
48nrf9160-ns = ["_nrf9160"] 48nrf9160-ns = ["_nrf9160"]
49 49
50gpiote = [] 50gpiote = []
51i2s = []
51time-driver-rtc1 = ["_time-driver"] 52time-driver-rtc1 = ["_time-driver"]
52 53
53# Features starting with `_` are for internal use only. They're not intended 54# 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! {
164 164
165 // PDM 165 // PDM
166 PDM, 166 PDM,
167
168 // I2S
169 I2S,
167} 170}
168 171
169#[cfg(feature = "nightly")] 172#[cfg(feature = "nightly")]
@@ -285,6 +288,8 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5);
285impl_saadc_input!(P0_30, ANALOG_INPUT6); 288impl_saadc_input!(P0_30, ANALOG_INPUT6);
286impl_saadc_input!(P0_31, ANALOG_INPUT7); 289impl_saadc_input!(P0_31, ANALOG_INPUT7);
287 290
291impl_i2s!(I2S, I2S, I2S);
292
288pub mod irqs { 293pub mod irqs {
289 use embassy_cortex_m::interrupt::_export::declare; 294 use embassy_cortex_m::interrupt::_export::declare;
290 295
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 @@
1#![macro_use]
2
3//! I2S
4
5use core::future::poll_fn;
6use core::sync::atomic::{compiler_fence, Ordering};
7use core::task::Poll;
8
9use embassy_hal_common::drop::OnDrop;
10use embassy_hal_common::{into_ref, PeripheralRef};
11use pac::i2s::config::mcken;
12
13use crate::{pac, Peripheral};
14use crate::interrupt::{Interrupt, InterruptExt};
15use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
16use crate::gpio::sealed::Pin as _;
17
18// TODO: Define those in lib.rs somewhere else
19//
20// I2S EasyDMA MAXCNT bit length = 14
21const MAX_DMA_MAXCNT: u32 = 1 << 14;
22
23// Limits for Easy DMA - it can only read from data ram
24pub const SRAM_LOWER: usize = 0x2000_0000;
25pub const SRAM_UPPER: usize = 0x3000_0000;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29#[non_exhaustive]
30pub enum Error {
31 BufferTooLong,
32 BufferZeroLength,
33 DMABufferNotInDataMemory,
34 BufferMisaligned,
35 // TODO: add other error variants.
36}
37
38#[derive(Clone)]
39#[non_exhaustive]
40pub struct Config {
41 pub ratio: Ratio,
42 pub sample_width: SampleWidth,
43 pub align: Align,
44 pub format: Format,
45 pub channels: Channels,
46}
47
48impl Default for Config {
49 fn default() -> Self {
50 Self {
51 ratio: Ratio::_32x,
52 sample_width: SampleWidth::_16bit,
53 align: Align::Left,
54 format: Format::I2S,
55 channels: Channels::Stereo,
56 }
57 }
58}
59
60/// MCK / LRCK ratio.
61#[derive(Debug, Eq, PartialEq, Clone, Copy)]
62pub enum Ratio {
63 _32x,
64 _48x,
65 _64x,
66 _96x,
67 _128x,
68 _192x,
69 _256x,
70 _384x,
71 _512x,
72}
73
74impl From<Ratio> for u8 {
75 fn from(variant: Ratio) -> Self {
76 variant as _
77 }
78}
79
80#[derive(Debug, Eq, PartialEq, Clone, Copy)]
81pub enum SampleWidth {
82 _8bit,
83 _16bit,
84 _24bit,
85}
86
87impl From<SampleWidth> for u8 {
88 fn from(variant: SampleWidth) -> Self {
89 variant as _
90 }
91}
92
93/// Alignment of sample within a frame.
94#[derive(Debug, Eq, PartialEq, Clone, Copy)]
95pub enum Align {
96 Left,
97 Right,
98}
99
100impl From<Align> for bool {
101 fn from(variant: Align) -> Self {
102 match variant {
103 Align::Left => false,
104 Align::Right => true,
105 }
106 }
107}
108
109/// Frame format.
110#[derive(Debug, Eq, PartialEq, Clone, Copy)]
111pub enum Format {
112 I2S,
113 Aligned,
114}
115
116impl From<Format> for bool {
117 fn from(variant: Format) -> Self {
118 match variant {
119 Format::I2S => false,
120 Format::Aligned => true,
121 }
122 }
123}
124
125/// Enable channels.
126#[derive(Debug, Eq, PartialEq, Clone, Copy)]
127pub enum Channels {
128 Stereo,
129 Left,
130 Right,
131}
132
133impl From<Channels> for u8 {
134 fn from(variant: Channels) -> Self {
135 variant as _
136 }
137}
138
139/// I2S Mode
140#[derive(Debug, Eq, PartialEq, Clone, Copy)]
141pub enum Mode {
142 Controller,
143 Peripheral,
144}
145
146// /// Master clock generator frequency.
147// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
148// pub enum MckFreq {
149// _32MDiv8 = 0x20000000,
150// _32MDiv10 = 0x18000000,
151// _32MDiv11 = 0x16000000,
152// _32MDiv15 = 0x11000000,
153// _32MDiv16 = 0x10000000,
154// _32MDiv21 = 0x0C000000,
155// _32MDiv23 = 0x0B000000,
156// _32MDiv30 = 0x08800000,
157// _32MDiv31 = 0x08400000,
158// _32MDiv32 = 0x08000000,
159// _32MDiv42 = 0x06000000,
160// _32MDiv63 = 0x04100000,
161// _32MDiv125 = 0x020C0000,
162// }
163
164
165/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload.
166///
167/// For more details about EasyDMA, consult the module documentation.
168pub struct I2s<'d, T: Instance> {
169 output: I2sOutput<'d, T>,
170 input: I2sInput<'d, T>,
171}
172
173/// Transmitter interface to the UARTE peripheral obtained
174/// via [Uarte]::split.
175pub struct I2sOutput<'d, T: Instance> {
176 _p: PeripheralRef<'d, T>,
177}
178
179/// Receiver interface to the UARTE peripheral obtained
180/// via [Uarte]::split.
181pub struct I2sInput<'d, T: Instance> {
182 _p: PeripheralRef<'d, T>,
183}
184
185impl<'d, T: Instance> I2s<'d, T> {
186 /// Create a new I2S
187 pub fn new(
188 i2s: impl Peripheral<P = T> + 'd,
189 // irq: impl Peripheral<P = T::Interrupt> + 'd,
190 mck: impl Peripheral<P = impl GpioPin> + 'd,
191 sck: impl Peripheral<P = impl GpioPin> + 'd,
192 lrck: impl Peripheral<P = impl GpioPin> + 'd,
193 sdin: impl Peripheral<P = impl GpioPin> + 'd,
194 sdout: impl Peripheral<P = impl GpioPin> + 'd,
195 config: Config,
196 ) -> Self {
197 into_ref!(mck, sck, lrck, sdin, sdout);
198 Self::new_inner(
199 i2s,
200 // irq,
201 mck.map_into(), sck.map_into(), lrck.map_into(), sdin.map_into(), sdout.map_into(), config)
202 }
203
204 fn new_inner(
205 i2s: impl Peripheral<P = T> + 'd,
206 // irq: impl Peripheral<P = T::Interrupt> + 'd,
207 mck: PeripheralRef<'d, AnyPin>,
208 sck: PeripheralRef<'d, AnyPin>,
209 lrck: PeripheralRef<'d, AnyPin>,
210 sdin: PeripheralRef<'d, AnyPin>,
211 sdout: PeripheralRef<'d, AnyPin>,
212 config: Config,
213 ) -> Self {
214 into_ref!(
215 i2s,
216 // irq,
217 mck, sck, lrck, sdin, sdout);
218
219 let r = T::regs();
220
221 // TODO get configuration rather than hardcoding ratio, swidth, align, format, channels
222
223 r.config.mcken.write(|w| w.mcken().enabled());
224 r.config.mckfreq.write(|w| w.mckfreq()._32mdiv16());
225 r.config.ratio.write(|w| w.ratio()._192x());
226 r.config.mode.write(|w| w.mode().master());
227 r.config.swidth.write(|w| w.swidth()._16bit());
228 r.config.align.write(|w| w.align().left());
229 r.config.format.write(|w| w.format().i2s());
230 r.config.channels.write(|w| w.channels().stereo());
231
232 r.psel.mck.write(|w| {
233 unsafe { w.bits(mck.psel_bits()) };
234 w.connect().connected()
235 });
236
237 r.psel.sck.write(|w| {
238 unsafe { w.bits(sck.psel_bits()) };
239 w.connect().connected()
240 });
241
242 r.psel.lrck.write(|w| {
243 unsafe { w.bits(lrck.psel_bits()) };
244 w.connect().connected()
245 });
246
247 r.psel.sdin.write(|w| {
248 unsafe { w.bits(sdin.psel_bits()) };
249 w.connect().connected()
250 });
251
252 r.psel.sdout.write(|w| {
253 unsafe { w.bits(sdout.psel_bits()) };
254 w.connect().connected()
255 });
256
257 r.enable.write(|w| w.enable().enabled());
258
259 Self {
260 output: I2sOutput {
261 _p: unsafe { i2s.clone_unchecked() },
262 },
263 input: I2sInput { _p: i2s },
264 }
265 }
266
267 /// Enables the I2S module.
268 #[inline(always)]
269 pub fn enable(&self) -> &Self {
270 let r = T::regs();
271 r.enable.write(|w| w.enable().enabled());
272 self
273 }
274
275 /// Disables the I2S module.
276 #[inline(always)]
277 pub fn disable(&self) -> &Self {
278 let r = T::regs();
279 r.enable.write(|w| w.enable().disabled());
280 self
281 }
282
283 /// Starts I2S transfer.
284 #[inline(always)]
285 pub fn start(&self) -> &Self {
286 let r = T::regs();
287 self.enable();
288 r.tasks_start.write(|w| unsafe { w.bits(1) });
289 self
290 }
291
292 /// Stops the I2S transfer and waits until it has stopped.
293 #[inline(always)]
294 pub fn stop(&self) -> &Self {
295 todo!()
296 }
297
298 /// Enables/disables I2S transmission (TX).
299 #[inline(always)]
300 pub fn set_tx_enabled(&self, enabled: bool) -> &Self {
301 let r = T::regs();
302 r.config.txen.write(|w| w.txen().bit(enabled));
303 self
304 }
305
306 /// Enables/disables I2S reception (RX).
307 #[inline(always)]
308 pub fn set_rx_enabled(&self, enabled: bool) -> &Self {
309 let r = T::regs();
310 r.config.rxen.write(|w| w.rxen().bit(enabled));
311 self
312 }
313
314 /// Transmits the given `tx_buffer`.
315 /// Buffer address must be 4 byte aligned and located in RAM.
316 /// Returns a value that represents the in-progress DMA transfer.
317 // TODO Define a better interface for the input buffer
318 #[allow(unused_mut)]
319 pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> {
320 self.output.tx(ptr, len).await
321 }
322}
323
324impl<'d, T: Instance> I2sOutput<'d, T> {
325 /// Transmits the given `tx_buffer`.
326 /// Buffer address must be 4 byte aligned and located in RAM.
327 /// Returns a value that represents the in-progress DMA transfer.
328 // TODO Define a better interface for the input buffer
329 pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> {
330 if ptr as u32 % 4 != 0 {
331 return Err(Error::BufferMisaligned);
332 }
333 let maxcnt = (len / (core::mem::size_of::<u32>() / core::mem::size_of::<u8>())) as u32;
334 if maxcnt > MAX_DMA_MAXCNT {
335 return Err(Error::BufferTooLong);
336 }
337 if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER {
338 return Err(Error::DMABufferNotInDataMemory);
339 }
340
341 let r = T::regs();
342 let _s = T::state();
343
344 // TODO we can not progress until the last buffer written in TXD.PTR
345 // has started the transmission.
346 // We can use some sync primitive from `embassy-sync`.
347
348 r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) });
349 r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) });
350
351 Ok(())
352 }
353}
354
355pub(crate) mod sealed {
356 use core::sync::atomic::AtomicU8;
357
358 use embassy_sync::waitqueue::AtomicWaker;
359
360 use super::*;
361
362 pub struct State {
363 pub input_waker: AtomicWaker,
364 pub output_waker: AtomicWaker,
365 pub buffers_refcount: AtomicU8,
366 }
367 impl State {
368 pub const fn new() -> Self {
369 Self {
370 input_waker: AtomicWaker::new(),
371 output_waker: AtomicWaker::new(),
372 buffers_refcount: AtomicU8::new(0),
373 }
374 }
375 }
376
377 pub trait Instance {
378 fn regs() -> &'static pac::i2s::RegisterBlock;
379 fn state() -> &'static State;
380 }
381}
382
383pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
384 type Interrupt: Interrupt;
385}
386
387macro_rules! impl_i2s {
388 ($type:ident, $pac_type:ident, $irq:ident) => {
389 impl crate::i2s::sealed::Instance for peripherals::$type {
390 fn regs() -> &'static pac::i2s::RegisterBlock {
391 unsafe { &*pac::$pac_type::ptr() }
392 }
393 fn state() -> &'static crate::i2s::sealed::State {
394 static STATE: crate::i2s::sealed::State = crate::i2s::sealed::State::new();
395 &STATE
396 }
397 }
398 impl crate::i2s::Instance for peripherals::$type {
399 type Interrupt = crate::interrupt::$irq;
400 }
401 };
402}
403
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;
74pub mod gpio; 74pub mod gpio;
75#[cfg(feature = "gpiote")] 75#[cfg(feature = "gpiote")]
76pub mod gpiote; 76pub mod gpiote;
77// #[cfg(all(feature = "i2s", feature = "nrf52840"))]
78pub mod i2s;
77#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] 79#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
78pub mod nvmc; 80pub mod nvmc;
79#[cfg(any( 81#[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" }
14embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } 14embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] }
15embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } 15embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] }
16embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } 16embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
17embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } 17embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "i2s", "unstable-pac"] }
18embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } 18embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true }
19embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } 19embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true }
20embedded-io = "0.3.1" 20embedded-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 @@
1// Example inspired by RTIC's I2S demo: https://github.com/nrf-rs/nrf-hal/blob/master/examples/i2s-controller-demo/src/main.rs
2
3#![no_std]
4#![no_main]
5#![feature(type_alias_impl_trait)]
6
7use defmt::*;
8use embassy_executor::Spawner;
9use embassy_nrf::{i2s};
10use {defmt_rtt as _, panic_probe as _};
11
12#[repr(align(4))]
13pub struct Aligned<T: ?Sized>(T);
14
15#[embassy_executor::main]
16async fn main(_spawner: Spawner) {
17 let p = embassy_nrf::init(Default::default());
18 let config = i2s::Config::default();
19
20 let mut i2s = i2s::I2s::new(p.I2S, p.P0_28, p.P0_29, p.P0_31, p.P0_11, p.P0_30, config);
21
22 let mut signal_buf: Aligned<[i16; 32]> = Aligned([0i16; 32]);
23 let len = signal_buf.0.len() / 2;
24 for x in 0..len {
25 signal_buf.0[2 * x] = triangle_wave(x as i32, len, 2048, 0, 1) as i16;
26 signal_buf.0[2 * x + 1] = triangle_wave(x as i32, len, 2048, 0, 1) as i16;
27 }
28
29 let ptr = &signal_buf.0 as *const i16 as *const u8;
30 let len = signal_buf.0.len() * core::mem::size_of::<i16>();
31
32 i2s.start();
33 i2s.set_tx_enabled(true);
34
35 loop {
36 i2s.tx(ptr, len).await;
37 }
38}
39
40fn triangle_wave(x: i32, length: usize, amplitude: i32, phase: i32, periods: i32) -> i32 {
41 let length = length as i32;
42 amplitude
43 - ((2 * periods * (x + phase + length / (4 * periods)) * amplitude / length)
44 % (2 * amplitude)
45 - amplitude)
46 .abs()
47 - amplitude / 2
48}