Skip to content

Commit 15384bc

Browse files
committed
Add bidirectional I2S class.
1 parent 3c97fd8 commit 15384bc

File tree

14 files changed

+753
-14
lines changed

14 files changed

+753
-14
lines changed
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include <stdint.h>
8+
#include <string.h>
9+
10+
#include "mpconfigport.h"
11+
12+
#include "py/mperrno.h"
13+
#include "py/runtime.h"
14+
#include "common-hal/audiobusio/I2S.h"
15+
#include "shared-bindings/audiobusio/I2S.h"
16+
#include "shared-bindings/microcontroller/__init__.h"
17+
#include "shared-bindings/microcontroller/Pin.h"
18+
#include "shared-module/audiocore/__init__.h"
19+
#include "bindings/rp2pio/StateMachine.h"
20+
21+
#include "audio_dma.h"
22+
23+
#include "src/rp2_common/hardware_pio/include/hardware/pio_instructions.h"
24+
25+
#define I2S_CODE(bits_per_sample, out, in, left_justified, swap) \
26+
{ \
27+
/* /--- LRCLK */ \
28+
/* |/-- BCLK */ \
29+
/* || */ \
30+
/* 00 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap) | pio_encode_delay(1), \
31+
/* .wrap_target */ \
32+
/* 01 */ (out ? pio_encode_pull(false, false) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \
33+
/* 02 */ (out ? pio_encode_mov(pio_x, pio_osr) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \
34+
/* 03 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00) | pio_encode_delay(3), \
35+
/* 04 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap), \
36+
/* 05 */ pio_encode_jmp_y_dec(3) | pio_encode_sideset(2, 0b01 << swap) | pio_encode_delay(2), \
37+
/* 06 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00 | !left_justified << !swap) | pio_encode_delay(3), \
38+
/* 07 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | !left_justified << !swap), \
39+
/* 08 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b01 << swap | !left_justified << !swap) | pio_encode_delay(2), \
40+
/* 09 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b10 >> swap) | pio_encode_delay(3), \
41+
/* 10 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b11), \
42+
/* 11 */ pio_encode_jmp_y_dec(9) | pio_encode_sideset(2, 0b11) | pio_encode_delay(2), \
43+
/* 12 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00 | left_justified << !swap) | pio_encode_delay(2), \
44+
/* 13 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b00 | left_justified << !swap), \
45+
/* 13 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \
46+
/* 14 */ (in ? pio_encode_push(false, false) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \
47+
/* .wrap */ \
48+
}
49+
50+
// Caller validates that pins are free.
51+
void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self,
52+
const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select,
53+
const mcu_pin_obj_t *data_out, const mcu_pin_obj_t *data_in,
54+
const mcu_pin_obj_t *main_clock, bool left_justified,
55+
uint32_t buffer_size, uint8_t channel_count, uint32_t sample_rate,
56+
uint8_t bits_per_sample, bool samples_signed) {
57+
if (main_clock != NULL) {
58+
mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_main_clock);
59+
}
60+
61+
const mcu_pin_obj_t *sideset_pin = NULL;
62+
bool swap = false;
63+
64+
if (bit_clock->number == word_select->number - 1) {
65+
sideset_pin = bit_clock;
66+
} else if (bit_clock->number == word_select->number + 1) {
67+
sideset_pin = word_select;
68+
swap = true;
69+
} else {
70+
mp_raise_ValueError(MP_ERROR_TEXT("Bit clock and word select must be sequential GPIO pins"));
71+
}
72+
73+
uint16_t program[] = I2S_CODE(bits_per_sample, data_out != NULL, data_in != NULL, left_justified, swap);
74+
75+
// Use the state machine to manage pins.
76+
common_hal_rp2pio_statemachine_construct(
77+
&self->state_machine,
78+
program, MP_ARRAY_SIZE(program),
79+
sample_rate * bits_per_sample * 16, // Frequency based on sample rate and bit width
80+
NULL, 0, // init
81+
NULL, 0, // may_exec
82+
data_out, 1, 0, 0xffffffff, // out pin
83+
data_in, 1, // in pins
84+
0, 0, // in pulls
85+
NULL, 1, 0, 0, // set pins
86+
sideset_pin, 2, false, 0, 0x1f, // sideset pins
87+
false, // No sideset enable
88+
NULL, PULL_NONE, // jump pin
89+
0, // wait gpio pins
90+
true, // exclusive pin use
91+
false, 32, false, // out settings
92+
false, // Wait for txstall
93+
false, 32, false, // in settings
94+
false, // Not user-interruptible.
95+
1, -1, // wrap settings
96+
PIO_ANY_OFFSET,
97+
PIO_FIFO_TYPE_DEFAULT,
98+
PIO_MOV_STATUS_DEFAULT,
99+
PIO_MOV_N_DEFAULT
100+
);
101+
102+
self->buffer_size = buffer_size;
103+
self->channel_count = channel_count;
104+
self->sample_rate = sample_rate;
105+
self->bits_per_sample = bits_per_sample;
106+
self->samples_signed = samples_signed;
107+
108+
self->playing = false;
109+
audio_dma_init(&self->dma);
110+
}
111+
112+
void i2s_configure_audio_dma(audiobusio_i2s_obj_t *self, mp_obj_t sample, bool loop, uint32_t sample_rate, uint8_t bits_per_sample) {
113+
114+
if (self->dma.output_channel[0] != NUM_DMA_CHANNELS || self->dma.input_channel[0] != NUM_DMA_CHANNELS) {
115+
if (self->state_machine.out) {
116+
audio_dma_stop_output(&self->dma);
117+
}
118+
if (self->state_machine.in) {
119+
audio_dma_stop_input(&self->dma);
120+
}
121+
audio_dma_deinit(&self->dma);
122+
}
123+
124+
common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, sample_rate * bits_per_sample * 16);
125+
common_hal_rp2pio_statemachine_restart(&self->state_machine);
126+
127+
// On the RP2040, output registers are always written with a 32-bit write.
128+
// If the write is 8 or 16 bits wide, the data will be replicated in upper bytes.
129+
// See section 2.1.4 Narrow IO Register Writes in the RP2040 datasheet.
130+
// This means that identical 16-bit audio data will be written in both halves of the incoming PIO
131+
// FIFO register. Thus we get mono-to-stereo conversion for the I2S output for free.
132+
audio_dma_result result = audio_dma_setup(
133+
&self->dma,
134+
sample,
135+
loop,
136+
false, // single channel
137+
0, // audio channel
138+
true, // output signed
139+
bits_per_sample,
140+
(self->state_machine.out ? (uint32_t)&self->state_machine.pio->txf[self->state_machine.state_machine] : 0), // output register
141+
(self->state_machine.out ? self->state_machine.tx_dreq : 0), // output data request line
142+
(self->state_machine.in ? (uint32_t)&self->state_machine.pio->rxf[self->state_machine.state_machine] : 0), // input register
143+
(self->state_machine.in ? self->state_machine.rx_dreq : 0), // input data request line
144+
false); // swap channel
145+
146+
if (result == AUDIO_DMA_DMA_BUSY) {
147+
common_hal_audiobusio_i2s_stop(self);
148+
mp_raise_RuntimeError(MP_ERROR_TEXT("No DMA channel found"));
149+
} else if (result == AUDIO_DMA_MEMORY_ERROR) {
150+
common_hal_audiobusio_i2s_stop(self);
151+
mp_raise_RuntimeError(MP_ERROR_TEXT("Unable to allocate buffers for signed conversion"));
152+
}
153+
}
154+
155+
bool common_hal_audiobusio_i2s_deinited(audiobusio_i2s_obj_t *self) {
156+
return common_hal_rp2pio_statemachine_deinited(&self->state_machine);
157+
}
158+
159+
void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self) {
160+
if (common_hal_audiobusio_i2s_deinited(self)) {
161+
return;
162+
}
163+
164+
common_hal_rp2pio_statemachine_deinit(&self->state_machine);
165+
166+
audio_dma_deinit(&self->dma);
167+
}
168+
169+
void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self,
170+
mp_obj_t sample, bool loop) {
171+
if (!self->state_machine.out) {
172+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data out"));
173+
}
174+
175+
if (common_hal_audiobusio_i2s_get_playing(self)) {
176+
common_hal_audiobusio_i2s_stop(self);
177+
}
178+
179+
uint8_t bits_per_sample = audiosample_bits_per_sample(sample);
180+
uint32_t sample_rate = audiosample_sample_rate(sample);
181+
uint8_t channel_count = audiosample_channel_count(sample);
182+
if (channel_count > 2) {
183+
mp_raise_ValueError(MP_ERROR_TEXT("Too many channels in sample."));
184+
}
185+
186+
if (self->state_machine.in) {
187+
if (bits_per_sample > self->bits_per_sample) {
188+
mp_raise_ValueError(MP_ERROR_TEXT("Bits per sample cannot be greater than input."));
189+
}
190+
if (sample_rate != self->sample_rate) {
191+
mp_raise_ValueError(MP_ERROR_TEXT("Sample rate must match."));
192+
}
193+
}
194+
195+
i2s_configure_audio_dma(self, sample, loop, sample_rate, bits_per_sample);
196+
self->playing = true;
197+
}
198+
199+
void common_hal_audiobusio_i2s_pause(audiobusio_i2s_obj_t *self) {
200+
if (!self->state_machine.out) {
201+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data out"));
202+
}
203+
audio_dma_pause(&self->dma);
204+
}
205+
206+
void common_hal_audiobusio_i2s_resume(audiobusio_i2s_obj_t *self) {
207+
if (!self->state_machine.out) {
208+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data out"));
209+
}
210+
// Maybe: Clear any overrun/underrun errors
211+
audio_dma_resume(&self->dma);
212+
}
213+
214+
bool common_hal_audiobusio_i2s_get_paused(audiobusio_i2s_obj_t *self) {
215+
if (!self->state_machine.out) {
216+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data out"));
217+
}
218+
return audio_dma_get_paused(&self->dma);
219+
}
220+
221+
void common_hal_audiobusio_i2s_stop(audiobusio_i2s_obj_t *self) {
222+
if (!self->state_machine.out) {
223+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data out"));
224+
}
225+
226+
audio_dma_stop(&self->dma);
227+
228+
common_hal_rp2pio_statemachine_stop(&self->state_machine);
229+
230+
self->playing = false;
231+
}
232+
233+
bool common_hal_audiobusio_i2s_get_playing(audiobusio_i2s_obj_t *self) {
234+
if (!self->state_machine.out) {
235+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data out"));
236+
}
237+
238+
bool playing = audio_dma_get_playing(&self->dma);
239+
if (!playing && self->playing) {
240+
common_hal_audiobusio_i2s_stop(self);
241+
}
242+
return playing;
243+
}
244+
245+
uint32_t common_hal_audiobusio_i2s_get_sample_rate(audiobusio_i2s_obj_t *self) {
246+
if (!self->state_machine.in) {
247+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data in"));
248+
}
249+
return self->sample_rate;
250+
}
251+
252+
uint8_t common_hal_audiobusio_i2s_get_channel_count(audiobusio_i2s_obj_t *self) {
253+
if (!self->state_machine.in) {
254+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data in"));
255+
}
256+
return self->channel_count;
257+
}
258+
259+
uint8_t common_hal_audiobusio_i2s_get_bits_per_sample(audiobusio_i2s_obj_t *self) {
260+
if (!self->state_machine.in) {
261+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data in"));
262+
}
263+
return self->bits_per_sample;
264+
}
265+
266+
void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self,
267+
bool single_channel_output,
268+
uint8_t channel) {
269+
if (!self->state_machine.in) {
270+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data in"));
271+
}
272+
273+
if (single_channel_output) {
274+
mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported."));
275+
}
276+
277+
i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample);
278+
}
279+
280+
audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self,
281+
bool single_channel_output,
282+
uint8_t channel,
283+
uint8_t **buffer,
284+
uint32_t *buffer_length) {
285+
if (!self->state_machine.in) {
286+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data in"));
287+
}
288+
289+
if (single_channel_output) {
290+
mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported."));
291+
}
292+
293+
// Do other things while we wait for the buffer to fill.
294+
while (!audio_dma_has_buffer(&self->dma)) {
295+
// BUG: Issue with interrupt?
296+
if (self->state_machine.out) {
297+
common_hal_mcu_delay_us(1000000 / self->sample_rate);
298+
} else {
299+
RUN_BACKGROUND_TASKS;
300+
}
301+
}
302+
303+
*buffer_length = self->buffer_size;
304+
*buffer = audio_dma_get_buffer(&self->dma);
305+
return GET_BUFFER_MORE_DATA;
306+
}
307+
308+
void audiobusio_i2s_get_buffer_structure(audiobusio_i2s_obj_t *self, bool single_channel_output,
309+
bool *single_buffer, bool *samples_signed,
310+
uint32_t *max_buffer_length, uint8_t *spacing) {
311+
if (!self->state_machine.in) {
312+
mp_raise_RuntimeError(MP_ERROR_TEXT("No data in"));
313+
}
314+
315+
*single_buffer = false;
316+
*samples_signed = self->samples_signed;
317+
*max_buffer_length = self->buffer_size;
318+
if (single_channel_output) {
319+
*spacing = self->channel_count;
320+
} else {
321+
*spacing = 1;
322+
}
323+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "common-hal/microcontroller/Pin.h"
10+
#include "common-hal/rp2pio/StateMachine.h"
11+
12+
#include "audio_dma.h"
13+
#include "py/obj.h"
14+
15+
#include "shared-module/audiocore/__init__.h"
16+
17+
// We don't bit pack because we'll only have two at most. Its better to save code size instead.
18+
typedef struct {
19+
mp_obj_base_t base;
20+
rp2pio_statemachine_obj_t state_machine;
21+
audio_dma_t dma;
22+
bool left_justified;
23+
bool playing;
24+
uint32_t buffer_size;
25+
uint8_t channel_count;
26+
uint32_t sample_rate;
27+
uint8_t bits_per_sample;
28+
bool samples_signed;
29+
} audiobusio_i2s_obj_t;
30+
31+
// These are not available from Python because it may be called in an interrupt.
32+
void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self,
33+
bool single_channel_output,
34+
uint8_t channel);
35+
audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self,
36+
bool single_channel_output,
37+
uint8_t channel,
38+
uint8_t **buffer,
39+
uint32_t *buffer_length); // length in bytes
40+
void audiobusio_i2s_get_buffer_structure(audiobusio_i2s_obj_t *self, bool single_channel_output,
41+
bool *single_buffer, bool *samples_signed,
42+
uint32_t *max_buffer_length, uint8_t *spacing);
43+
44+
void i2s_configure_audio_dma(audiobusio_i2s_obj_t *self, mp_obj_t sample, bool loop,
45+
uint32_t sample_rate, uint8_t bits_per_sample);

