forked from micropython/micropython
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
audio_i2sin module for espressif and raspberrypi #10990
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
FoamyGuy
wants to merge
12
commits into
adafruit:main
Choose a base branch
from
FoamyGuy:audio_i2sin
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
2236879
audio_i2sin module for espressif and raspberrypi
FoamyGuy da09138
format sdcard test
FoamyGuy 599ef3f
refactor to audioi2sin
FoamyGuy 510307c
add samples_signed argument
FoamyGuy 4bb0863
try to fix c6 builds. implement changes from review
FoamyGuy 4bfcd6c
disable audioi2sin for esp32c2
FoamyGuy 3728298
output_bit_depth argument
FoamyGuy cf41db1
output_bit_depth manual test
FoamyGuy e22dc0a
improve argument validation logic and error messages
FoamyGuy 6479ceb
update PIO programs
FoamyGuy 5282f62
mirror low bits. change error message in PDMIn
FoamyGuy 4ff09c1
make translate
FoamyGuy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| // This file is part of the CircuitPython project: https://circuitpython.org | ||
| // | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries | ||
| // | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| #include <string.h> | ||
|
|
||
| #include "bindings/espidf/__init__.h" | ||
|
|
||
| #include "common-hal/audioi2sin/I2SIn.h" | ||
| #include "py/runtime.h" | ||
| #include "shared-bindings/audioi2sin/I2SIn.h" | ||
| #include "shared-bindings/microcontroller/Pin.h" | ||
|
|
||
| #include "driver/i2s_std.h" | ||
|
|
||
| #if CIRCUITPY_AUDIOI2SIN | ||
|
|
||
| void common_hal_audioi2sin_i2sin_construct(audioi2sin_i2sin_obj_t *self, | ||
| const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, | ||
| const mcu_pin_obj_t *data, const mcu_pin_obj_t *main_clock, | ||
| uint32_t sample_rate, uint8_t bit_depth, bool mono, bool left_justified, | ||
| bool samples_signed) { | ||
|
|
||
| if (bit_depth != 8 && bit_depth != 16 && bit_depth != 24 && bit_depth != 32) { | ||
| mp_raise_ValueError(MP_ERROR_TEXT("bit_depth must be 8, 16, 24, or 32.")); | ||
|
FoamyGuy marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| i2s_data_bit_width_t bit_width = (i2s_data_bit_width_t)bit_depth; | ||
|
|
||
| i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); | ||
| esp_err_t err = i2s_new_channel(&chan_cfg, NULL, &self->rx_chan); | ||
| if (err == ESP_ERR_NOT_FOUND) { | ||
| mp_raise_RuntimeError(MP_ERROR_TEXT("Peripheral in use")); | ||
| } | ||
| CHECK_ESP_RESULT(err); | ||
|
|
||
| // Always configure the bus as stereo. The newer-family I2S peripherals | ||
| // (S2/S3/C-series) ignore I2S_SLOT_MODE_MONO on RX and write both slots | ||
| // into the DMA buffer regardless, which yields buffers that fill at 2x | ||
| // the WS rate and produces half-speed audio. By configuring stereo and | ||
| // dropping one slot ourselves in record_to_buffer, behavior is uniform | ||
| // across chips. | ||
| i2s_std_slot_config_t slot_cfg = left_justified | ||
| ? (i2s_std_slot_config_t)I2S_STD_MSB_SLOT_DEFAULT_CONFIG(bit_width, I2S_SLOT_MODE_STEREO) | ||
| : (i2s_std_slot_config_t)I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bit_width, I2S_SLOT_MODE_STEREO); | ||
|
|
||
| i2s_std_config_t std_cfg = { | ||
| .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate), | ||
| .slot_cfg = slot_cfg, | ||
| .gpio_cfg = { | ||
| .mclk = main_clock != NULL ? main_clock->number : I2S_GPIO_UNUSED, | ||
| .bclk = bit_clock->number, | ||
| .ws = word_select->number, | ||
| .dout = I2S_GPIO_UNUSED, | ||
| .din = data->number, | ||
| }, | ||
| }; | ||
| CHECK_ESP_RESULT(i2s_channel_init_std_mode(self->rx_chan, &std_cfg)); | ||
| CHECK_ESP_RESULT(i2s_channel_enable(self->rx_chan)); | ||
|
|
||
| self->bit_clock = bit_clock; | ||
| self->word_select = word_select; | ||
| self->data = data; | ||
| self->mclk = main_clock; | ||
| self->sample_rate = sample_rate; | ||
| self->bit_depth = bit_depth; | ||
| self->mono = mono; | ||
| self->samples_signed = samples_signed; | ||
|
|
||
| claim_pin(bit_clock); | ||
| claim_pin(word_select); | ||
| claim_pin(data); | ||
| if (main_clock) { | ||
| claim_pin(main_clock); | ||
| } | ||
| } | ||
|
|
||
| bool common_hal_audioi2sin_i2sin_deinited(audioi2sin_i2sin_obj_t *self) { | ||
| return self->data == NULL; | ||
| } | ||
|
|
||
| void common_hal_audioi2sin_i2sin_deinit(audioi2sin_i2sin_obj_t *self) { | ||
| if (common_hal_audioi2sin_i2sin_deinited(self)) { | ||
| return; | ||
| } | ||
|
|
||
| if (self->rx_chan) { | ||
| i2s_channel_disable(self->rx_chan); | ||
| i2s_del_channel(self->rx_chan); | ||
| self->rx_chan = NULL; | ||
| } | ||
|
|
||
| if (self->bit_clock) { | ||
| reset_pin_number(self->bit_clock->number); | ||
| } | ||
| self->bit_clock = NULL; | ||
|
|
||
| if (self->word_select) { | ||
| reset_pin_number(self->word_select->number); | ||
| } | ||
| self->word_select = NULL; | ||
|
|
||
| if (self->data) { | ||
| reset_pin_number(self->data->number); | ||
| } | ||
| self->data = NULL; | ||
|
|
||
| if (self->mclk) { | ||
| reset_pin_number(self->mclk->number); | ||
| } | ||
| self->mclk = NULL; | ||
| } | ||
|
|
||
| // I2S delivers signed PCM. When samples_signed is false, XOR each sample with | ||
| // the sign bit for its width to convert to unsigned PCM (WAV convention). | ||
| static void i2sin_convert_to_unsigned(void *buffer, uint32_t samples, | ||
|
FoamyGuy marked this conversation as resolved.
|
||
| uint8_t bit_depth, size_t element_size) { | ||
| if (bit_depth == 8) { | ||
| uint8_t *p = (uint8_t *)buffer; | ||
| for (uint32_t i = 0; i < samples; i++) { | ||
| p[i] ^= 0x80u; | ||
| } | ||
| } else if (bit_depth == 16) { | ||
| uint16_t *p = (uint16_t *)buffer; | ||
| for (uint32_t i = 0; i < samples; i++) { | ||
| p[i] ^= 0x8000u; | ||
| } | ||
| } else { | ||
| // 24- or 32-bit; both stored in 32-bit slots (element_size == 4). | ||
| (void)element_size; | ||
| uint32_t mask = (bit_depth == 24) ? 0x800000u : 0x80000000u; | ||
| uint32_t *p = (uint32_t *)buffer; | ||
| for (uint32_t i = 0; i < samples; i++) { | ||
| p[i] ^= mask; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| uint32_t common_hal_audioi2sin_i2sin_record_to_buffer(audioi2sin_i2sin_obj_t *self, | ||
| void *buffer, uint32_t length) { | ||
| size_t element_size = self->bit_depth / 8; | ||
| // 24-bit samples occupy a 32-bit slot on the I2S bus. | ||
| if (self->bit_depth == 24) { | ||
| element_size = 4; | ||
| } | ||
|
|
||
| uint32_t produced; | ||
| if (!self->mono) { | ||
| size_t result = 0; | ||
| esp_err_t err = i2s_channel_read(self->rx_chan, buffer, length * element_size, | ||
| &result, portMAX_DELAY); | ||
| CHECK_ESP_RESULT(err); | ||
| produced = result / element_size; | ||
| } else { | ||
| // Mono: bus is configured stereo, so each WS frame yields two slots in | ||
| // the DMA buffer. Read in chunks into a scratch and keep only the left | ||
| // slot of each frame. | ||
| uint8_t scratch[256]; | ||
| const size_t frame_bytes = 2 * element_size; | ||
| const size_t scratch_frames = sizeof(scratch) / frame_bytes; | ||
| uint8_t *out = (uint8_t *)buffer; | ||
| produced = 0; | ||
| while (produced < length) { | ||
| size_t want_frames = length - produced; | ||
| if (want_frames > scratch_frames) { | ||
| want_frames = scratch_frames; | ||
| } | ||
| size_t got_bytes = 0; | ||
| esp_err_t err = i2s_channel_read(self->rx_chan, scratch, | ||
| want_frames * frame_bytes, &got_bytes, portMAX_DELAY); | ||
| CHECK_ESP_RESULT(err); | ||
| size_t got_frames = got_bytes / frame_bytes; | ||
| for (size_t i = 0; i < got_frames; i++) { | ||
| memcpy(out + produced * element_size, | ||
| scratch + i * frame_bytes, | ||
| element_size); | ||
| produced++; | ||
| } | ||
| if (got_frames < want_frames) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (!self->samples_signed && produced > 0) { | ||
| i2sin_convert_to_unsigned(buffer, produced, self->bit_depth, element_size); | ||
| } | ||
| return produced; | ||
| } | ||
|
|
||
| uint8_t common_hal_audioi2sin_i2sin_get_bit_depth(audioi2sin_i2sin_obj_t *self) { | ||
| return self->bit_depth; | ||
| } | ||
|
|
||
| uint32_t common_hal_audioi2sin_i2sin_get_sample_rate(audioi2sin_i2sin_obj_t *self) { | ||
| return self->sample_rate; | ||
| } | ||
|
|
||
| bool common_hal_audioi2sin_i2sin_get_samples_signed(audioi2sin_i2sin_obj_t *self) { | ||
| return self->samples_signed; | ||
| } | ||
|
|
||
| #endif // CIRCUITPY_AUDIOI2SIN | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // This file is part of the CircuitPython project: https://circuitpython.org | ||
| // | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries | ||
| // | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "py/obj.h" | ||
|
|
||
| #include "common-hal/microcontroller/Pin.h" | ||
|
|
||
| #include "driver/i2s_std.h" | ||
|
|
||
| #if CIRCUITPY_AUDIOI2SIN | ||
|
|
||
| typedef struct { | ||
| mp_obj_base_t base; | ||
| i2s_chan_handle_t rx_chan; | ||
| const mcu_pin_obj_t *bit_clock; | ||
| const mcu_pin_obj_t *word_select; | ||
| const mcu_pin_obj_t *data; | ||
| const mcu_pin_obj_t *mclk; | ||
| uint32_t sample_rate; | ||
| uint8_t bit_depth; | ||
| bool mono; | ||
| bool samples_signed; | ||
| } audioi2sin_i2sin_obj_t; | ||
|
|
||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // This file is part of the CircuitPython project: https://circuitpython.org | ||
| // | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries | ||
| // | ||
| // SPDX-License-Identifier: MIT |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.