diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 6beb320527e43..c9f73023119cd 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -211,6 +211,7 @@ msgstr "" msgid "%q must be array of type 'h'" msgstr "" +#: shared-bindings/audiobusio/I2S.c shared-bindings/audiobusio/I2SIn.c #: shared-bindings/audiobusio/PDMIn.c msgid "%q must be multiple of 8." msgstr "" @@ -641,6 +642,8 @@ msgstr "" msgid "Below minimum frame rate" msgstr "" +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +#: ports/raspberrypi/common-hal/audiobusio/I2SIn.c #: ports/raspberrypi/common-hal/audiobusio/I2SOut.c msgid "Bit clock and word select must be sequential GPIO pins" msgstr "" @@ -649,6 +652,10 @@ msgstr "" msgid "Bitmap size and bits per value must match" msgstr "" +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +msgid "Bits per sample cannot be greater than input." +msgstr "" + #: supervisor/shared/safe_mode.c msgid "Boot device must be first (interface #0)." msgstr "" @@ -1488,6 +1495,8 @@ msgstr "" #: ports/atmel-samd/common-hal/audiobusio/I2SOut.c #: ports/atmel-samd/common-hal/audioio/AudioOut.c +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +#: ports/raspberrypi/common-hal/audiobusio/I2SIn.c #: ports/raspberrypi/common-hal/audiobusio/I2SOut.c #: ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c msgid "No DMA channel found" @@ -1520,6 +1529,14 @@ msgstr "" msgid "No connection: length cannot be determined" msgstr "" +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +msgid "No data in" +msgstr "" + +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +msgid "No data out" +msgstr "" + #: shared-bindings/board/__init__.c msgid "No default %q bus" msgstr "" @@ -1603,8 +1620,8 @@ msgstr "" msgid "Not connected" msgstr "" -#: shared-bindings/audiobusio/I2SOut.c shared-bindings/audioio/AudioOut.c -#: shared-bindings/audiopwmio/PWMAudioOut.c +#: shared-bindings/audiobusio/I2S.c shared-bindings/audiobusio/I2SOut.c +#: shared-bindings/audioio/AudioOut.c shared-bindings/audiopwmio/PWMAudioOut.c msgid "Not playing" msgstr "" @@ -1959,6 +1976,10 @@ msgstr "" msgid "SPI re-init" msgstr "" +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +msgid "Sample rate must match." +msgstr "" + #: shared-bindings/is31fl3741/FrameBuffer.c msgid "Scale dimensions must divide by 3" msgstr "" @@ -1977,6 +1998,10 @@ msgstr "" msgid "Server side context cannot have hostname" msgstr "" +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +msgid "Single channel output not supported." +msgstr "" + #: ports/cxd56/common-hal/camera/Camera.c msgid "Size not supported" msgstr "" @@ -2082,6 +2107,7 @@ msgstr "" msgid "Too many channels in sample" msgstr "" +#: ports/raspberrypi/common-hal/audiobusio/I2S.c #: ports/raspberrypi/common-hal/audiobusio/I2SOut.c msgid "Too many channels in sample." msgstr "" @@ -2171,6 +2197,8 @@ msgstr "" #: ports/atmel-samd/common-hal/audiobusio/I2SOut.c #: ports/atmel-samd/common-hal/audioio/AudioOut.c +#: ports/raspberrypi/common-hal/audiobusio/I2S.c +#: ports/raspberrypi/common-hal/audiobusio/I2SIn.c #: ports/raspberrypi/common-hal/audiobusio/I2SOut.c #: ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c msgid "Unable to allocate buffers for signed conversion" diff --git a/ports/atmel-samd/common-hal/audiobusio/I2S.c b/ports/atmel-samd/common-hal/audiobusio/I2S.c new file mode 100644 index 0000000000000..587e33852bcac --- /dev/null +++ b/ports/atmel-samd/common-hal/audiobusio/I2S.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/atmel-samd/common-hal/audiobusio/I2S.h b/ports/atmel-samd/common-hal/audiobusio/I2S.h new file mode 100644 index 0000000000000..53e15e22f2820 --- /dev/null +++ b/ports/atmel-samd/common-hal/audiobusio/I2S.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/atmel-samd/common-hal/audiobusio/I2SIn.c b/ports/atmel-samd/common-hal/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..1dd80b347c86a --- /dev/null +++ b/ports/atmel-samd/common-hal/audiobusio/I2SIn.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/atmel-samd/common-hal/audiobusio/I2SIn.h b/ports/atmel-samd/common-hal/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..bb12b78e6f4a7 --- /dev/null +++ b/ports/atmel-samd/common-hal/audiobusio/I2SIn.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/espressif/common-hal/audiobusio/I2S.c b/ports/espressif/common-hal/audiobusio/I2S.c new file mode 100644 index 0000000000000..587e33852bcac --- /dev/null +++ b/ports/espressif/common-hal/audiobusio/I2S.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/espressif/common-hal/audiobusio/I2S.h b/ports/espressif/common-hal/audiobusio/I2S.h new file mode 100644 index 0000000000000..53e15e22f2820 --- /dev/null +++ b/ports/espressif/common-hal/audiobusio/I2S.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/espressif/common-hal/audiobusio/I2SIn.c b/ports/espressif/common-hal/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..1dd80b347c86a --- /dev/null +++ b/ports/espressif/common-hal/audiobusio/I2SIn.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/espressif/common-hal/audiobusio/I2SIn.h b/ports/espressif/common-hal/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..bb12b78e6f4a7 --- /dev/null +++ b/ports/espressif/common-hal/audiobusio/I2SIn.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/mimxrt10xx/common-hal/audiobusio/I2S.c b/ports/mimxrt10xx/common-hal/audiobusio/I2S.c new file mode 100644 index 0000000000000..587e33852bcac --- /dev/null +++ b/ports/mimxrt10xx/common-hal/audiobusio/I2S.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/mimxrt10xx/common-hal/audiobusio/I2S.h b/ports/mimxrt10xx/common-hal/audiobusio/I2S.h new file mode 100644 index 0000000000000..53e15e22f2820 --- /dev/null +++ b/ports/mimxrt10xx/common-hal/audiobusio/I2S.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/mimxrt10xx/common-hal/audiobusio/I2SIn.c b/ports/mimxrt10xx/common-hal/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..1dd80b347c86a --- /dev/null +++ b/ports/mimxrt10xx/common-hal/audiobusio/I2SIn.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/mimxrt10xx/common-hal/audiobusio/I2SIn.h b/ports/mimxrt10xx/common-hal/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..bb12b78e6f4a7 --- /dev/null +++ b/ports/mimxrt10xx/common-hal/audiobusio/I2SIn.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/nordic/common-hal/audiobusio/I2S.c b/ports/nordic/common-hal/audiobusio/I2S.c new file mode 100644 index 0000000000000..587e33852bcac --- /dev/null +++ b/ports/nordic/common-hal/audiobusio/I2S.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/nordic/common-hal/audiobusio/I2S.h b/ports/nordic/common-hal/audiobusio/I2S.h new file mode 100644 index 0000000000000..53e15e22f2820 --- /dev/null +++ b/ports/nordic/common-hal/audiobusio/I2S.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2 is not enabled on this family, this file is still required for the build to pass diff --git a/ports/nordic/common-hal/audiobusio/I2SIn.c b/ports/nordic/common-hal/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..1dd80b347c86a --- /dev/null +++ b/ports/nordic/common-hal/audiobusio/I2SIn.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/nordic/common-hal/audiobusio/I2SIn.h b/ports/nordic/common-hal/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..bb12b78e6f4a7 --- /dev/null +++ b/ports/nordic/common-hal/audiobusio/I2SIn.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2In is not enabled on this family, this file is still required for the build to pass diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index ae3997a128f8e..bd59850ad0a00 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -23,11 +23,11 @@ void audio_dma_reset(void) { for (size_t channel = 0; channel < NUM_DMA_CHANNELS; channel++) { - if (MP_STATE_PORT(playing_audio)[channel] == NULL) { - continue; + if (MP_STATE_PORT(playing_audio)[channel] != NULL) { + audio_dma_stop_output(MP_STATE_PORT(playing_audio)[channel]); + } else if (MP_STATE_PORT(recording_audio)[channel] != NULL) { + audio_dma_stop_input(MP_STATE_PORT(recording_audio)[channel]); } - - audio_dma_stop(MP_STATE_PORT(playing_audio)[channel]); } } @@ -118,8 +118,12 @@ static size_t audio_dma_convert_samples(audio_dma_t *dma, uint8_t *input, uint32 // buffer_idx is 0 or 1. static void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) { + if (!dma->output_register_address) { + return; + } + assert(dma->channel[buffer_idx] < NUM_DMA_CHANNELS); - size_t dma_channel = dma->channel[buffer_idx]; + size_t dma_channel = dma->output_channel[buffer_idx]; audioio_get_buffer_result_t get_buffer_result; uint8_t *sample_buffer; @@ -128,7 +132,7 @@ static void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) { dma->single_channel_output, dma->audio_channel, &sample_buffer, &sample_buffer_length); if (get_buffer_result == GET_BUFFER_ERROR) { - audio_dma_stop(dma); + audio_dma_stop_output(dma); dma->dma_result = AUDIO_DMA_SOURCE_ERROR; return; } @@ -139,9 +143,9 @@ static void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) { size_t output_length_used = audio_dma_convert_samples( dma, sample_buffer, sample_buffer_length, - dma->buffer[buffer_idx], dma->buffer_length[buffer_idx]); + dma->output_buffer[buffer_idx], dma->output_buffer_length[buffer_idx]); - dma_channel_set_read_addr(dma_channel, dma->buffer[buffer_idx], false /* trigger */); + dma_channel_set_read_addr(dma_channel, dma->output_buffer[buffer_idx], false /* trigger */); dma_channel_set_trans_count(dma_channel, output_length_used / dma->output_size, false /* trigger */); if (get_buffer_result == GET_BUFFER_DONE) { @@ -155,10 +159,10 @@ static void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) { (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); if (output_length_used == 0 && - !dma_channel_is_busy(dma->channel[0]) && - !dma_channel_is_busy(dma->channel[1])) { + !dma_channel_is_busy(dma->output_channel[0]) && + !dma_channel_is_busy(dma->output_channel[1])) { // No data has been read, and both DMA channels have now finished, so it's safe to stop. - audio_dma_stop(dma); + audio_dma_stop_output(dma); dma->dma_result = AUDIO_DMA_OK; return; } @@ -171,8 +175,8 @@ static void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) { dma->dma_result = AUDIO_DMA_OK; } -// Playback should be shutdown before calling this. -audio_dma_result audio_dma_setup_playback( +// Playback and recording should be shutdown before calling this. +audio_dma_result audio_dma_setup( audio_dma_t *dma, mp_obj_t sample, bool loop, @@ -181,24 +185,57 @@ audio_dma_result audio_dma_setup_playback( bool output_signed, uint8_t output_resolution, uint32_t output_register_address, - uint8_t dma_trigger_source, + uint8_t output_dma_trigger_source, + uint32_t input_register_address, + uint8_t input_dma_trigger_source, bool swap_channel) { - // Use two DMA channels to play because the DMA can't wrap to itself without the - // buffer being power of two aligned. - int dma_channel_0_maybe = dma_claim_unused_channel(false); - if (dma_channel_0_maybe < 0) { - return AUDIO_DMA_DMA_BUSY; - } + int output_dma_channel_0_maybe = -1; + int output_dma_channel_1_maybe = -1; + + if (output_register_address) { + // Use two DMA channels to play because the DMA can't wrap to itself without the + // buffer being power of two aligned. + output_dma_channel_0_maybe = dma_claim_unused_channel(false); + if (output_dma_channel_0_maybe < 0) { + return AUDIO_DMA_DMA_BUSY; + } + + output_dma_channel_1_maybe = dma_claim_unused_channel(false); + if (output_dma_channel_1_maybe < 0) { + dma_channel_unclaim((uint)output_dma_channel_0_maybe); + return AUDIO_DMA_DMA_BUSY; + } - int dma_channel_1_maybe = dma_claim_unused_channel(false); - if (dma_channel_1_maybe < 0) { - dma_channel_unclaim((uint)dma_channel_0_maybe); - return AUDIO_DMA_DMA_BUSY; + dma->output_channel[0] = (uint8_t)output_dma_channel_0_maybe; + dma->output_channel[1] = (uint8_t)output_dma_channel_1_maybe; } - dma->channel[0] = (uint8_t)dma_channel_0_maybe; - dma->channel[1] = (uint8_t)dma_channel_1_maybe; + if (input_register_address) { + // Use two DMA channels to record because the DMA can't wrap to itself without the + // buffer being power of two aligned. + int input_dma_channel_0_maybe = dma_claim_unused_channel(false); + if (input_dma_channel_0_maybe < 0) { + if (output_register_address) { + dma_channel_unclaim((uint)output_dma_channel_0_maybe); + dma_channel_unclaim((uint)output_dma_channel_1_maybe); + } + return AUDIO_DMA_DMA_BUSY; + } + + int input_dma_channel_1_maybe = dma_claim_unused_channel(false); + if (input_dma_channel_1_maybe < 0) { + if (output_register_address) { + dma_channel_unclaim((uint)output_dma_channel_0_maybe); + dma_channel_unclaim((uint)output_dma_channel_1_maybe); + } + dma_channel_unclaim((uint)input_dma_channel_0_maybe); + return AUDIO_DMA_DMA_BUSY; + } + + dma->input_channel[0] = (uint8_t)input_dma_channel_0_maybe; + dma->input_channel[1] = (uint8_t)input_dma_channel_1_maybe; + } dma->sample = sample; dma->loop = loop; @@ -211,9 +248,12 @@ audio_dma_result audio_dma_setup_playback( dma->output_resolution = output_resolution; dma->sample_resolution = audiosample_get_bits_per_sample(sample); dma->output_register_address = output_register_address; + dma->input_register_address = input_register_address; dma->swap_channel = swap_channel; - audiosample_reset_buffer(sample, single_channel_output, audio_channel); + if (output_register_address) { + audiosample_reset_buffer(sample, single_channel_output, audio_channel); + } bool single_buffer; // True if data fits in one single buffer. @@ -233,36 +273,72 @@ audio_dma_result audio_dma_setup_playback( max_buffer_length /= dma->sample_spacing; } - #ifdef PICO_RP2350 - dma->buffer[0] = (uint8_t *)port_realloc(dma->buffer[0], max_buffer_length, true); - #else - dma->buffer[0] = (uint8_t *)m_realloc(dma->buffer[0], - #if MICROPY_MALLOC_USES_ALLOCATED_SIZE - dma->buffer_length[0], // Old size + if (output_register_address) { + #ifdef PICO_RP2350 + dma->output_buffer[0] = (uint8_t *)port_realloc(dma->output_buffer[0], max_buffer_length, true); + #else + dma->output_buffer[0] = (uint8_t *)m_realloc(dma->output_buffer[0], + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + dma->output_buffer_length[0], // Old size + #endif + max_buffer_length); #endif - max_buffer_length); - #endif - dma->buffer_length[0] = max_buffer_length; + dma->output_buffer_length[0] = max_buffer_length; + + if (dma->output_buffer[0] == NULL) { + return AUDIO_DMA_MEMORY_ERROR; + } - if (dma->buffer[0] == NULL) { - return AUDIO_DMA_MEMORY_ERROR; + if (!single_buffer) { + #ifdef PICO_RP2350 + dma->output_buffer[1] = (uint8_t *)port_realloc(dma->output_buffer[1], max_buffer_length, true); + #else + dma->output_buffer[1] = (uint8_t *)m_realloc(dma->output_buffer[1], + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + dma->output_buffer_length[1], // Old size + #endif + max_buffer_length); + #endif + dma->output_buffer_length[1] = max_buffer_length; + + if (dma->output_buffer[1] == NULL) { + return AUDIO_DMA_MEMORY_ERROR; + } + } } - if (!single_buffer) { + if (input_register_address) { #ifdef PICO_RP2350 - dma->buffer[1] = (uint8_t *)port_realloc(dma->buffer[1], max_buffer_length, true); + dma->input_buffer[0] = (uint8_t *)port_realloc(dma->input_buffer[0], max_buffer_length, true); #else - dma->buffer[1] = (uint8_t *)m_realloc(dma->buffer[1], + dma->input_buffer[0] = (uint8_t *)m_realloc(dma->input_buffer[0], #if MICROPY_MALLOC_USES_ALLOCATED_SIZE - dma->buffer_length[1], // Old size + dma->input_buffer_length[0], // Old size #endif - max_buffer_length); + max_buffer_length); #endif - dma->buffer_length[1] = max_buffer_length; - - if (dma->buffer[1] == NULL) { + dma->input_buffer_length[0] = max_buffer_length; + + if (dma->input_buffer[0] == NULL) { return AUDIO_DMA_MEMORY_ERROR; } + + if (!single_buffer) { + #ifdef PICO_RP2350 + dma->input_buffer[1] = (uint8_t *)port_realloc(dma->input_buffer[1], max_buffer_length, true); + #else + dma->input_buffer[1] = (uint8_t *)m_realloc(dma->input_buffer[1], + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + dma->input_buffer_length[1], // Old size + #endif + max_buffer_length); + #endif + dma->input_buffer_length[1] = max_buffer_length; + + if (dma->input_buffer[1] == NULL) { + return AUDIO_DMA_MEMORY_ERROR; + } + } } dma->signed_to_unsigned = !output_signed && samples_signed; @@ -285,80 +361,176 @@ audio_dma_result audio_dma_setup_playback( } for (size_t i = 0; i < 2; i++) { - dma_channel_config c = dma_channel_get_default_config(dma->channel[i]); - channel_config_set_transfer_data_size(&c, dma_size); - channel_config_set_dreq(&c, dma_trigger_source); - channel_config_set_read_increment(&c, true); - channel_config_set_write_increment(&c, false); - - // Chain to the other channel by default. - channel_config_set_chain_to(&c, dma->channel[(i + 1) % 2]); - dma_channel_set_config(dma->channel[i], &c, false /* trigger */); + dma_channel_config c; + if (output_register_address) { + c = dma_channel_get_default_config(dma->output_channel[i]); + channel_config_set_transfer_data_size(&c, dma_size); + channel_config_set_dreq(&c, output_dma_trigger_source); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + + // Chain to the other channel by default. + channel_config_set_chain_to(&c, dma->output_channel[(i + 1) % 2]); + dma_channel_set_config(dma->output_channel[i], &c, false /* trigger */); + + dma_channel_set_write_addr(dma->output_channel[i], (void *)output_register_address, false /* trigger */); + } + if (input_register_address) { + c = dma_channel_get_default_config(dma->input_channel[i]); + channel_config_set_transfer_data_size(&c, dma_size); + channel_config_set_dreq(&c, input_dma_trigger_source); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + + // Chain to the other channel by default. + channel_config_set_chain_to(&c, dma->input_channel[(i + 1) % 2]); + dma_channel_set_config(dma->input_channel[i], &c, false /* trigger */); + + dma_channel_set_read_addr(dma->input_channel[i], (void *)input_register_address, false /* trigger */); + dma_channel_set_write_addr(dma->input_channel[i], dma->input_buffer[i], false /* trigger */); + dma_channel_set_trans_count(dma->input_channel[i], dma->input_buffer_length[i] / dma->output_size, false /* trigger */); + } - dma_channel_set_write_addr(dma->channel[i], (void *)output_register_address, false /* trigger */); } - // We keep the audio_dma_t for internal use and the sample as a root pointer because it - // contains the audiodma structure. - MP_STATE_PORT(playing_audio)[dma->channel[0]] = dma; - MP_STATE_PORT(playing_audio)[dma->channel[1]] = dma; - - dma->paused = false; + // Start input before output to allow the first two blocks to load. + if (input_register_address) { + // We keep the audio_dma_t for internal use and the sample as a root pointer because it + // contains the audiodma structure. + MP_STATE_PORT(recording_audio)[dma->input_channel[0]] = dma; + MP_STATE_PORT(recording_audio)[dma->input_channel[1]] = dma; + + // Special case the DMA for a single buffer. + if (single_buffer) { + dma_channel_config c = dma_channel_get_default_config(dma->input_channel[1]); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + channel_config_set_dreq(&c, 0x3f); // dma as fast as possible + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, false); + channel_config_set_chain_to(&c, dma->input_channel[1]); // Chain to ourselves so we stop. + dma_channel_configure(dma->input_channel[1], &c, + &dma->input_buffer[0], // write address + &dma_hw->ch[dma->input_channel[0]].al2_write_addr_trig, // read address + 1, // transaction count + false); // trigger + } else { + // Clear any latent interrupts so that we don't immediately disable channels. + dma_hw->ints0 |= (1 << dma->input_channel[0]) | (1 << dma->input_channel[1]); + // Enable our DMA channels on DMA_IRQ_0 to the CPU. + dma_hw->inte0 |= (1 << dma->input_channel[0]) | (1 << dma->input_channel[1]); + irq_set_mask_enabled(1 << DMA_IRQ_0, true); + } - // Load the first two blocks up front. - audio_dma_load_next_block(dma, 0); - if (dma->dma_result != AUDIO_DMA_OK) { - return dma->dma_result; + dma->input_index = -1; + dma->recording_in_progress = true; + dma_channel_start(dma->input_channel[0]); } - if (!single_buffer) { - audio_dma_load_next_block(dma, 1); + + if (output_register_address) { + // We keep the audio_dma_t for internal use and the sample as a root pointer because it + // contains the audiodma structure. + MP_STATE_PORT(playing_audio)[dma->output_channel[0]] = dma; + MP_STATE_PORT(playing_audio)[dma->output_channel[1]] = dma; + + dma->paused = false; + + // Load the first two blocks up front. + audio_dma_load_next_block(dma, 0); if (dma->dma_result != AUDIO_DMA_OK) { return dma->dma_result; } - } + if (!single_buffer) { + audio_dma_load_next_block(dma, 1); + if (dma->dma_result != AUDIO_DMA_OK) { + return dma->dma_result; + } + } - // Special case the DMA for a single buffer. It's commonly used for a single wave length of sound - // and may be short. Therefore, we use DMA chaining to loop quickly without involving interrupts. - // On the RP2040 we chain by having a second DMA writing to the config registers of the first. - // Read and write addresses change with DMA so we need to reset the read address back to the - // start of the sample. - if (single_buffer) { - dma_channel_config c = dma_channel_get_default_config(dma->channel[1]); - channel_config_set_transfer_data_size(&c, DMA_SIZE_32); - channel_config_set_dreq(&c, 0x3f); // dma as fast as possible - channel_config_set_read_increment(&c, false); - channel_config_set_write_increment(&c, false); - channel_config_set_chain_to(&c, dma->channel[1]); // Chain to ourselves so we stop. - dma_channel_configure(dma->channel[1], &c, - &dma_hw->ch[dma->channel[0]].al3_read_addr_trig, // write address - &dma->buffer[0], // read address - 1, // transaction count - false); // trigger - } else { - // Clear any latent interrupts so that we don't immediately disable channels. - dma_hw->ints0 |= (1 << dma->channel[0]) | (1 << dma->channel[1]); - // Enable our DMA channels on DMA_IRQ_0 to the CPU. This will wake us up when - // we're WFI. - dma_hw->inte0 |= (1 << dma->channel[0]) | (1 << dma->channel[1]); - irq_set_mask_enabled(1 << DMA_IRQ_0, true); - } + // Special case the DMA for a single buffer. It's commonly used for a single wave length of sound + // and may be short. Therefore, we use DMA chaining to loop quickly without involving interrupts. + // On the RP2040 we chain by having a second DMA writing to the config registers of the first. + // Read and write addresses change with DMA so we need to reset the read address back to the + // start of the sample. + if (single_buffer) { + dma_channel_config c = dma_channel_get_default_config(dma->output_channel[1]); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + channel_config_set_dreq(&c, 0x3f); // dma as fast as possible + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, false); + channel_config_set_chain_to(&c, dma->output_channel[1]); // Chain to ourselves so we stop. + dma_channel_configure(dma->output_channel[1], &c, + &dma_hw->ch[dma->output_channel[0]].al3_read_addr_trig, // write address + &dma->output_buffer[0], // read address + 1, // transaction count + false); // trigger + } else { + // Clear any latent interrupts so that we don't immediately disable channels. + dma_hw->ints0 |= (1 << dma->output_channel[0]) | (1 << dma->output_channel[1]); + // Enable our DMA channels on DMA_IRQ_0 to the CPU. This will wake us up when + // we're WFI. + dma_hw->inte0 |= (1 << dma->output_channel[0]) | (1 << dma->output_channel[1]); + irq_set_mask_enabled(1 << DMA_IRQ_0, true); + } - dma->playing_in_progress = true; - dma_channel_start(dma->channel[0]); + dma->playing_in_progress = true; + dma_channel_start(dma->output_channel[0]); + } return AUDIO_DMA_OK; } -void audio_dma_stop(audio_dma_t *dma) { +// Playback should be shutdown before calling this. +audio_dma_result audio_dma_setup_playback( + audio_dma_t *dma, + mp_obj_t sample, + bool loop, + bool single_channel_output, + uint8_t audio_channel, + bool output_signed, + uint8_t output_resolution, + uint32_t output_register_address, + uint8_t dma_trigger_source, + bool swap_channel) { + return audio_dma_setup(dma, sample, + loop, single_channel_output, audio_channel, + output_signed, output_resolution, + output_register_address, dma_trigger_source, + 0, 0, + swap_channel + ); +} + +// Recording should be shutdown before calling this. +audio_dma_result audio_dma_setup_record( + audio_dma_t *dma, + mp_obj_t sample, + bool loop, + bool single_channel_output, + uint8_t audio_channel, + bool output_signed, + uint8_t output_resolution, + uint32_t input_register_address, + uint8_t dma_trigger_source, + bool swap_channel) { + return audio_dma_setup(dma, sample, + loop, single_channel_output, audio_channel, + output_signed, output_resolution, + 0, 0, + input_register_address, dma_trigger_source, + swap_channel + ); +} + +void audio_dma_stop_output(audio_dma_t *dma) { dma->paused = true; // Disable our interrupts. uint32_t channel_mask = 0; - if (dma->channel[0] < NUM_DMA_CHANNELS) { - channel_mask |= 1 << dma->channel[0]; + if (dma->output_channel[0] < NUM_DMA_CHANNELS) { + channel_mask |= 1 << dma->output_channel[0]; } - if (dma->channel[1] < NUM_DMA_CHANNELS) { - channel_mask |= 1 << dma->channel[1]; + if (dma->output_channel[1] < NUM_DMA_CHANNELS) { + channel_mask |= 1 << dma->output_channel[1]; } dma_hw->inte0 &= ~channel_mask; if (!dma_hw->inte0) { @@ -370,8 +542,8 @@ void audio_dma_stop(audio_dma_t *dma) { RUN_BACKGROUND_TASKS; for (size_t i = 0; i < 2; i++) { - size_t channel = dma->channel[i]; - dma->channel[i] = NUM_DMA_CHANNELS; + size_t channel = dma->output_channel[i]; + dma->output_channel[i] = NUM_DMA_CHANNELS; if (channel == NUM_DMA_CHANNELS) { // Channel not in use. continue; @@ -396,38 +568,109 @@ void audio_dma_stop(audio_dma_t *dma) { // Hold onto our buffers. } +void audio_dma_stop_input(audio_dma_t *dma) { + // Disable our interrupts. + uint32_t channel_mask = 0; + if (dma->input_channel[0] < NUM_DMA_CHANNELS) { + channel_mask |= 1 << dma->input_channel[0]; + } + if (dma->input_channel[1] < NUM_DMA_CHANNELS) { + channel_mask |= 1 << dma->input_channel[1]; + } + dma_hw->inte0 &= ~channel_mask; + if (!dma_hw->inte0) { + irq_set_mask_enabled(1 << DMA_IRQ_0, false); + } + + // Run any remaining audio tasks because we remove ourselves from + // playing_audio. + RUN_BACKGROUND_TASKS; + + for (size_t i = 0; i < 2; i++) { + size_t channel = dma->input_channel[i]; + dma->input_channel[i] = NUM_DMA_CHANNELS; + if (channel == NUM_DMA_CHANNELS) { + // Channel not in use. + continue; + } + + dma_channel_config c = dma_channel_get_default_config(channel); + channel_config_set_enable(&c, false); + dma_channel_set_config(channel, &c, false /* trigger */); + + if (dma_channel_is_busy(channel)) { + dma_channel_abort(channel); + } + + dma_channel_set_read_addr(channel, NULL, false /* trigger */); + dma_channel_set_write_addr(channel, NULL, false /* trigger */); + dma_channel_set_trans_count(channel, 0, false /* trigger */); + dma_channel_unclaim(channel); + MP_STATE_PORT(playing_audio)[channel] = NULL; + } + dma->recording_in_progress = false; + + // Hold onto our buffers. +} + +void audio_dma_stop(audio_dma_t *dma) { + if (dma->output_register_address) { + audio_dma_stop_output(dma); + } + if (dma->input_register_address) { + audio_dma_stop_input(dma); + } +} + // To pause we simply stop the DMA. It is the responsibility of the output peripheral // to hold the previous value. void audio_dma_pause(audio_dma_t *dma) { - dma_hw->ch[dma->channel[0]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS; - dma_hw->ch[dma->channel[1]].al1_ctrl &= ~DMA_CH1_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->output_channel[0]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->output_channel[1]].al1_ctrl &= ~DMA_CH1_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->input_channel[0]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->input_channel[1]].al1_ctrl &= ~DMA_CH1_CTRL_TRIG_EN_BITS; dma->paused = true; } void audio_dma_resume(audio_dma_t *dma) { // Always re-enable the non-busy channel first so it's ready to continue when the busy channel // finishes and chains to it. (An interrupt could make the time between enables long.) - if (dma_channel_is_busy(dma->channel[0])) { - dma_hw->ch[dma->channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS; - dma_hw->ch[dma->channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; + if (dma_channel_is_busy(dma->output_channel[0])) { + dma_hw->ch[dma->output_channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->output_channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; } else { - dma_hw->ch[dma->channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; - dma_hw->ch[dma->channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->output_channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->output_channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS; + } + if (dma_channel_is_busy(dma->input_channel[0])) { + dma_hw->ch[dma->input_channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->input_channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; + } else { + dma_hw->ch[dma->input_channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS; + dma_hw->ch[dma->input_channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS; } dma->paused = false; } bool audio_dma_get_paused(audio_dma_t *dma) { - if (dma->channel[0] >= NUM_DMA_CHANNELS) { + uint32_t channel = NUM_DMA_CHANNELS; + if (dma->output_channel[0] < NUM_DMA_CHANNELS) { + channel = dma->output_channel[0]; + } else if (dma->input_channel[0] < NUM_DMA_CHANNELS) { + channel = dma->input_channel[0]; + } else { return false; } - return dma->playing_in_progress && dma->paused; + return (dma->playing_in_progress || dma->recording_in_progress) && dma->paused; } uint32_t audio_dma_pause_all(void) { uint32_t result = 0; for (size_t channel = 0; channel < NUM_DMA_CHANNELS; channel++) { audio_dma_t *dma = MP_STATE_PORT(playing_audio)[channel]; + if (dma == NULL) { + dma = MP_STATE_PORT(recording_audio)[channel]; + } if (dma != NULL && !audio_dma_get_paused(dma)) { audio_dma_pause(dma); result |= (1 << channel); @@ -439,6 +682,9 @@ uint32_t audio_dma_pause_all(void) { void audio_dma_unpause_mask(uint32_t channel_mask) { for (size_t channel = 0; channel < NUM_DMA_CHANNELS; channel++) { audio_dma_t *dma = MP_STATE_PORT(playing_audio)[channel]; + if (dma == NULL) { + dma = MP_STATE_PORT(recording_audio)[channel]; + } if (dma != NULL && (channel_mask & (1 << channel))) { audio_dma_resume(dma); } @@ -446,52 +692,100 @@ void audio_dma_unpause_mask(uint32_t channel_mask) { } void audio_dma_init(audio_dma_t *dma) { - dma->buffer[0] = NULL; - dma->buffer[1] = NULL; - - dma->buffer_length[0] = 0; - dma->buffer_length[1] = 0; - - dma->channel[0] = NUM_DMA_CHANNELS; - dma->channel[1] = NUM_DMA_CHANNELS; - + dma->output_buffer[0] = NULL; + dma->output_buffer[1] = NULL; + + dma->output_buffer_length[0] = 0; + dma->output_buffer_length[1] = 0; + + dma->output_channel[0] = NUM_DMA_CHANNELS; + dma->output_channel[1] = NUM_DMA_CHANNELS; + + dma->input_buffer[0] = NULL; + dma->input_buffer[1] = NULL; + + dma->input_buffer_length[0] = 0; + dma->input_buffer_length[1] = 0; + + dma->input_channel[0] = NUM_DMA_CHANNELS; + dma->input_channel[1] = NUM_DMA_CHANNELS; + dma->playing_in_progress = false; + dma->recording_in_progress = false; dma->paused = false; } void audio_dma_deinit(audio_dma_t *dma) { #ifdef PICO_RP2350 - port_free(dma->buffer[0]); + port_free(dma->output_buffer[0]); #else #if MICROPY_MALLOC_USES_ALLOCATED_SIZE - m_free(dma->buffer[0], dma->buffer_length[0]); + m_free(dma->output_buffer[0], dma->output_buffer_length[0]); #else - m_free(dma->buffer[0]); + m_free(dma->output_buffer[0]); #endif #endif - dma->buffer[0] = NULL; - dma->buffer_length[0] = 0; + dma->output_buffer[0] = NULL; + dma->output_buffer_length[0] = 0; #ifdef PICO_RP2350 - port_free(dma->buffer[1]); + port_free(dma->output_buffer[1]); #else #if MICROPY_MALLOC_USES_ALLOCATED_SIZE - m_free(dma->buffer[1], dma->buffer_length[1]); + m_free(dma->output_buffer[1], dma->output_buffer_length[1]); #else - m_free(dma->buffer[1]); + m_free(dma->output_buffer[1]); #endif #endif - dma->buffer[1] = NULL; - dma->buffer_length[1] = 0; + dma->output_buffer[1] = NULL; + dma->output_buffer_length[1] = 0; + + #ifdef PICO_RP2350 + port_free(dma->input_buffer[0]); + #else + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + m_free(dma->input_buffer[0], dma->input_buffer_length[0]); + #else + m_free(dma->input_buffer[0]); + #endif + #endif + dma->input_buffer[0] = NULL; + dma->input_buffer_length[0] = 0; + + #ifdef PICO_RP2350 + port_free(dma->input_buffer[1]); + #else + #if MICROPY_MALLOC_USES_ALLOCATED_SIZE + m_free(dma->input_buffer[1], dma->input_buffer_length[1]); + #else + m_free(dma->input_buffer[1]); + #endif + #endif + dma->input_buffer[1] = NULL; + dma->input_buffer_length[1] = 0; } bool audio_dma_get_playing(audio_dma_t *dma) { - if (dma->channel[0] == NUM_DMA_CHANNELS) { + if (dma->output_channel[0] == NUM_DMA_CHANNELS) { return false; } return dma->playing_in_progress; } +bool audio_dma_get_recording(audio_dma_t *dma) { + if (dma->input_channel[0] == NUM_DMA_CHANNELS) { + return false; + } + return dma->recording_in_progress; +} + +uint8_t *audio_dma_get_buffer(audio_dma_t *dma) { + if (!dma->input_register_address || dma->input_index >= 2) { + return NULL; + } + return dma->input_buffer[dma->input_index]; +} + // WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls // background tasks such as this and causes a stack overflow. // NOTE(dhalbert): I successfully printed from here while debugging. @@ -513,14 +807,14 @@ static void dma_callback_fun(void *arg) { uint8_t first_filled_channel = NUM_DMA_CHANNELS; size_t filled_count = 0; - if (dma->channel[0] != NUM_DMA_CHANNELS && (channels_to_load_mask & (1 << dma->channel[0]))) { + if (dma->output_channel[0] != NUM_DMA_CHANNELS && (channels_to_load_mask & (1 << dma->output_channel[0]))) { audio_dma_load_next_block(dma, 0); - first_filled_channel = dma->channel[0]; + first_filled_channel = dma->output_channel[0]; filled_count++; } - if (dma->channel[1] != NUM_DMA_CHANNELS && (channels_to_load_mask & (1 << dma->channel[1]))) { + if (dma->output_channel[1] != NUM_DMA_CHANNELS && (channels_to_load_mask & (1 << dma->output_channel[1]))) { audio_dma_load_next_block(dma, 1); - first_filled_channel = dma->channel[1]; + first_filled_channel = dma->output_channel[1]; filled_count++; } @@ -552,6 +846,14 @@ void __not_in_flash_func(isr_dma_0)(void) { // This is a noop if the callback is already queued. background_callback_add(&dma->callback, dma_callback_fun, (void *)dma); } + if (MP_STATE_PORT(recording_audio)[i] != NULL) { + audio_dma_t *dma = MP_STATE_PORT(recording_audio)[i]; + // Update last recorded buffer. + dma->input_index = (uint8_t)(i != dma->input_channel[0]); + // Reset destination buffer for the dma channel. + dma_channel_set_write_addr(i, dma->input_buffer[dma->input_index], false /* trigger */); + dma_channel_set_trans_count(i, dma->input_buffer_length[dma->input_index] / dma->output_size, false /* trigger */); + } if (MP_STATE_PORT(background_pio_read)[i] != NULL) { rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(background_pio_read)[i]; rp2pio_statemachine_dma_complete_read(pio, i); @@ -564,4 +866,5 @@ void __not_in_flash_func(isr_dma_0)(void) { } MP_REGISTER_ROOT_POINTER(mp_obj_t playing_audio[enum_NUM_DMA_CHANNELS]); +MP_REGISTER_ROOT_POINTER(mp_obj_t recording_audio[enum_NUM_DMA_CHANNELS]); #endif diff --git a/ports/raspberrypi/audio_dma.h b/ports/raspberrypi/audio_dma.h index cd892f5151b5c..79aa104f49334 100644 --- a/ports/raspberrypi/audio_dma.h +++ b/ports/raspberrypi/audio_dma.h @@ -20,12 +20,16 @@ typedef enum { typedef struct { mp_obj_t sample; - uint8_t *buffer[2]; // Allocated through port_malloc on RP2350 so they are dma-able - size_t buffer_length[2]; + uint8_t *output_buffer[2]; // Allocated through port_malloc on RP2350 so they are dma-able + size_t output_buffer_length[2]; + uint8_t *input_buffer[2]; // Allocated through port_malloc on RP2350 so they are dma-able + size_t input_buffer_length[2]; uint32_t channels_to_load_mask; uint32_t output_register_address; + uint32_t input_register_address; background_callback_t callback; - uint8_t channel[2]; + uint8_t output_channel[2]; + uint8_t input_channel[2]; uint8_t audio_channel; uint8_t output_size; uint8_t sample_spacing; @@ -38,8 +42,10 @@ typedef struct { bool unsigned_to_signed; bool output_signed; bool playing_in_progress; + bool recording_in_progress; bool paused; bool swap_channel; + uint8_t input_index; } audio_dma_t; void audio_dma_init(audio_dma_t *dma); @@ -55,6 +61,19 @@ void audio_dma_reset(void); // output_signed is true if the dma'd data should be signed. False and it will be unsigned. // output_register_address is the address to copy data to. // dma_trigger_source is the DMA trigger source which cause another copy +audio_dma_result audio_dma_setup(audio_dma_t *dma, + mp_obj_t sample, + bool loop, + bool single_channel_output, + uint8_t audio_channel, + bool output_signed, + uint8_t output_resolution, + uint32_t output_register_address, + uint8_t output_dma_trigger_source, + uint32_t input_register_address, + uint8_t input_dma_trigger_source, + bool swap_channel); + audio_dma_result audio_dma_setup_playback(audio_dma_t *dma, mp_obj_t sample, bool loop, @@ -66,8 +85,23 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma, uint8_t dma_trigger_source, bool swap_channel); +audio_dma_result audio_dma_setup_record(audio_dma_t *dma, + mp_obj_t sample, + bool loop, + bool single_channel_output, + uint8_t audio_channel, + bool output_signed, + uint8_t output_resolution, + uint32_t input_register_address, + uint8_t dma_trigger_source, + bool swap_channel); + void audio_dma_stop(audio_dma_t *dma); +void audio_dma_stop_output(audio_dma_t *dma); +void audio_dma_stop_input(audio_dma_t *dma); bool audio_dma_get_playing(audio_dma_t *dma); +bool audio_dma_get_recording(audio_dma_t *dma); +uint8_t *audio_dma_get_buffer(audio_dma_t *dma); void audio_dma_pause(audio_dma_t *dma); void audio_dma_resume(audio_dma_t *dma); bool audio_dma_get_paused(audio_dma_t *dma); diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c new file mode 100644 index 0000000000000..a57b84f044141 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -0,0 +1,378 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include +#include + +#include "mpconfigport.h" + +#include "py/mperrno.h" +#include "py/runtime.h" +#include "common-hal/audiobusio/I2S.h" +#include "shared-bindings/audiobusio/I2S.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-module/audiocore/__init__.h" +#include "bindings/rp2pio/StateMachine.h" + +#include "audio_dma.h" + +#include "src/rp2_common/hardware_pio/include/hardware/pio_instructions.h" + +#define I2S_CODE(bits_per_sample, out, in, left_justified, swap) \ + { \ +/* 00 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap) | pio_encode_delay(1), \ + /* .wrap_target */ \ +/* 01 */ (out ? pio_encode_pull(false, false) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ +/* 02 */ (out ? pio_encode_mov(pio_x, pio_osr) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ +/* 03 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00) | pio_encode_delay(3), \ +/* 04 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap), \ +/* 05 */ pio_encode_jmp_y_dec(3) | pio_encode_sideset(2, 0b01 << swap) | pio_encode_delay(2), \ +/* 06 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00 | !left_justified << !swap) | pio_encode_delay(3), \ +/* 07 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | !left_justified << !swap), \ +/* 08 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b01 << swap | !left_justified << !swap) | pio_encode_delay(2), \ +/* 09 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b10 >> swap) | pio_encode_delay(3), \ +/* 10 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b11), \ +/* 11 */ pio_encode_jmp_y_dec(9) | pio_encode_sideset(2, 0b11) | pio_encode_delay(2), \ +/* 12 */ (out ? pio_encode_out(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b00 | left_justified << !swap) | pio_encode_delay(2), \ +/* 13 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b00 | left_justified << !swap), \ +/* 13 */ (in ? pio_encode_in(pio_pins, 1) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ +/* 14 */ (in ? pio_encode_push(false, false) : pio_encode_nop()) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap), \ + /* .wrap */ \ + } + +// Caller validates that pins are free. +void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, + const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, + const mcu_pin_obj_t *data_out, const mcu_pin_obj_t *data_in, + const mcu_pin_obj_t *main_clock, bool left_justified, + uint32_t buffer_size, uint8_t channel_count, uint32_t sample_rate, + uint8_t bits_per_sample, bool samples_signed) { + if (main_clock != NULL) { + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_main_clock); + } + + const mcu_pin_obj_t *sideset_pin = NULL; + bool swap = false; + + if (bit_clock->number == word_select->number - 1) { + sideset_pin = bit_clock; + } else if (bit_clock->number == word_select->number + 1) { + sideset_pin = word_select; + swap = true; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("Bit clock and word select must be sequential GPIO pins")); + } + + uint16_t program[] = I2S_CODE(bits_per_sample, data_out != NULL, data_in != NULL, left_justified, swap); + + // Use the state machine to manage pins. + common_hal_rp2pio_statemachine_construct( + &self->state_machine, + program, MP_ARRAY_SIZE(program), + sample_rate * bits_per_sample * 16, // Frequency based on sample rate and bit width + NULL, 0, // init + NULL, 0, // may_exec + data_out, 1, PIO_PINMASK32_NONE, PIO_PINMASK32_ALL, // out pin + data_in, 1, // in pins + PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // in pulls + NULL, 1, PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // set pins + sideset_pin, 2, false, PIO_PINMASK32_NONE, PIO_PINMASK32_FROM_VALUE(0x1f), // sideset pins + false, // No sideset enable + NULL, PULL_NONE, // jump pin + PIO_PINMASK_NONE, // wait gpio pins + true, // exclusive pin use + false, 32, false, // out settings + false, // Wait for txstall + false, 32, false, // in settings + false, // Not user-interruptible. + 1, -1, // wrap settings + PIO_ANY_OFFSET, + PIO_FIFO_TYPE_DEFAULT, + PIO_MOV_STATUS_DEFAULT, + PIO_MOV_N_DEFAULT + ); + + self->buffer_size = buffer_size; + self->channel_count = channel_count; + self->sample_rate = sample_rate; + self->bits_per_sample = bits_per_sample; + self->samples_signed = samples_signed; + + self->playing = false; + audio_dma_init(&self->dma); + + if (self->state_machine.in) { + self->buffer[0] = m_malloc(self->buffer_size); + if (self->buffer[0] == NULL) { + common_hal_audiobusio_i2s_deinit(self); + m_malloc_fail(self->buffer_size); + } + memset(self->buffer[0], 0, self->buffer_size); + + self->buffer[1] = m_malloc(self->buffer_size); + if (self->buffer[1] == NULL) { + common_hal_audiobusio_i2s_deinit(self); + m_malloc_fail(self->buffer_size); + } + memset(self->buffer[1], 0, self->buffer_size); + + self->last_buf_idx = 1; // Which buffer to use first, toggle between 0 and 1 + } +} + +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, bool force) { + if (self->dma.output_channel[0] != NUM_DMA_CHANNELS || self->dma.input_channel[0] != NUM_DMA_CHANNELS) { + if (!force) { + return; + } + + audio_dma_stop(&self->dma); + common_hal_rp2pio_statemachine_stop(&self->state_machine); + } + + common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, sample_rate * bits_per_sample * 16); + common_hal_rp2pio_statemachine_restart(&self->state_machine); + + // On the RP2040, output registers are always written with a 32-bit write. + // If the write is 8 or 16 bits wide, the data will be replicated in upper bytes. + // See section 2.1.4 Narrow IO Register Writes in the RP2040 datasheet. + // This means that identical 16-bit audio data will be written in both halves of the incoming PIO + // FIFO register. Thus we get mono-to-stereo conversion for the I2S output for free. + audio_dma_result result = audio_dma_setup( + &self->dma, + sample, + loop, + false, // single channel + 0, // audio channel + true, // output signed + bits_per_sample, + (self->state_machine.out ? (uint32_t)&self->state_machine.pio->txf[self->state_machine.state_machine] : 0), // output register + (self->state_machine.out ? self->state_machine.tx_dreq : 0), // output data request line + (self->state_machine.in ? (uint32_t)&self->state_machine.pio->rxf[self->state_machine.state_machine] : 0), // input register + (self->state_machine.in ? self->state_machine.rx_dreq : 0), // input data request line + false); // swap channel + + if (result == AUDIO_DMA_DMA_BUSY) { + common_hal_audiobusio_i2s_stop(self); + mp_raise_RuntimeError(MP_ERROR_TEXT("No DMA channel found")); + } else if (result == AUDIO_DMA_MEMORY_ERROR) { + common_hal_audiobusio_i2s_stop(self); + mp_raise_RuntimeError(MP_ERROR_TEXT("Unable to allocate buffers for signed conversion")); + } +} + +bool common_hal_audiobusio_i2s_deinited(audiobusio_i2s_obj_t *self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self) { + if (common_hal_audiobusio_i2s_deinited(self)) { + return; + } + + common_hal_rp2pio_statemachine_deinit(&self->state_machine); + + audio_dma_deinit(&self->dma); + + self->buffer[0] = NULL; + self->buffer[1] = NULL; +} + +// output_buffer may be a byte buffer or a halfword buffer. +// output_buffer_length is the number of slots, not the number of bytes. +uint32_t common_hal_audiobusio_i2s_record_to_buffer(audiobusio_i2s_obj_t *self, + int16_t *output_buffer, uint32_t output_buffer_length) { + if (!self->state_machine.in) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); + } + + // Make sure that dma is running. + i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample, true); + + size_t output_count = 0; + int16_t *buffer[2]; + int8_t buffer_idx = 1; + size_t buffer_length = MIN((output_buffer_length - output_count), self->buffer_size / sizeof(int16_t)); + + while (output_count < output_buffer_length) { + do { + buffer_idx = !buffer_idx; + buffer[buffer_idx] = (int16_t *)audio_dma_get_buffer(&self->dma); + } while (buffer[buffer_idx] == NULL || buffer[0] == buffer[1]); + + for (size_t i = 0; i < buffer_length; i++) { + output_buffer[i + output_count] = buffer[buffer_idx][i]; + } + + output_count += buffer_length; + } + + return output_count; +} + +void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self, + mp_obj_t sample, bool loop) { + if (!self->state_machine.out) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); + } + + if (common_hal_audiobusio_i2s_get_playing(self)) { + common_hal_audiobusio_i2s_stop(self); + } + + uint8_t bits_per_sample = audiosample_bits_per_sample(sample); + uint32_t sample_rate = audiosample_sample_rate(sample); + uint8_t channel_count = audiosample_channel_count(sample); + if (channel_count > 2) { + mp_raise_ValueError(MP_ERROR_TEXT("Too many channels in sample.")); + } + + if (self->state_machine.in) { + if (bits_per_sample > self->bits_per_sample) { + mp_raise_ValueError(MP_ERROR_TEXT("Bits per sample cannot be greater than input.")); + } + if (sample_rate != self->sample_rate) { + mp_raise_ValueError(MP_ERROR_TEXT("Sample rate must match.")); + } + } + + i2s_configure_audio_dma(self, sample, loop, sample_rate, bits_per_sample, true); + self->playing = true; +} + +void common_hal_audiobusio_i2s_pause(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.out) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); + } + audio_dma_pause(&self->dma); +} + +void common_hal_audiobusio_i2s_resume(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.out) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); + } + // Maybe: Clear any overrun/underrun errors + audio_dma_resume(&self->dma); +} + +bool common_hal_audiobusio_i2s_get_paused(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.out) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); + } + return audio_dma_get_paused(&self->dma); +} + +void common_hal_audiobusio_i2s_stop(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.out) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); + } + + audio_dma_stop(&self->dma); + + common_hal_rp2pio_statemachine_stop(&self->state_machine); + + self->playing = false; +} + +bool common_hal_audiobusio_i2s_get_playing(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.out) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data out")); + } + + bool playing = audio_dma_get_playing(&self->dma); + if (!playing && self->playing) { + common_hal_audiobusio_i2s_stop(self); + } + return playing; +} + +uint32_t common_hal_audiobusio_i2s_get_sample_rate(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.in) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); + } + return self->sample_rate; +} + +uint8_t common_hal_audiobusio_i2s_get_channel_count(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.in) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); + } + return self->channel_count; +} + +uint8_t common_hal_audiobusio_i2s_get_bits_per_sample(audiobusio_i2s_obj_t *self) { + if (!self->state_machine.in) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); + } + return self->bits_per_sample; +} + +void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self, + bool single_channel_output, + uint8_t channel) { + if (!self->state_machine.in) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); + } + + if (single_channel_output) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported.")); + } + + memset(self->buffer[0], 0, self->buffer_size); + memset(self->buffer[1], 0, self->buffer_size); + + i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample, false); +} + +audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length) { + if (!self->state_machine.in) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); + } + + if (single_channel_output) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported.")); + } + + // Switch our buffers to the other buffer + self->last_buf_idx = !self->last_buf_idx; + + uint8_t *dma_buffer; + do { + dma_buffer = audio_dma_get_buffer(&self->dma); + } while (dma_buffer == NULL); + + // Copy dma buffer to output buffer + memcpy(self->buffer[self->last_buf_idx], dma_buffer, self->buffer_size); + + // Finally pass our buffer and length to the calling audio function + *buffer = (uint8_t *)self->buffer[self->last_buf_idx]; + *buffer_length = self->buffer_size; + + // I2S always returns more data unless an error occured (see audiocore/__init__.h) + return GET_BUFFER_MORE_DATA; +} + +void audiobusio_i2s_get_buffer_structure(audiobusio_i2s_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing) { + if (!self->state_machine.in) { + mp_raise_RuntimeError(MP_ERROR_TEXT("No data in")); + } + + *single_buffer = false; + *samples_signed = self->samples_signed; + *max_buffer_length = self->buffer_size; + if (single_channel_output) { + *spacing = self->channel_count; + } else { + *spacing = 1; + } +} diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.h b/ports/raspberrypi/common-hal/audiobusio/I2S.h new file mode 100644 index 0000000000000..3d4af7a27128b --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.h @@ -0,0 +1,47 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "common-hal/microcontroller/Pin.h" +#include "common-hal/rp2pio/StateMachine.h" + +#include "audio_dma.h" +#include "py/obj.h" + +#include "shared-module/audiocore/__init__.h" + +// We don't bit pack because we'll only have two at most. Its better to save code size instead. +typedef struct { + mp_obj_base_t base; + rp2pio_statemachine_obj_t state_machine; + audio_dma_t dma; + bool left_justified; + bool playing; + uint32_t buffer_size; + uint8_t channel_count; + uint32_t sample_rate; + uint8_t bits_per_sample; + bool samples_signed; + uint8_t *buffer[2]; + uint8_t last_buf_idx; +} audiobusio_i2s_obj_t; + +// These are not available from Python because it may be called in an interrupt. +void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self, + bool single_channel_output, + uint8_t channel); +audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length); // length in bytes +void audiobusio_i2s_get_buffer_structure(audiobusio_i2s_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing); + +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, bool force); diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..3cfb11166aafd --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -0,0 +1,330 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include +#include + +#include "mpconfigport.h" + +#include "py/mperrno.h" +#include "py/runtime.h" +#include "common-hal/audiobusio/I2SIn.h" +#include "shared-bindings/audiobusio/I2SIn.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "bindings/rp2pio/StateMachine.h" + +#include "audio_dma.h" + +#if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT + +const uint16_t i2sin_program_mono[] = { +// pull block side 0b11 ; Load OSR with bits_per_sample-2 + 0x98a0, +// out y 8 side 0b11 ; Save the value in y + 0x7848, +// nop side 0b01 + 0xa842, +// mov x y side 0b01 + 0xa822, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b01 + 0x4801, +// jmp x-- lbit side 0b01 + 0x0844, +// nop side 0b10 [1] + 0xb142, +// in pins 1 side 0b11 + 0x5801, +// mov x y side 0b11 + 0xb822, +// rbit: +// nop side 0b10 [1] + 0xb142, +// nop side 0b11 + 0xb842, +// jmp x-- rbit side 0b11 + 0x184a, +// nop side 0b00 [1] + 0xa142, +// nop side 0b01 + 0xa842, +}; + +const uint16_t i2sin_program_mono_swap[] = { +// pull block side 0b11 ; Load OSR with bits_per_sample-2 + 0x98a0, +// out y 8 side 0b11 ; Save the value in y + 0x7848, +// nop side 0b10 + 0xb042, +// mov x y side 0b10 + 0xb022, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b10 + 0x5001, +// jmp x-- lbit side 0b10 + 0x1044, +// nop side 0b01 [1] + 0xa942, +// in pins 1 side 0b11 + 0x5801, +// mov x y side 0b11 + 0xb822, +// rbit: +// nop side 0b01 [1] + 0xa942, +// nop side 0b11 + 0xb842, +// jmp x-- rbit side 0b11 + 0x184a, +// nop side 0b00 [1] + 0xa142, +// nop side 0b10 + 0xb042, +}; + +const uint16_t i2sin_program_stereo[] = { +// ; /--- LRCLK +// ; |/-- BCLK +// ; || +// pull block side 0b11 ; Load OSR with bits_per_sample-2 + 0x98a0, +// out y 8 side 0b11 ; Save the value in y + 0x7848, +// nop side 0b01 + 0xa842, +// mov x y side 0b01 + 0xa822, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b01 + 0x4801, +// jmp x-- lbit side 0b01 + 0x0844, +// nop side 0b10 [1] + 0xb142, +// in pins 1 side 0b11 + 0x5801, +// mov x y side 0b11 + 0xb822, +// rbit: +// nop side 0b10 [1] + 0xb142, +// in pins 1 side 0b11 + 0x5801, +// jmp x-- rbit side 0b11 + 0x184a, +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b01 + 0x4801, +}; + +const uint16_t i2sin_program_stereo_swap[] = { +// ; /--- LRCLK +// ; |/-- BCLK +// ; || +// pull block side 0b11 ; Load OSR with bits_per_sample-2 + 0x98a0, +// out y 8 side 0b11 ; Save the value in y + 0x7848, +// nop side 0b10 + 0xb042, +// mov x y side 0b10 + 0xb022, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b10 + 0x5001, +// jmp x-- lbit side 0b10 + 0x1044, +// nop side 0b01 [1] + 0xa942, +// in pins 1 side 0b11 + 0x5801, +// mov x y side 0b11 + 0xb822, +// rbit: +// nop side 0b01 [1] + 0xa942, +// in pins 1 side 0b11 + 0x5801, +// jmp x-- rbit side 0b11 + 0x184a, +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b10 + 0x5001, +}; + +// Caller validates that pins are free. +void common_hal_audiobusio_i2sin_construct(audiobusio_i2sin_obj_t *self, + const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, const mcu_pin_obj_t *data, + uint32_t buffer_size, uint8_t channel_count, uint32_t sample_rate, uint8_t bits_per_sample, + bool samples_signed) { + + const mcu_pin_obj_t *sideset_pin = NULL; + const uint16_t *program = NULL; + size_t program_len = 0; + + if (bit_clock->number == word_select->number - 1) { + sideset_pin = bit_clock; + + if (channel_count == 1) { + program_len = MP_ARRAY_SIZE(i2sin_program_mono); + program = i2sin_program_mono; + } else { + program_len = MP_ARRAY_SIZE(i2sin_program_stereo); + program = i2sin_program_stereo; + } + + } else if (bit_clock->number == word_select->number + 1) { + sideset_pin = word_select; + + if (channel_count == 1) { + program_len = MP_ARRAY_SIZE(i2sin_program_mono_swap); + program = i2sin_program_mono_swap; + } else { + program_len = MP_ARRAY_SIZE(i2sin_program_stereo_swap); + program = i2sin_program_stereo_swap; + } + + } else { + mp_raise_ValueError(MP_ERROR_TEXT("Bit clock and word select must be sequential GPIO pins")); + } + + // Use the state machine to manage pins. + common_hal_rp2pio_statemachine_construct( + &self->state_machine, + program, program_len, + sample_rate * bits_per_sample * 2 * 4, // Frequency based on sample rate and bit width + NULL, 0, // init + NULL, 0, // may_exec + NULL, 1, PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // out pin + data, 1, // in pins + PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // in pulls + NULL, 1, PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // set pins + sideset_pin, 2, false, PIO_PINMASK32_NONE, PIO_PINMASK32_FROM_VALUE(0x1f), // sideset pins + false, // No sideset enable + NULL, PULL_NONE, // jump pin + PIO_PINMASK_NONE, // wait gpio pins + true, // exclusive pin use + false, 8, false, // out settings + false, // Wait for txstall + true, bits_per_sample, false, // in settings + false, // Not user-interruptible. + 3, -1, // wrap settings + PIO_ANY_OFFSET, + PIO_FIFO_TYPE_DEFAULT, + PIO_MOV_STATUS_DEFAULT, + PIO_MOV_N_DEFAULT + ); + + audio_dma_init(&self->dma); + + self->buffer_size = buffer_size; + self->channel_count = channel_count; + self->sample_rate = sample_rate; + self->bits_per_sample = bits_per_sample; + self->samples_signed = samples_signed; +} + +bool common_hal_audiobusio_i2sin_deinited(audiobusio_i2sin_obj_t *self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_audiobusio_i2sin_deinit(audiobusio_i2sin_obj_t *self) { + if (common_hal_audiobusio_i2sin_deinited(self)) { + return; + } + + common_hal_rp2pio_statemachine_deinit(&self->state_machine); + + audio_dma_deinit(&self->dma); +} + +uint32_t common_hal_audiobusio_i2sin_get_sample_rate(audiobusio_i2sin_obj_t *self) { + return self->sample_rate; +} + +uint8_t common_hal_audiobusio_i2sin_get_channel_count(audiobusio_i2sin_obj_t *self) { + return self->channel_count; +} + +uint8_t common_hal_audiobusio_i2sin_get_bits_per_sample(audiobusio_i2sin_obj_t *self) { + return self->bits_per_sample; +} + +void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, + bool single_channel_output, + uint8_t channel) { + + common_hal_rp2pio_statemachine_restart(&self->state_machine); + + // Send bit width + const uint8_t bit_width_data[1] = { self->bits_per_sample - 2 }; + common_hal_rp2pio_statemachine_write(&self->state_machine, bit_width_data, 1, 1, false); + + audio_dma_result result = audio_dma_setup_record( + &self->dma, + self, + true, + single_channel_output, // single channel + channel, // audio channel + true, // output signed + self->bits_per_sample, // output resolution + (uint32_t)&self->state_machine.pio->rxf[self->state_machine.state_machine], // input register + self->state_machine.rx_dreq, // data request line + false); // swap channel + + if (result == AUDIO_DMA_DMA_BUSY) { + common_hal_rp2pio_statemachine_stop(&self->state_machine); + mp_raise_RuntimeError(MP_ERROR_TEXT("No DMA channel found")); + } else if (result == AUDIO_DMA_MEMORY_ERROR) { + common_hal_rp2pio_statemachine_stop(&self->state_machine); + mp_raise_RuntimeError(MP_ERROR_TEXT("Unable to allocate buffers for signed conversion")); + } + + self->last_index = -1; +} + +audioio_get_buffer_result_t audiobusio_i2sin_get_buffer(audiobusio_i2sin_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length) { + + // Do other things while we wait for the buffer to fill. + while (self->last_index == self->dma.input_index) { + RUN_BACKGROUND_TASKS; + } + + *buffer_length = self->buffer_size; + *buffer = audio_dma_get_buffer(&self->dma); + return GET_BUFFER_MORE_DATA; +} + +void audiobusio_i2sin_get_buffer_structure(audiobusio_i2sin_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing) { + + *single_buffer = false; + *samples_signed = self->samples_signed; + *max_buffer_length = self->buffer_size; + if (single_channel_output) { + *spacing = self->channel_count; + } else { + *spacing = 1; + } +} + +#endif // CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.h b/ports/raspberrypi/common-hal/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..746fc0b82bb57 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.h @@ -0,0 +1,46 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "common-hal/microcontroller/Pin.h" +#include "common-hal/rp2pio/StateMachine.h" + +#include "audio_dma.h" +#include "py/obj.h" + +#include "shared-module/audiocore/__init__.h" + +#if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT + +// We don't bit pack because we'll only have two at most. Its better to save code size instead. +typedef struct { + mp_obj_base_t base; + rp2pio_statemachine_obj_t state_machine; + audio_dma_t dma; + uint32_t buffer_size; + uint8_t channel_count; + uint32_t sample_rate; + uint8_t bits_per_sample; + bool samples_signed; + uint8_t last_index; +} audiobusio_i2sin_obj_t; + + +// These are not available from Python because it may be called in an interrupt. +void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, + bool single_channel_output, + uint8_t channel); +audioio_get_buffer_result_t audiobusio_i2sin_get_buffer(audiobusio_i2sin_obj_t *self, + bool single_channel_output, + uint8_t channel, + uint8_t **buffer, + uint32_t *buffer_length); // length in bytes +void audiobusio_i2sin_get_buffer_structure(audiobusio_i2sin_obj_t *self, bool single_channel_output, + bool *single_buffer, bool *samples_signed, + uint32_t *max_buffer_length, uint8_t *spacing); + +#endif // CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SOut.c b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c index d29f50b06b827..71f2f34684a80 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SOut.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c @@ -18,6 +18,8 @@ #include "shared-module/audiocore/__init__.h" #include "bindings/rp2pio/StateMachine.h" +#if CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN + const uint16_t i2s_program[] = { /* From i2s.pio: @@ -341,3 +343,5 @@ bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t *self) { } return playing; } + +#endif // CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SOut.h b/ports/raspberrypi/common-hal/audiobusio/I2SOut.h index 2996640dc2d49..5c968532ad230 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SOut.h +++ b/ports/raspberrypi/common-hal/audiobusio/I2SOut.h @@ -12,6 +12,8 @@ #include "audio_dma.h" #include "py/obj.h" +#if CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN + // We don't bit pack because we'll only have two at most. Its better to save code size instead. typedef struct { mp_obj_base_t base; @@ -22,3 +24,5 @@ typedef struct { } audiobusio_i2sout_obj_t; void i2sout_reset(void); + +#endif // CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index 1404b2b06777c..c18ec025ebd69 100644 --- a/ports/raspberrypi/mpconfigport.mk +++ b/ports/raspberrypi/mpconfigport.mk @@ -41,6 +41,7 @@ CIRCUITPY_ANALOGBUFIO = 1 # Audio via PWM CIRCUITPY_AUDIOIO = 0 CIRCUITPY_AUDIOBUSIO ?= 1 +CIRCUITPY_AUDIOBUSIO_I2SIN ?= 1 CIRCUITPY_AUDIOCORE ?= 1 CIRCUITPY_AUDIOPWMIO ?= 1 diff --git a/ports/stm/common-hal/audiobusio/I2S.c b/ports/stm/common-hal/audiobusio/I2S.c new file mode 100644 index 0000000000000..8ebbbb240a76e --- /dev/null +++ b/ports/stm/common-hal/audiobusio/I2S.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2 is not enabled on the STM32L4 family, this file is still required for the build to pass diff --git a/ports/stm/common-hal/audiobusio/I2S.h b/ports/stm/common-hal/audiobusio/I2S.h new file mode 100644 index 0000000000000..7605a80972cb2 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/I2S.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2 is not enabled on the STM32L4 family, this file is still required for the build to pass diff --git a/ports/stm/common-hal/audiobusio/I2SIn.c b/ports/stm/common-hal/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..9332f224b8e4e --- /dev/null +++ b/ports/stm/common-hal/audiobusio/I2SIn.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +// Although IS2In is not enabled on the STM32L4 family, this file is still required for the build to pass diff --git a/ports/stm/common-hal/audiobusio/I2SIn.h b/ports/stm/common-hal/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..fbbc045750797 --- /dev/null +++ b/ports/stm/common-hal/audiobusio/I2SIn.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/audiocore/__init__.h" + +// Although IS2In is not enabled on the STM32L4 family, this file is still required for the build to pass diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index f73d52a6c4b12..5a8872702f04f 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -484,6 +484,8 @@ SRC_COMMON_HAL_ALL = \ analogio/AnalogIn.c \ analogio/AnalogOut.c \ analogio/__init__.c \ + audiobusio/I2S.c \ + audiobusio/I2SIn.c \ audiobusio/I2SOut.c \ audiobusio/PDMIn.c \ audiobusio/__init__.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index b081e0f52dbde..ab29f5c46c4e1 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -121,6 +121,10 @@ CFLAGS += -DCIRCUITPY_AUDIOBUSIO_I2SOUT=$(CIRCUITPY_AUDIOBUSIO_I2SOUT) CIRCUITPY_AUDIOBUSIO_PDMIN ?= $(CIRCUITPY_AUDIOBUSIO) CFLAGS += -DCIRCUITPY_AUDIOBUSIO_PDMIN=$(CIRCUITPY_AUDIOBUSIO_PDMIN) +# Only RP2xxx boards currently support I2SIn +CIRCUITPY_AUDIOBUSIO_I2SIN ?= 0 +CFLAGS += -DCIRCUITPY_AUDIOBUSIO_I2SIN=$(CIRCUITPY_AUDIOBUSIO_I2SIN) + CIRCUITPY_AUDIOIO ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_AUDIOIO=$(CIRCUITPY_AUDIOIO) diff --git a/shared-bindings/audiobusio/I2S.c b/shared-bindings/audiobusio/I2S.c new file mode 100644 index 0000000000000..3e8a8087a337b --- /dev/null +++ b/shared-bindings/audiobusio/I2S.c @@ -0,0 +1,353 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared/runtime/context_manager_helpers.h" +#include "py/binary.h" +#include "py/mphal.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/audiobusio/I2S.h" +#include "shared-bindings/util.h" + +//| class I2S: +//| """Connect with an I2S bus to input and/or output an audio stream""" +//| +//| def __init__( +//| self, +//| bit_clock: microcontroller.Pin, +//| word_select: microcontroller.Pin, +//| *, +//| data_out: Optional[microcontroller.Pin] = None, +//| data_in: Optional[microcontroller.Pin] = None, +//| main_clock: Optional[microcontroller.Pin] = None, +//| left_justified: bool = False, +//| buffer_size: int = 512, +//| channel_count: int = 2, +//| sample_rate: int = 8000, +//| bits_per_sample: int = 16, +//| samples_signed: bool = True +//| ) -> None: +//| """Create a I2S object associated with the given pins. +//| +//| :param ~microcontroller.Pin bit_clock: The bit clock (or serial clock) pin +//| :param ~microcontroller.Pin word_select: The word select (or left/right clock) pin +//| :param ~microcontroller.Pin data_out: The data output pin +//| :param ~microcontroller.Pin data_in: The data input pin +//| :param ~microcontroller.Pin main_clock: The main clock pin +//| :param bool left_justified: True when data bits are aligned with the word select clock. False +//| when they are shifted by one to match classic I2S protocol. +//| :param int buffer_size: The total size in bytes of the input buffer. Only used if handling +//| input. +//| :param int channel_count: The number of channels. 1 = mono; 2 = stereo. Only used if handling +//| input. +//| :param int sample_rate: The desired sample rate. Only used if handling input. +//| :param int bits_per_sample: Number of bits per sample. Must be divisible by 8. Only used if +//| handling input. +//| :param bool samples_signed: Samples are signed (True) or unsigned (False). Only used if +//| handling input. +//| +//| Simple 8ksps 440 Hz sine wave on `Metro M0 Express `_ +//| using `UDA1334 Breakout `_:: +//| +//| import audiobusio +//| import audiocore +//| import board +//| import array +//| import time +//| import math +//| +//| # Generate one period of sine wave. +//| length = 8000 // 440 +//| sine_wave = array.array("H", [0] * length) +//| for i in range(length): +//| sine_wave[i] = int(math.sin(math.pi * 2 * i / length) * (2 ** 15) + 2 ** 15) +//| +//| sine_wave = audiocore.RawSample(sine_wave, sample_rate=8000) +//| i2s = audiobusio.I2S(board.D1, board.D0, data_out=board.D9) +//| i2s.play(sine_wave, loop=True) +//| time.sleep(1) +//| i2s.stop() +//| +//| Playing a wave file from flash:: +//| +//| import board +//| import audiocore +//| import audiobusio +//| import digitalio +//| +//| f = open("cplay-5.1-16bit-16khz.wav", "rb") +//| wav = audiocore.WaveFile(f) +//| +//| a = audiobusio.I2S(board.D1, board.D0, data_out=board.D9) +//| +//| print("playing") +//| a.play(wav) +//| while a.playing: +//| pass +//| print("stopped") +//| +//| Playing an I2S input signal to a PWMAudioOut:: +//| +//| import audiobusio +//| import board +//| import audiopwmio +//| +//| mic = audiobusio.I2S(board.GP0, board.GP1, data_in=board.GP2, channel_count=1, sample_rate=16000) +//| dac = audiopwmio.PWMAudioOut(board.GP3) +//| dac.play(mic) +//| """ +//| ... +static mp_obj_t audiobusio_i2s_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if !CIRCUITPY_AUDIOBUSIO_I2SOUT || !CIRCUITPY_AUDIOBUSIO_I2SIN + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_I2S); + return NULL; // Not reachable. + #else + enum { ARG_bit_clock, ARG_word_select, ARG_data_out, ARG_data_in, ARG_main_clock, ARG_left_justified, ARG_buffer_size, ARG_channel_count, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bit_clock, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_word_select, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_data_out, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_data_in, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_main_clock, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_left_justified, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_buffer_size, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 512} }, + { MP_QSTR_channel_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 2} }, + { MP_QSTR_sample_rate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8000} }, + { MP_QSTR_bits_per_sample, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 16} }, + { MP_QSTR_samples_signed, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const mcu_pin_obj_t *bit_clock = validate_obj_is_free_pin(args[ARG_bit_clock].u_obj, MP_QSTR_bit_clock); + const mcu_pin_obj_t *word_select = validate_obj_is_free_pin(args[ARG_word_select].u_obj, MP_QSTR_word_select); + const mcu_pin_obj_t *data_out = validate_obj_is_free_pin_or_none(args[ARG_data_out].u_obj, MP_QSTR_data_out); + const mcu_pin_obj_t *data_in = validate_obj_is_free_pin_or_none(args[ARG_data_in].u_obj, MP_QSTR_data_in); + const mcu_pin_obj_t *main_clock = validate_obj_is_free_pin_or_none(args[ARG_main_clock].u_obj, MP_QSTR_main_clock); + + mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count); + mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate); + mp_int_t bits_per_sample = mp_arg_validate_int_range(args[ARG_bits_per_sample].u_int, 8, 32, MP_QSTR_bits_per_sample); + if (bits_per_sample % 8 != 0) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be multiple of 8."), MP_QSTR_bits_per_sample); + } + + audiobusio_i2s_obj_t *self = mp_obj_malloc(audiobusio_i2s_obj_t, &audiobusio_i2s_type); + common_hal_audiobusio_i2s_construct(self, bit_clock, word_select, data_out, data_in, main_clock, args[ARG_left_justified].u_bool, args[ARG_buffer_size].u_int, channel_count, sample_rate, bits_per_sample, args[ARG_samples_signed].u_bool); + + return MP_OBJ_FROM_PTR(self); + #endif +} + +#if CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN + +//| def deinit(self) -> None: +//| """Deinitialises the I2S and releases any hardware resources for reuse.""" +//| ... +static mp_obj_t audiobusio_i2s_deinit(mp_obj_t self_in) { + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiobusio_i2s_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2s_deinit_obj, audiobusio_i2s_deinit); + +static void check_for_deinit(audiobusio_i2s_obj_t *self) { + if (common_hal_audiobusio_i2s_deinited(self)) { + raise_deinited_error(); + } +} + +//| def __enter__(self) -> I2S: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +static mp_obj_t audiobusio_i2s_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_audiobusio_i2s_deinit(args[0]); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiobusio_i2s___exit___obj, 4, 4, audiobusio_i2s_obj___exit__); + +//| def record(self, destination: WriteableBuffer, destination_length: int) -> int: +//| """Records destination_length bytes of samples to destination. This is +//| blocking. +//| +//| An IOError may be raised when the destination is too slow to record the +//| audio at the given rate. For internal flash, writing all 1s to the file +//| before recording is recommended to speed up writes. +//| +//| :return: The number of samples recorded. If this is less than ``destination_length``, +//| some samples were missed due to processing time.""" +//| ... +static mp_obj_t audiobusio_i2s_obj_record(mp_obj_t self_obj, mp_obj_t destination, mp_obj_t destination_length) { + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(self_obj); + check_for_deinit(self); + uint32_t length = mp_arg_validate_type_int(destination_length, MP_QSTR_length); + mp_arg_validate_length_min(length, 0, MP_QSTR_length); + + mp_buffer_info_t bufinfo; + if (mp_obj_is_type(destination, &mp_type_fileio)) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Cannot record to a file")); + } else if (mp_get_buffer(destination, &bufinfo, MP_BUFFER_WRITE)) { + if (bufinfo.len / mp_binary_get_size('@', bufinfo.typecode, NULL) < length) { + mp_raise_ValueError(MP_ERROR_TEXT("Destination capacity is smaller than destination_length.")); + } + uint8_t bit_depth = common_hal_audiobusio_i2s_get_bits_per_sample(self); + if (bufinfo.typecode != 'h' && bit_depth == 16) { + mp_raise_ValueError(MP_ERROR_TEXT("destination buffer must be an array of type 'h' for bit_depth = 16")); + } else if (bufinfo.typecode != 'B' && bufinfo.typecode != BYTEARRAY_TYPECODE && bit_depth == 8) { + mp_raise_ValueError(MP_ERROR_TEXT("destination buffer must be a bytearray or array of type 'B' for bit_depth = 8")); + } + // length is the buffer length in slots, not bytes. + uint32_t length_written = + common_hal_audiobusio_i2s_record_to_buffer(self, bufinfo.buf, length); + return MP_OBJ_NEW_SMALL_INT(length_written); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(audiobusio_i2s_record_obj, audiobusio_i2s_obj_record); + +//| def play(self, sample: circuitpython_typing.AudioSample, *, loop: bool = False) -> None: +//| """Plays the sample once when loop=False and continuously when loop=True. +//| Does not block. Use `playing` to block. +//| +//| Sample must be an `audiocore.WaveFile`, `audiocore.RawSample`, `audiomixer.Mixer` or `audiomp3.MP3Decoder`. +//| +//| The sample itself should consist of 8 bit or 16 bit samples.""" +//| ... +static mp_obj_t audiobusio_i2s_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_sample, ARG_loop }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED }, + { MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, + }; + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + check_for_deinit(self); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_obj_t sample = args[ARG_sample].u_obj; + common_hal_audiobusio_i2s_play(self, sample, args[ARG_loop].u_bool); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(audiobusio_i2s_play_obj, 1, audiobusio_i2s_obj_play); + +//| def stop(self) -> None: +//| """Stops playback.""" +//| ... +static mp_obj_t audiobusio_i2s_obj_stop(mp_obj_t self_in) { + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + common_hal_audiobusio_i2s_stop(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2s_stop_obj, audiobusio_i2s_obj_stop); + +//| playing: bool +//| """True when the audio sample is being output. (read-only)""" +static mp_obj_t audiobusio_i2s_obj_get_playing(mp_obj_t self_in) { + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_audiobusio_i2s_get_playing(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2s_get_playing_obj, audiobusio_i2s_obj_get_playing); + +MP_PROPERTY_GETTER(audiobusio_i2s_playing_obj, + (mp_obj_t)&audiobusio_i2s_get_playing_obj); + +//| def pause(self) -> None: +//| """Stops playback temporarily while remembering the position. Use `resume` to resume playback.""" +//| ... +static mp_obj_t audiobusio_i2s_obj_pause(mp_obj_t self_in) { + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + + if (!common_hal_audiobusio_i2s_get_playing(self)) { + mp_raise_RuntimeError(MP_ERROR_TEXT("Not playing")); + } + common_hal_audiobusio_i2s_pause(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2s_pause_obj, audiobusio_i2s_obj_pause); + +//| def resume(self) -> None: +//| """Resumes sample playback after :py:func:`pause`.""" +//| ... +static mp_obj_t audiobusio_i2s_obj_resume(mp_obj_t self_in) { + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + + if (common_hal_audiobusio_i2s_get_paused(self)) { + common_hal_audiobusio_i2s_resume(self); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2s_resume_obj, audiobusio_i2s_obj_resume); + +//| paused: bool +//| """True when playback is paused. (read-only)""" +//| +static mp_obj_t audiobusio_i2s_obj_get_paused(mp_obj_t self_in) { + audiobusio_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_audiobusio_i2s_get_paused(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2s_get_paused_obj, audiobusio_i2s_obj_get_paused); + +MP_PROPERTY_GETTER(audiobusio_i2s_paused_obj, + (mp_obj_t)&audiobusio_i2s_get_paused_obj); + +#endif // CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN + +static const mp_rom_map_elem_t audiobusio_i2s_locals_dict_table[] = { + #if CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN + // Methods + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiobusio_i2s_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiobusio_i2s___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_record), MP_ROM_PTR(&audiobusio_i2s_record_obj) }, + { MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audiobusio_i2s_play_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audiobusio_i2s_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_pause), MP_ROM_PTR(&audiobusio_i2s_pause_obj) }, + { MP_ROM_QSTR(MP_QSTR_resume), MP_ROM_PTR(&audiobusio_i2s_resume_obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiobusio_i2s_playing_obj) }, + { MP_ROM_QSTR(MP_QSTR_paused), MP_ROM_PTR(&audiobusio_i2s_paused_obj) }, + #endif // CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN +}; +static MP_DEFINE_CONST_DICT(audiobusio_i2s_locals_dict, audiobusio_i2s_locals_dict_table); + +static const audiosample_p_t audiobusio_i2s_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + #if CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN + .sample_rate = (audiosample_sample_rate_fun)common_hal_audiobusio_i2s_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiobusio_i2s_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_audiobusio_i2s_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)audiobusio_i2s_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)audiobusio_i2s_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)audiobusio_i2s_get_buffer_structure, + #endif // CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN +}; + +MP_DEFINE_CONST_OBJ_TYPE( + audiobusio_i2s_type, + MP_QSTR_I2S, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, audiobusio_i2s_make_new, + locals_dict, &audiobusio_i2s_locals_dict, + protocol, &audiobusio_i2s_proto + ); diff --git a/shared-bindings/audiobusio/I2S.h b/shared-bindings/audiobusio/I2S.h new file mode 100644 index 0000000000000..82509cfd6946a --- /dev/null +++ b/shared-bindings/audiobusio/I2S.h @@ -0,0 +1,40 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "common-hal/audiobusio/I2S.h" +#include "common-hal/microcontroller/Pin.h" +#include "extmod/vfs_fat.h" + +extern const mp_obj_type_t audiobusio_i2s_type; + +#if CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN + +void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, + const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, const mcu_pin_obj_t *data_out, const mcu_pin_obj_t *data_in, + const mcu_pin_obj_t *main_clock, bool left_justified, + uint32_t buffer_size, uint8_t channel_count, uint32_t sample_rate, uint8_t bits_per_sample, + bool samples_signed); + +void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self); +bool common_hal_audiobusio_i2s_deinited(audiobusio_i2s_obj_t *self); + +uint32_t common_hal_audiobusio_i2s_record_to_buffer(audiobusio_i2s_obj_t *self, + int16_t *buffer, uint32_t length); + +void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self, mp_obj_t sample, bool loop); +void common_hal_audiobusio_i2s_stop(audiobusio_i2s_obj_t *self); +bool common_hal_audiobusio_i2s_get_playing(audiobusio_i2s_obj_t *self); +void common_hal_audiobusio_i2s_pause(audiobusio_i2s_obj_t *self); +void common_hal_audiobusio_i2s_resume(audiobusio_i2s_obj_t *self); +bool common_hal_audiobusio_i2s_get_paused(audiobusio_i2s_obj_t *self); + +uint32_t common_hal_audiobusio_i2s_get_sample_rate(audiobusio_i2s_obj_t *self); +uint8_t common_hal_audiobusio_i2s_get_channel_count(audiobusio_i2s_obj_t *self); +uint8_t common_hal_audiobusio_i2s_get_bits_per_sample(audiobusio_i2s_obj_t *self); + +#endif // CIRCUITPY_AUDIOBUSIO_I2SOUT && CIRCUITPY_AUDIOBUSIO_I2SIN diff --git a/shared-bindings/audiobusio/I2SIn.c b/shared-bindings/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..eecc9fdc0382d --- /dev/null +++ b/shared-bindings/audiobusio/I2SIn.c @@ -0,0 +1,153 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#include + +#include "shared/runtime/context_manager_helpers.h" +#include "py/binary.h" +#include "py/mphal.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/audiobusio/I2SIn.h" +#include "shared-bindings/util.h" + +//| class I2SIn: +//| """Record an input I2S audio stream""" +//| +//| def __init__( +//| self, +//| bit_clock: microcontroller.Pin, +//| word_select: microcontroller.Pin, +//| data: microcontroller.Pin, +//| *, +//| buffer_size: int = 512, +//| channel_count: int = 2, +//| sample_rate: int = 8000, +//| bits_per_sample: int = 16, +//| samples_signed: bool = True +//| ) -> None: +//| """Create a I2SIn object associated with the given pins. This allows you to +//| record audio signals from the given pins. Individual ports may put further +//| restrictions on the recording parameters. +//| +//| :param ~microcontroller.Pin bit_clock: The bit clock (or serial clock) pin +//| :param ~microcontroller.Pin word_select: The word select (or left/right clock) pin +//| :param ~microcontroller.Pin data: The data pin +//| :param int buffer_size: The total size in bytes of the input buffer +//| :param int channel_count: The number of channels. 1 = mono; 2 = stereo. +//| :param int sample_rate: The desired sample rate +//| :param int bits_per_sample: Number of bits per sample. Must be divisible by 8 +//| :param bool samples_signed: Samples are signed (True) or unsigned (False) +//| +//| Playing an I2SIn signal to a PWMAudioOut:: +//| +//| import audiobusio +//| import board +//| import audiopwmio +//| +//| mic = audiobusio.I2SIn(board.GP0, board.GP1, board.GP2, channel_count=1, sample_rate=16000) +//| dac = audiopwmio.PWMAudioOut(board.GP3) +//| dac.play(mic) +//| """ +//| ... +static mp_obj_t audiobusio_i2sin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + #if !CIRCUITPY_AUDIOBUSIO_I2SIN || CIRCUITPY_AUDIOBUSIO_I2SOUT + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_I2SIn); + return NULL; // Not reachable. + #else + enum { ARG_bit_clock, ARG_word_select, ARG_data, ARG_buffer_size, ARG_channel_count, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bit_clock, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_word_select, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_buffer_size, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 512} }, + { MP_QSTR_channel_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 2} }, + { MP_QSTR_sample_rate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8000} }, + { MP_QSTR_bits_per_sample, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 16} }, + { MP_QSTR_samples_signed, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const mcu_pin_obj_t *bit_clock = validate_obj_is_free_pin(args[ARG_bit_clock].u_obj, MP_QSTR_bit_clock); + const mcu_pin_obj_t *word_select = validate_obj_is_free_pin(args[ARG_word_select].u_obj, MP_QSTR_word_select); + const mcu_pin_obj_t *data = validate_obj_is_free_pin(args[ARG_data].u_obj, MP_QSTR_data); + + mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, 2, MP_QSTR_channel_count); + mp_int_t sample_rate = mp_arg_validate_int_min(args[ARG_sample_rate].u_int, 1, MP_QSTR_sample_rate); + mp_int_t bits_per_sample = mp_arg_validate_int_range(args[ARG_bits_per_sample].u_int, 8, 32, MP_QSTR_bits_per_sample); + if (bits_per_sample % 8 != 0) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be multiple of 8."), MP_QSTR_bits_per_sample); + } + + audiobusio_i2sin_obj_t *self = mp_obj_malloc(audiobusio_i2sin_obj_t, &audiobusio_i2sin_type); + common_hal_audiobusio_i2sin_construct(self, bit_clock, word_select, data, args[ARG_buffer_size].u_int, channel_count, sample_rate, bits_per_sample, args[ARG_samples_signed].u_bool); + + return MP_OBJ_FROM_PTR(self); + #endif +} + +#if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT + +//| def deinit(self) -> None: +//| """Deinitialises the I2SIn and releases any hardware resources for reuse.""" +//| ... +static mp_obj_t audiobusio_i2sin_deinit(mp_obj_t self_in) { + audiobusio_i2sin_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_audiobusio_i2sin_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sin_deinit_obj, audiobusio_i2sin_deinit); + +//| def __enter__(self) -> I2SIn: +//| """No-op used by Context Managers.""" +//| ... +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context.""" +//| ... +//| +static mp_obj_t audiobusio_i2sin_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_audiobusio_i2sin_deinit(args[0]); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiobusio_i2sin___exit___obj, 4, 4, audiobusio_i2sin_obj___exit__); + +#endif // CIRCUITPY_AUDIOBUSIO_I2SIN + +static const mp_rom_map_elem_t audiobusio_i2sin_locals_dict_table[] = { + #if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT + // Methods + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiobusio_i2sin_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audiobusio_i2sin___exit___obj) }, + #endif // CIRCUITPY_AUDIOBUSIO_I2SIN +}; +static MP_DEFINE_CONST_DICT(audiobusio_i2sin_locals_dict, audiobusio_i2sin_locals_dict_table); + +static const audiosample_p_t audiobusio_i2sin_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) + #if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT + .sample_rate = (audiosample_sample_rate_fun)common_hal_audiobusio_i2sin_get_sample_rate, + .bits_per_sample = (audiosample_bits_per_sample_fun)common_hal_audiobusio_i2sin_get_bits_per_sample, + .channel_count = (audiosample_channel_count_fun)common_hal_audiobusio_i2sin_get_channel_count, + .reset_buffer = (audiosample_reset_buffer_fun)audiobusio_i2sin_reset_buffer, + .get_buffer = (audiosample_get_buffer_fun)audiobusio_i2sin_get_buffer, + .get_buffer_structure = (audiosample_get_buffer_structure_fun)audiobusio_i2sin_get_buffer_structure, + #endif // CIRCUITPY_AUDIOBUSIO_I2SIN +}; + +MP_DEFINE_CONST_OBJ_TYPE( + audiobusio_i2sin_type, + MP_QSTR_I2SIn, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, audiobusio_i2sin_make_new, + locals_dict, &audiobusio_i2sin_locals_dict, + protocol, &audiobusio_i2sin_proto + ); diff --git a/shared-bindings/audiobusio/I2SIn.h b/shared-bindings/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..65d9867f8f2d9 --- /dev/null +++ b/shared-bindings/audiobusio/I2SIn.h @@ -0,0 +1,28 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2024 Cooper Dalrymple +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "common-hal/audiobusio/I2SIn.h" +#include "common-hal/microcontroller/Pin.h" +#include "extmod/vfs_fat.h" + +extern const mp_obj_type_t audiobusio_i2sin_type; + +#if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT +void common_hal_audiobusio_i2sin_construct(audiobusio_i2sin_obj_t *self, + const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, const mcu_pin_obj_t *data, + uint32_t buffer_size, uint8_t channel_count, uint32_t sample_rate, uint8_t bits_per_sample, + bool samples_signed); + +void common_hal_audiobusio_i2sin_deinit(audiobusio_i2sin_obj_t *self); +bool common_hal_audiobusio_i2sin_deinited(audiobusio_i2sin_obj_t *self); + +uint32_t common_hal_audiobusio_i2sin_get_sample_rate(audiobusio_i2sin_obj_t *self); +uint8_t common_hal_audiobusio_i2sin_get_channel_count(audiobusio_i2sin_obj_t *self); +uint8_t common_hal_audiobusio_i2sin_get_bits_per_sample(audiobusio_i2sin_obj_t *self); + +#endif // CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c index 9aaf7421c653c..98b3fbcc26fb6 100644 --- a/shared-bindings/audiobusio/I2SOut.c +++ b/shared-bindings/audiobusio/I2SOut.c @@ -78,7 +78,7 @@ //| ... //| static mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - #if !CIRCUITPY_AUDIOBUSIO_I2SOUT + #if !CIRCUITPY_AUDIOBUSIO_I2SOUT || CIRCUITPY_AUDIOBUSIO_I2SIN mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_I2SOut); return NULL; // Not reachable. #else @@ -88,7 +88,7 @@ static mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_a { MP_QSTR_word_select, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_data, MP_ARG_OBJ | MP_ARG_REQUIRED }, { MP_QSTR_main_clock, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} }, - { MP_QSTR_left_justified, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_bool = false} }, + { MP_QSTR_left_justified, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -105,7 +105,7 @@ static mp_obj_t audiobusio_i2sout_make_new(const mp_obj_type_t *type, size_t n_a #endif } -#if CIRCUITPY_AUDIOBUSIO_I2SOUT +#if CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN //| def deinit(self) -> None: //| """Deinitialises the I2SOut and releases any hardware resources for reuse.""" @@ -239,7 +239,7 @@ MP_PROPERTY_GETTER(audiobusio_i2sout_paused_obj, static const mp_rom_map_elem_t audiobusio_i2sout_locals_dict_table[] = { // Methods - #if CIRCUITPY_AUDIOBUSIO_I2SOUT + #if CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiobusio_i2sout_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiobusio_i2sout_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, diff --git a/shared-bindings/audiobusio/I2SOut.h b/shared-bindings/audiobusio/I2SOut.h index a53997ef91b68..1ba7b2ca9d1de 100644 --- a/shared-bindings/audiobusio/I2SOut.h +++ b/shared-bindings/audiobusio/I2SOut.h @@ -12,7 +12,7 @@ extern const mp_obj_type_t audiobusio_i2sout_type; // Some boards don't have the I2SOut pins available. -#if CIRCUITPY_AUDIOBUSIO_I2SOUT +#if CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t *self, const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, const mcu_pin_obj_t *data, @@ -27,4 +27,4 @@ void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t *self); void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t *self); bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t *self); -#endif // CIRCUITPY_AUDIOBUSIO_I2SOUT +#endif // CIRCUITPY_AUDIOBUSIO_I2SOUT && !CIRCUITPY_AUDIOBUSIO_I2SIN diff --git a/shared-bindings/audiobusio/__init__.c b/shared-bindings/audiobusio/__init__.c index 70680b1cf16e4..2e90f9b6cce32 100644 --- a/shared-bindings/audiobusio/__init__.c +++ b/shared-bindings/audiobusio/__init__.c @@ -11,6 +11,8 @@ #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/audiobusio/__init__.h" +#include "shared-bindings/audiobusio/I2S.h" +#include "shared-bindings/audiobusio/I2SIn.h" #include "shared-bindings/audiobusio/I2SOut.h" #include "shared-bindings/audiobusio/PDMIn.h" @@ -27,6 +29,8 @@ static const mp_rom_map_elem_t audiobusio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiobusio) }, + { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&audiobusio_i2s_type) }, + { MP_ROM_QSTR(MP_QSTR_I2SIn), MP_ROM_PTR(&audiobusio_i2sin_type) }, { MP_ROM_QSTR(MP_QSTR_I2SOut), MP_ROM_PTR(&audiobusio_i2sout_type) }, { MP_ROM_QSTR(MP_QSTR_PDMIn), MP_ROM_PTR(&audiobusio_pdmin_type) }, }; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy