Skip to content

Commit 2236879

Browse files
committed
audio_i2sin module for espressif and raspberrypi
1 parent e512a25 commit 2236879

29 files changed

Lines changed: 1462 additions & 5 deletions

File tree

locale/circuitpython.pot

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ msgstr ""
219219
msgid "%q must be array of type 'h'"
220220
msgstr ""
221221

222-
#: shared-bindings/audiobusio/PDMIn.c
222+
#: shared-bindings/audio_i2sin/I2SIn.c shared-bindings/audiobusio/PDMIn.c
223223
msgid "%q must be multiple of 8."
224224
msgstr ""
225225

@@ -659,6 +659,7 @@ msgstr ""
659659
msgid "Below minimum frame rate"
660660
msgstr ""
661661

662+
#: ports/raspberrypi/common-hal/audio_i2sin/I2SIn.c
662663
#: ports/raspberrypi/common-hal/audiobusio/I2SOut.c
663664
msgid "Bit clock and word select must be sequential GPIO pins"
664665
msgstr ""
@@ -804,7 +805,7 @@ msgstr ""
804805
msgid "Cannot pull on input-only pin."
805806
msgstr ""
806807

807-
#: shared-bindings/audiobusio/PDMIn.c
808+
#: shared-bindings/audio_i2sin/I2SIn.c shared-bindings/audiobusio/PDMIn.c
808809
msgid "Cannot record to a file"
809810
msgstr ""
810811

@@ -926,7 +927,7 @@ msgstr ""
926927
msgid "Deep sleep pins must use a rising edge with pulldown"
927928
msgstr ""
928929

929-
#: shared-bindings/audiobusio/PDMIn.c
930+
#: shared-bindings/audio_i2sin/I2SIn.c shared-bindings/audiobusio/PDMIn.c
930931
msgid "Destination capacity is smaller than destination_length."
931932
msgstr ""
932933

@@ -1694,6 +1695,10 @@ msgstr ""
16941695
msgid "Ok"
16951696
msgstr ""
16961697

1698+
#: ports/raspberrypi/common-hal/audio_i2sin/I2SIn.c
1699+
msgid "Only 16, 24, or 32 bit depth supported."
1700+
msgstr ""
1701+
16971702
#: ports/atmel-samd/common-hal/audiobusio/PDMIn.c
16981703
#: ports/raspberrypi/common-hal/audiobusio/PDMIn.c
16991704
#, c-format
@@ -1806,6 +1811,7 @@ msgstr ""
18061811
msgid "Parameter error"
18071812
msgstr ""
18081813

1814+
#: ports/espressif/common-hal/audio_i2sin/I2SIn.c
18091815
#: ports/espressif/common-hal/audiobusio/__init__.c
18101816
msgid "Peripheral in use"
18111817
msgstr ""
@@ -2696,6 +2702,7 @@ msgstr ""
26962702
msgid "binary op %q not implemented"
26972703
msgstr ""
26982704

2705+
#: ports/espressif/common-hal/audio_i2sin/I2SIn.c
26992706
#: ports/espressif/common-hal/audiobusio/PDMIn.c
27002707
msgid "bit_depth must be 8, 16, 24, or 32."
27012708
msgstr ""
@@ -3088,15 +3095,20 @@ msgstr ""
30883095
msgid "default is not a function"
30893096
msgstr ""
30903097

3091-
#: shared-bindings/audiobusio/PDMIn.c
3098+
#: shared-bindings/audio_i2sin/I2SIn.c shared-bindings/audiobusio/PDMIn.c
30923099
msgid ""
30933100
"destination buffer must be a bytearray or array of type 'B' for bit_depth = 8"
30943101
msgstr ""
30953102

3096-
#: shared-bindings/audiobusio/PDMIn.c
3103+
#: shared-bindings/audio_i2sin/I2SIn.c shared-bindings/audiobusio/PDMIn.c
30973104
msgid "destination buffer must be an array of type 'H' for bit_depth = 16"
30983105
msgstr ""
30993106

3107+
#: shared-bindings/audio_i2sin/I2SIn.c
3108+
msgid ""
3109+
"destination buffer must be an array of type 'I' for bit_depth = 24 or 32"
3110+
msgstr ""
3111+
31003112
#: py/objdict.c
31013113
msgid "dict update sequence has wrong length"
31023114
msgstr ""

ports/espressif/boards/adafruit_sparkle_motion/pins.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ static const mp_rom_map_elem_t board_module_globals_table[] = {
4646
{ MP_ROM_QSTR(MP_QSTR_SIG4), MP_ROM_PTR(&pin_GPIO23) },
4747
{ MP_ROM_QSTR(MP_QSTR_D23), MP_ROM_PTR(&pin_GPIO23) },
4848

49+
{ MP_ROM_QSTR(MP_QSTR_D25), MP_ROM_PTR(&pin_GPIO25) },
50+
{ MP_ROM_QSTR(MP_QSTR_D26), MP_ROM_PTR(&pin_GPIO26) },
51+
{ MP_ROM_QSTR(MP_QSTR_D33), MP_ROM_PTR(&pin_GPIO33) },
52+
4953
{ MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO9) },
5054
{ MP_ROM_QSTR(MP_QSTR_D9), MP_ROM_PTR(&pin_GPIO9) },
5155

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include <string.h>
8+
9+
#include "bindings/espidf/__init__.h"
10+
11+
#include "common-hal/audio_i2sin/I2SIn.h"
12+
#include "py/runtime.h"
13+
#include "shared-bindings/audio_i2sin/I2SIn.h"
14+
#include "shared-bindings/microcontroller/Pin.h"
15+
16+
#include "driver/i2s_std.h"
17+
18+
#if CIRCUITPY_AUDIO_I2SIN
19+
20+
void common_hal_audio_i2sin_i2sin_construct(audio_i2sin_i2sin_obj_t *self,
21+
const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select,
22+
const mcu_pin_obj_t *data, const mcu_pin_obj_t *main_clock,
23+
uint32_t sample_rate, uint8_t bit_depth, bool mono, bool left_justified) {
24+
25+
if (bit_depth != 8 && bit_depth != 16 && bit_depth != 24 && bit_depth != 32) {
26+
mp_raise_ValueError(MP_ERROR_TEXT("bit_depth must be 8, 16, 24, or 32."));
27+
}
28+
29+
i2s_data_bit_width_t bit_width = (i2s_data_bit_width_t)bit_depth;
30+
31+
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
32+
esp_err_t err = i2s_new_channel(&chan_cfg, NULL, &self->rx_chan);
33+
if (err == ESP_ERR_NOT_FOUND) {
34+
mp_raise_RuntimeError(MP_ERROR_TEXT("Peripheral in use"));
35+
}
36+
CHECK_ESP_RESULT(err);
37+
38+
// Always configure the bus as stereo. The newer-family I2S peripherals
39+
// (S2/S3/C-series) ignore I2S_SLOT_MODE_MONO on RX and write both slots
40+
// into the DMA buffer regardless, which yields buffers that fill at 2x
41+
// the WS rate and produces half-speed audio. By configuring stereo and
42+
// dropping one slot ourselves in record_to_buffer, behavior is uniform
43+
// across chips.
44+
i2s_std_slot_config_t slot_cfg = left_justified
45+
? (i2s_std_slot_config_t)I2S_STD_MSB_SLOT_DEFAULT_CONFIG(bit_width, I2S_SLOT_MODE_STEREO)
46+
: (i2s_std_slot_config_t)I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bit_width, I2S_SLOT_MODE_STEREO);
47+
48+
i2s_std_config_t std_cfg = {
49+
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate),
50+
.slot_cfg = slot_cfg,
51+
.gpio_cfg = {
52+
.mclk = main_clock != NULL ? main_clock->number : I2S_GPIO_UNUSED,
53+
.bclk = bit_clock->number,
54+
.ws = word_select->number,
55+
.dout = I2S_GPIO_UNUSED,
56+
.din = data->number,
57+
},
58+
};
59+
CHECK_ESP_RESULT(i2s_channel_init_std_mode(self->rx_chan, &std_cfg));
60+
CHECK_ESP_RESULT(i2s_channel_enable(self->rx_chan));
61+
62+
self->bit_clock = bit_clock;
63+
self->word_select = word_select;
64+
self->data = data;
65+
self->mclk = main_clock;
66+
self->sample_rate = sample_rate;
67+
self->bit_depth = bit_depth;
68+
self->mono = mono;
69+
70+
claim_pin(bit_clock);
71+
claim_pin(word_select);
72+
claim_pin(data);
73+
if (main_clock) {
74+
claim_pin(main_clock);
75+
}
76+
}
77+
78+
bool common_hal_audio_i2sin_i2sin_deinited(audio_i2sin_i2sin_obj_t *self) {
79+
return self->data == NULL;
80+
}
81+
82+
void common_hal_audio_i2sin_i2sin_deinit(audio_i2sin_i2sin_obj_t *self) {
83+
if (common_hal_audio_i2sin_i2sin_deinited(self)) {
84+
return;
85+
}
86+
87+
if (self->rx_chan) {
88+
i2s_channel_disable(self->rx_chan);
89+
i2s_del_channel(self->rx_chan);
90+
self->rx_chan = NULL;
91+
}
92+
93+
if (self->bit_clock) {
94+
reset_pin_number(self->bit_clock->number);
95+
}
96+
self->bit_clock = NULL;
97+
98+
if (self->word_select) {
99+
reset_pin_number(self->word_select->number);
100+
}
101+
self->word_select = NULL;
102+
103+
if (self->data) {
104+
reset_pin_number(self->data->number);
105+
}
106+
self->data = NULL;
107+
108+
if (self->mclk) {
109+
reset_pin_number(self->mclk->number);
110+
}
111+
self->mclk = NULL;
112+
}
113+
114+
uint32_t common_hal_audio_i2sin_i2sin_record_to_buffer(audio_i2sin_i2sin_obj_t *self,
115+
void *buffer, uint32_t length) {
116+
size_t element_size = self->bit_depth / 8;
117+
// 24-bit samples occupy a 32-bit slot on the I2S bus.
118+
if (self->bit_depth == 24) {
119+
element_size = 4;
120+
}
121+
122+
if (!self->mono) {
123+
size_t result = 0;
124+
esp_err_t err = i2s_channel_read(self->rx_chan, buffer, length * element_size,
125+
&result, portMAX_DELAY);
126+
CHECK_ESP_RESULT(err);
127+
return result / element_size;
128+
}
129+
130+
// Mono: bus is configured stereo, so each WS frame yields two slots in
131+
// the DMA buffer. Read in chunks into a scratch and keep only the left
132+
// slot of each frame.
133+
uint8_t scratch[256];
134+
const size_t frame_bytes = 2 * element_size;
135+
const size_t scratch_frames = sizeof(scratch) / frame_bytes;
136+
uint8_t *out = (uint8_t *)buffer;
137+
uint32_t produced = 0;
138+
while (produced < length) {
139+
size_t want_frames = length - produced;
140+
if (want_frames > scratch_frames) {
141+
want_frames = scratch_frames;
142+
}
143+
size_t got_bytes = 0;
144+
esp_err_t err = i2s_channel_read(self->rx_chan, scratch,
145+
want_frames * frame_bytes, &got_bytes, portMAX_DELAY);
146+
CHECK_ESP_RESULT(err);
147+
size_t got_frames = got_bytes / frame_bytes;
148+
for (size_t i = 0; i < got_frames; i++) {
149+
memcpy(out + produced * element_size,
150+
scratch + i * frame_bytes,
151+
element_size);
152+
produced++;
153+
}
154+
if (got_frames < want_frames) {
155+
break;
156+
}
157+
}
158+
return produced;
159+
}
160+
161+
uint8_t common_hal_audio_i2sin_i2sin_get_bit_depth(audio_i2sin_i2sin_obj_t *self) {
162+
return self->bit_depth;
163+
}
164+
165+
uint32_t common_hal_audio_i2sin_i2sin_get_sample_rate(audio_i2sin_i2sin_obj_t *self) {
166+
return self->sample_rate;
167+
}
168+
169+
#endif // CIRCUITPY_AUDIO_I2SIN
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "py/obj.h"
10+
11+
#include "common-hal/microcontroller/Pin.h"
12+
13+
#include "driver/i2s_std.h"
14+
15+
#if CIRCUITPY_AUDIO_I2SIN
16+
17+
typedef struct {
18+
mp_obj_base_t base;
19+
i2s_chan_handle_t rx_chan;
20+
const mcu_pin_obj_t *bit_clock;
21+
const mcu_pin_obj_t *word_select;
22+
const mcu_pin_obj_t *data;
23+
const mcu_pin_obj_t *mclk;
24+
uint32_t sample_rate;
25+
uint8_t bit_depth;
26+
bool mono;
27+
} audio_i2sin_i2sin_obj_t;
28+
29+
#endif
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT

ports/espressif/mpconfigport.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ CIRCUITPY_ALARM_TOUCH ?= 0
6868
CIRCUITPY_ANALOGBUFIO ?= 1
6969
CIRCUITPY_AUDIOBUSIO ?= 1
7070
CIRCUITPY_AUDIOBUSIO_PDMIN ?= 0
71+
CIRCUITPY_AUDIO_I2SIN ?= 1
7172
CIRCUITPY_AUDIOIO ?= 1
7273
CIRCUITPY_BLEIO_HCI = 0
7374
CIRCUITPY_BLEIO_NATIVE ?= 1

ports/raspberrypi/bindings/rp2pio/StateMachine.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ bool common_hal_rp2pio_statemachine_background_read(rp2pio_statemachine_obj_t *s
6262
bool common_hal_rp2pio_statemachine_stop_background_write(rp2pio_statemachine_obj_t *self);
6363
bool common_hal_rp2pio_statemachine_stop_background_read(rp2pio_statemachine_obj_t *self);
6464

65+
// Set the once / loop / loop2 read buffers from raw pointers without going
66+
// through an mp_obj_t wrapper. Pass NULL/0 for unused slots. The caller owns
67+
// the memory; it must remain valid until stop_background_read.
68+
void common_hal_rp2pio_statemachine_set_read_buffers_raw(rp2pio_statemachine_obj_t *self,
69+
void *once, size_t once_len,
70+
void *loop, size_t loop_len,
71+
void *loop2, size_t loop2_len);
72+
73+
// Returns the DMA channel index used by the current background read, or -1
74+
// if no background read is active.
75+
int common_hal_rp2pio_statemachine_get_read_dma_channel(rp2pio_statemachine_obj_t *self);
76+
6577
mp_int_t common_hal_rp2pio_statemachine_get_pending_write(rp2pio_statemachine_obj_t *self);
6678
mp_int_t common_hal_rp2pio_statemachine_get_pending_read(rp2pio_statemachine_obj_t *self);
6779

0 commit comments

Comments
 (0)