ports/raspberrypi/common-hal/audiobusio/I2SIn.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
#include "audio_dma.h"
2020

21+
#if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT
22+
2123
const uint16_t i2sin_program_mono[] = {
2224
// pull block side 0b11 ; Load OSR with bits_per_sample-2
2325
0x98a0,
@@ -324,3 +326,5 @@ void audiobusio_i2sin_get_buffer_structure(audiobusio_i2sin_obj_t *self, bool si
324326
*spacing = 1;
325327
}
326328
}
329+
330+
#endif // CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT

ports/raspberrypi/common-hal/audiobusio/I2SIn.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#include "shared-module/audiocore/__init__.h"
1616

17+
#if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT
18+
1719
// We don't bit pack because we'll only have two at most. Its better to save code size instead.
1820
typedef struct {
1921
mp_obj_base_t base;
@@ -39,3 +41,5 @@ audioio_get_buffer_result_t audiobusio_i2sin_get_buffer(audiobusio_i2sin_obj_t *
3941
void audiobusio_i2sin_get_buffer_structure(audiobusio_i2sin_obj_t *self, bool single_channel_output,
4042
bool *single_buffer, bool *samples_signed,
4143
uint32_t *max_buffer_length, uint8_t *spacing);
44+
45+
#endif // CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT

0 commit comments

Comments
 (0)