From bfa7db1b22d0339220b22568de6a973f8b05d916 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 13 Nov 2024 14:15:19 -0600 Subject: [PATCH 01/22] Initial build of I2SIn. --- ports/raspberrypi/audio_dma.c | 18 +- ports/raspberrypi/audio_dma.h | 4 +- .../raspberrypi/common-hal/audiobusio/I2SIn.c | 319 ++++++++++++++++++ .../raspberrypi/common-hal/audiobusio/I2SIn.h | 41 +++ .../common-hal/audiobusio/I2SOut.c | 3 +- .../common-hal/audiopwmio/PWMAudioOut.c | 3 +- py/circuitpy_defns.mk | 1 + py/circuitpy_mpconfig.mk | 4 + shared-bindings/audiobusio/I2SIn.c | 191 +++++++++++ shared-bindings/audiobusio/I2SIn.h | 28 ++ shared-bindings/audiobusio/__init__.c | 2 + 11 files changed, 607 insertions(+), 7 deletions(-) create mode 100644 ports/raspberrypi/common-hal/audiobusio/I2SIn.c create mode 100644 ports/raspberrypi/common-hal/audiobusio/I2SIn.h create mode 100644 shared-bindings/audiobusio/I2SIn.c create mode 100644 shared-bindings/audiobusio/I2SIn.h diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index c837bc116d48d..ea47372e317e6 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -118,6 +118,10 @@ 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->record) { + return; + } + size_t dma_channel = dma->channel[buffer_idx]; audioio_get_buffer_result_t get_buffer_result; @@ -174,7 +178,8 @@ audio_dma_result audio_dma_setup_playback( uint8_t output_resolution, uint32_t output_register_address, uint8_t dma_trigger_source, - bool swap_channel) { + bool swap_channel, + bool record) { // Use two DMA channels to play because the DMA can't wrap to itself without the // buffer being power of two aligned. @@ -204,6 +209,7 @@ audio_dma_result audio_dma_setup_playback( dma->sample_resolution = audiosample_bits_per_sample(sample); dma->output_register_address = output_register_address; dma->swap_channel = swap_channel; + dma->record = record; audiosample_reset_buffer(sample, single_channel_output, audio_channel); @@ -262,14 +268,18 @@ audio_dma_result audio_dma_setup_playback( 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); + channel_config_set_read_increment(&c, !record); + channel_config_set_write_increment(&c, record); // 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_set_write_addr(dma->channel[i], (void *)output_register_address, false /* trigger */); + if (!record) { + dma_channel_set_write_addr(dma->channel[i], (void *)output_register_address, false /* trigger */); + } else { + dma_channel_set_read_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 diff --git a/ports/raspberrypi/audio_dma.h b/ports/raspberrypi/audio_dma.h index 7c33a9e2ac319..8e54b3b7f88a3 100644 --- a/ports/raspberrypi/audio_dma.h +++ b/ports/raspberrypi/audio_dma.h @@ -31,6 +31,7 @@ typedef struct { bool output_signed; bool playing_in_progress; bool swap_channel; + bool record; } audio_dma_t; typedef enum { @@ -62,7 +63,8 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma, uint8_t output_resolution, uint32_t output_register_address, uint8_t dma_trigger_source, - bool swap_channel); + bool swap_channel, + bool record); void audio_dma_stop(audio_dma_t *dma); bool audio_dma_get_playing(audio_dma_t *dma); diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..2df34395c6408 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -0,0 +1,319 @@ +// 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" + +const uint16_t i2sin_program_mono[] = { +// pull block side 0b11 ; Load OSR with bits_per_sample + 0x98a0, +// mov x osr side 0b11 ; Save the value in x + 0xb827, +// nop side 0b01 + 0xa842, +// mov y x side 0b01 + 0xa841, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b01 + 0x4801, +// jmp y-- lbit side 0b01 + 0x0884, +// nop side 0b10 [1] + 0xb142, +// in pins 1 side 0b11 + 0x5801, +// mov y x side 0b11 + 0xb841, +// 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 + 0x98a0, +// mov x osr side 0b11 ; Save the value in x + 0xb827, +// nop side 0b10 + 0xb042, +// mov y x side 0b10 + 0xb041, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b10 + 0x5001, +// jmp y-- lbit side 0b10 + 0x1084, +// nop side 0b01 [1] + 0xa942, +// in pins 1 side 0b11 + 0x5801, +// mov y x side 0b11 + 0xb841, +// 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 + 0x98a0, +// mov x osr side 0b11 ; Save the value in x + 0xb827, +// nop side 0b01 + 0xa842, +// mov y x side 0b01 + 0xa841, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b01 + 0x4801, +// jmp y-- lbit side 0b01 + 0x0884, +// nop side 0b10 [1] + 0xb142, +// in pins 1 side 0b11 + 0x5801, +// mov y x side 0b11 + 0xb841, +// 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 + 0x98a0, +// mov x osr side 0b11 ; Save the value in x + 0xb827, +// nop side 0b10 + 0xb042, +// mov y x side 0b10 + 0xb041, +// lbit: +// nop side 0b00 [1] + 0xa142, +// in pins 1 side 0b10 + 0x5001, +// jmp y-- lbit side 0b10 + 0x1084, +// nop side 0b01 [1] + 0xa942, +// in pins 1 side 0b11 + 0x5801, +// mov y x side 0b11 + 0xb841, +// 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, 0, 0xffffffff, // out pin + data, 1, // in pins + 0, 0, // in pulls + NULL, 0, 0, 0x1f, // set pins + sideset_pin, 2, 0, 0x1f, // sideset pins + false, // No sideset enable + NULL, PULL_NONE, // jump pin + 0, // 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 }; + common_hal_rp2pio_statemachine_write(&self->state_machine, bit_width_data, 1, 1, false); + + audio_dma_result result = audio_dma_setup_playback( + &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], // output register + self->state_machine.rx_dreq, // data request line + false, // swap channel + true); // record + + 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")); + } +} + +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) { + + // TODO + return GET_BUFFER_DONE; +} + +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; + } +} diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.h b/ports/raspberrypi/common-hal/audiobusio/I2SIn.h new file mode 100644 index 0000000000000..7e263c559ee25 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.h @@ -0,0 +1,41 @@ +// 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; + uint32_t buffer_size; + uint8_t channel_count; + uint32_t sample_rate; + uint8_t bits_per_sample; + bool samples_signed; +} 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); diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SOut.c b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c index 60200e5c1214e..6bb7877cd0cd1 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SOut.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c @@ -266,7 +266,8 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self, bits_per_sample, (uint32_t)&self->state_machine.pio->txf[self->state_machine.state_machine], // output register self->state_machine.tx_dreq, // data request line - false); // swap channel + false, // swap channel + false); // record if (result == AUDIO_DMA_DMA_BUSY) { common_hal_audiobusio_i2sout_stop(self); diff --git a/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c index d4cf161e61977..9619dcce0223d 100644 --- a/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c +++ b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c @@ -204,7 +204,8 @@ void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t *self, BITS_PER_SAMPLE, (uint32_t)tx_register, // output register: PWM cc register 0x3b + pacing_timer, // data request line - self->swap_channel); + self->swap_channel, + false); // record if (result == AUDIO_DMA_DMA_BUSY) { common_hal_audiopwmio_pwmaudioout_stop(self); diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 61516561822c7..5eb7607abaefe 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -472,6 +472,7 @@ SRC_COMMON_HAL_ALL = \ analogio/AnalogIn.c \ analogio/AnalogOut.c \ analogio/__init__.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 1a831dbf314b5..38eba4182092d 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/I2SIn.c b/shared-bindings/audiobusio/I2SIn.c new file mode 100644 index 0000000000000..3ca28fdc2b9be --- /dev/null +++ b/shared-bindings/audiobusio/I2SIn.c @@ -0,0 +1,191 @@ +// 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) +//| mic.play(output) +//| +//| Recording samples to a buffer:: +//| +//| import array +//| import audiobusio +//| import board +//| +//| # Prepare a buffer to record into. +//| b = array.array("h", [0] * 1024) +//| with audiobusio.I2SIn(board.GP0, board.GP1, board.GP2, channel_count=1, sample_rate=16000) as mic: +//| mic.record(b, len(b)) +//| """ +//| ... +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 + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_I2SIn); + #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 = 16000} }, + { 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 + +//| 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); + +static void check_for_deinit(audiobusio_i2sin_obj_t *self) { + if (common_hal_audiobusio_i2sin_deinited(self)) { + raise_deinited_error(); + } +} + +//| 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__); + +//| def record(self, destination: WriteableBuffer, destination_length: int) -> None: +//| """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_i2sin_record(mp_obj_t self_in, mp_obj_t destination, mp_obj_t destination_length) { + audiobusio_i2sin_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + + // TODO + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_record); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(audiobusio_i2sin_record_obj, audiobusio_i2sin_record); + +#endif // CIRCUITPY_AUDIOBUSIO_I2SIN + +static const mp_rom_map_elem_t audiobusio_i2sin_locals_dict_table[] = { + #if CIRCUITPY_AUDIOBUSIO_I2SIN + // 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) }, + { MP_ROM_QSTR(MP_QSTR_record), MP_ROM_PTR(&audiobusio_i2sin_record_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 + .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..d6d8840dde1d4 --- /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 +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 diff --git a/shared-bindings/audiobusio/__init__.c b/shared-bindings/audiobusio/__init__.c index 70680b1cf16e4..ecc74eaf3e5ef 100644 --- a/shared-bindings/audiobusio/__init__.c +++ b/shared-bindings/audiobusio/__init__.c @@ -11,6 +11,7 @@ #include "shared-bindings/microcontroller/Pin.h" #include "shared-bindings/audiobusio/__init__.h" +#include "shared-bindings/audiobusio/I2SIn.h" #include "shared-bindings/audiobusio/I2SOut.h" #include "shared-bindings/audiobusio/PDMIn.h" @@ -27,6 +28,7 @@ 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_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) }, }; From 0daaeb42d52d9c557820d6147a165246e42b0550 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 13 Nov 2024 14:15:58 -0600 Subject: [PATCH 02/22] Enable I2SIn on RP2xxx --- ports/raspberrypi/mpconfigport.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/raspberrypi/mpconfigport.mk b/ports/raspberrypi/mpconfigport.mk index d619e78bd97a8..16b10942215dd 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 From 805a6b1cf9df57e4c3ebd33052736082be6852d1 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 18 Dec 2024 11:52:11 -0600 Subject: [PATCH 03/22] Remove `record` method. --- shared-bindings/audiobusio/I2SIn.c | 41 +----------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/shared-bindings/audiobusio/I2SIn.c b/shared-bindings/audiobusio/I2SIn.c index 3ca28fdc2b9be..b67f4619f61d0 100644 --- a/shared-bindings/audiobusio/I2SIn.c +++ b/shared-bindings/audiobusio/I2SIn.c @@ -52,17 +52,6 @@ //| mic = audiobusio.I2SIn(board.GP0, board.GP1, board.GP2, channel_count=1, sample_rate=16000) //| dac = audiopwmio.PWMAudioOut(board.GP3) //| mic.play(output) -//| -//| Recording samples to a buffer:: -//| -//| import array -//| import audiobusio -//| import board -//| -//| # Prepare a buffer to record into. -//| b = array.array("h", [0] * 1024) -//| with audiobusio.I2SIn(board.GP0, board.GP1, board.GP2, channel_count=1, sample_rate=16000) as mic: -//| mic.record(b, len(b)) //| """ //| ... 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) { @@ -113,12 +102,6 @@ static mp_obj_t audiobusio_i2sin_deinit(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(audiobusio_i2sin_deinit_obj, audiobusio_i2sin_deinit); -static void check_for_deinit(audiobusio_i2sin_obj_t *self) { - if (common_hal_audiobusio_i2sin_deinited(self)) { - raise_deinited_error(); - } -} - //| def __enter__(self) -> I2SIn: //| """No-op used by Context Managers.""" //| ... @@ -127,6 +110,7 @@ static void check_for_deinit(audiobusio_i2sin_obj_t *self) { //| 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]); @@ -134,28 +118,6 @@ static mp_obj_t audiobusio_i2sin_obj___exit__(size_t n_args, const mp_obj_t *arg } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiobusio_i2sin___exit___obj, 4, 4, audiobusio_i2sin_obj___exit__); -//| def record(self, destination: WriteableBuffer, destination_length: int) -> None: -//| """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_i2sin_record(mp_obj_t self_in, mp_obj_t destination, mp_obj_t destination_length) { - audiobusio_i2sin_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_for_deinit(self); - - // TODO - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_record); - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_3(audiobusio_i2sin_record_obj, audiobusio_i2sin_record); - #endif // CIRCUITPY_AUDIOBUSIO_I2SIN static const mp_rom_map_elem_t audiobusio_i2sin_locals_dict_table[] = { @@ -164,7 +126,6 @@ static const mp_rom_map_elem_t audiobusio_i2sin_locals_dict_table[] = { { 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) }, - { MP_ROM_QSTR(MP_QSTR_record), MP_ROM_PTR(&audiobusio_i2sin_record_obj) }, #endif // CIRCUITPY_AUDIOBUSIO_I2SIN }; static MP_DEFINE_CONST_DICT(audiobusio_i2sin_locals_dict, audiobusio_i2sin_locals_dict_table); From e30e234e4713c7339b702cec3960b2e57d2d404d Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 18 Dec 2024 11:53:24 -0600 Subject: [PATCH 04/22] Update audio dma to support independent input dma. --- ports/raspberrypi/audio_dma.c | 487 +++++++++++++----- ports/raspberrypi/audio_dma.h | 45 +- .../raspberrypi/common-hal/audiobusio/I2SIn.c | 24 +- .../common-hal/audiobusio/I2SOut.c | 3 +- .../common-hal/audiopwmio/PWMAudioOut.c | 3 +- 5 files changed, 421 insertions(+), 141 deletions(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 5f204f6cb8e22..34ce92303c699 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,11 +118,11 @@ 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->record) { + if (!dma->output_register_address) { return; } - 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; @@ -131,7 +131,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); return; } @@ -141,9 +141,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) { @@ -157,18 +157,18 @@ 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->playing_in_progress = false; } } } } -// 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, @@ -177,25 +177,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, - bool swap_channel, - bool record) { + uint8_t output_dma_trigger_source, + uint32_t input_register_address, + uint8_t input_dma_trigger_source, + bool swap_channel) { + + 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; + } - // 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; - } + 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; @@ -208,8 +240,8 @@ audio_dma_result audio_dma_setup_playback( dma->output_resolution = output_resolution; dma->sample_resolution = audiosample_bits_per_sample(sample); dma->output_register_address = output_register_address; + dma->input_register_address = input_register_address; dma->swap_channel = swap_channel; - dma->record = record; audiosample_reset_buffer(sample, single_channel_output, audio_channel); @@ -231,18 +263,36 @@ audio_dma_result audio_dma_setup_playback( max_buffer_length /= dma->sample_spacing; } - dma->buffer[0] = (uint8_t *)m_realloc(dma->buffer[0], max_buffer_length); - dma->buffer_length[0] = max_buffer_length; - if (dma->buffer[0] == NULL) { - return AUDIO_DMA_MEMORY_ERROR; + if (output_register_address) { + dma->output_buffer[0] = (uint8_t *)m_realloc(dma->output_buffer[0], max_buffer_length); + dma->output_buffer_length[0] = max_buffer_length; + if (dma->output_buffer[0] == NULL) { + return AUDIO_DMA_MEMORY_ERROR; + } + + if (!single_buffer) { + dma->output_buffer[1] = (uint8_t *)m_realloc(dma->output_buffer[1], max_buffer_length); + dma->output_buffer_length[1] = max_buffer_length; + if (dma->output_buffer[1] == NULL) { + return AUDIO_DMA_MEMORY_ERROR; + } + } } - if (!single_buffer) { - dma->buffer[1] = (uint8_t *)m_realloc(dma->buffer[1], max_buffer_length); - dma->buffer_length[1] = max_buffer_length; - if (dma->buffer[1] == NULL) { + if (input_register_address) { + dma->input_buffer[0] = (uint8_t *)m_realloc(dma->input_buffer[0], max_buffer_length); + dma->input_buffer_length[0] = max_buffer_length; + if (dma->input_buffer[0] == NULL) { return AUDIO_DMA_MEMORY_ERROR; } + + if (!single_buffer) { + dma->input_buffer[1] = (uint8_t *)m_realloc(dma->input_buffer[1], max_buffer_length); + 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; @@ -265,72 +315,158 @@ 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, !record); - channel_config_set_write_increment(&c, record); - - // 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 */); - - if (!record) { - dma_channel_set_write_addr(dma->channel[i], (void *)output_register_address, false /* trigger */); - } else { - dma_channel_set_read_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; - - // Load the first two blocks up front. - audio_dma_load_next_block(dma, 0); - if (!single_buffer) { - audio_dma_load_next_block(dma, 1); - } - - // 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 { - // 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); + 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->playing_in_progress = true; - dma_channel_start(dma->channel[0]); + 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; + + // Load the first two blocks up front. + audio_dma_load_next_block(dma, 0); + if (!single_buffer) { + audio_dma_load_next_block(dma, 1); + } + + // 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 { + // 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->output_channel[0]); + } + + 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 { + // Enable our DMA channels on DMA_IRQ_1 to the CPU. + dma_hw->inte1 |= (1 << dma->input_channel[0]) | (1 << dma->input_channel[1]); + irq_set_mask_enabled(1 << DMA_IRQ_1, true); + } + + dma->last_record = -1; + dma->recording_in_progress = true; + dma_channel_start(dma->input_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) { // 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) { @@ -342,13 +478,13 @@ 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]; + size_t channel = dma->output_channel[i]; if (channel == NUM_DMA_CHANNELS) { // Channel not in use. continue; } - dma_channel_config c = dma_channel_get_default_config(dma->channel[i]); + dma_channel_config c = dma_channel_get_default_config(dma->output_channel[i]); channel_config_set_enable(&c, false); dma_channel_set_config(channel, &c, false /* trigger */); @@ -361,38 +497,105 @@ void audio_dma_stop(audio_dma_t *dma) { dma_channel_set_trans_count(channel, 0, false /* trigger */); dma_channel_unclaim(channel); MP_STATE_PORT(playing_audio)[channel] = NULL; - dma->channel[i] = NUM_DMA_CHANNELS; + dma->output_channel[i] = NUM_DMA_CHANNELS; } dma->playing_in_progress = false; // 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]; + if (channel == NUM_DMA_CHANNELS) { + // Channel not in use. + continue; + } + + dma_channel_config c = dma_channel_get_default_config(dma->input_channel[i]); + 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->input_channel[i] = NUM_DMA_CHANNELS; + } + 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; } 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->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->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->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; } } 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; } - uint32_t control = dma_hw->ch[dma->channel[0]].ctrl_trig; - + uint32_t control = dma_hw->ch[channel].ctrl_trig; return (control & DMA_CH0_CTRL_TRIG_EN_BITS) == 0; } @@ -400,6 +603,9 @@ 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); @@ -411,6 +617,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); } @@ -418,28 +627,60 @@ 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->output_buffer[0] = NULL; + dma->output_buffer[1] = NULL; + + dma->output_channel[0] = NUM_DMA_CHANNELS; + dma->output_channel[1] = NUM_DMA_CHANNELS; - dma->channel[0] = NUM_DMA_CHANNELS; - dma->channel[1] = NUM_DMA_CHANNELS; + dma->input_buffer[0] = NULL; + dma->input_buffer[1] = NULL; + + dma->input_channel[0] = NUM_DMA_CHANNELS; + dma->input_channel[1] = NUM_DMA_CHANNELS; } void audio_dma_deinit(audio_dma_t *dma) { - m_free(dma->buffer[0]); - dma->buffer[0] = NULL; + m_free(dma->output_buffer[0]); + dma->output_buffer[0] = NULL; + + m_free(dma->output_buffer[1]); + dma->output_buffer[1] = NULL; - m_free(dma->buffer[1]); - dma->buffer[1] = NULL; + m_free(dma->input_buffer[0]); + dma->input_buffer[0] = NULL; + + m_free(dma->input_buffer[1]); + dma->input_buffer[1] = NULL; } 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->last_record >= 2) { + return NULL; + } + uint8_t *buffer = dma->input_buffer[dma->last_record]; + dma->last_record = -1; + return buffer; +} + +bool audio_dma_has_buffer(audio_dma_t *dma) { + return dma->input_register_address && dma->last_record < 2; +} + // 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. @@ -455,14 +696,14 @@ static void dma_callback_fun(void *arg) { dma->channels_to_load_mask = 0; common_hal_mcu_enable_interrupts(); - // Load the blocks for the requested channels. + // Load the blocks for the requested output channels. uint32_t channel = 0; while (channels_to_load_mask) { if (channels_to_load_mask & 1) { - if (dma->channel[0] == channel) { + if (dma->output_channel[0] == channel) { audio_dma_load_next_block(dma, 0); } - if (dma->channel[1] == channel) { + if (dma->output_channel[1] == channel) { audio_dma_load_next_block(dma, 1); } } @@ -506,6 +747,11 @@ void __not_in_flash_func(isr_dma_1)(void) { // completed by the time callback_add() / dma_complete() returned. This // affected PIO continuous write more than audio. dma_hw->ints1 = mask; + if (MP_STATE_PORT(recording_audio)[i] != NULL) { + audio_dma_t *dma = MP_STATE_PORT(recording_audio)[i]; + // Rotate buffer to continue recording. + dma->last_record = (uint8_t)(i != dma->input_channel[0]); + } if (MP_STATE_PORT(background_pio)[i] != NULL) { rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(background_pio)[i]; rp2pio_statemachine_dma_complete_read(pio, i); @@ -514,4 +760,5 @@ void __not_in_flash_func(isr_dma_1)(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 8e54b3b7f88a3..e923a4cf006d8 100644 --- a/ports/raspberrypi/audio_dma.h +++ b/ports/raspberrypi/audio_dma.h @@ -13,12 +13,16 @@ typedef struct { mp_obj_t sample; - uint8_t *buffer[2]; - size_t buffer_length[2]; + uint8_t *output_buffer[2]; + size_t output_buffer_length[2]; + uint8_t *input_buffer[2]; + 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; @@ -30,8 +34,9 @@ typedef struct { bool unsigned_to_signed; bool output_signed; bool playing_in_progress; + bool recording_in_progress; bool swap_channel; - bool record; + uint8_t last_record; } audio_dma_t; typedef enum { @@ -54,6 +59,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, @@ -63,11 +81,26 @@ audio_dma_result audio_dma_setup_playback(audio_dma_t *dma, uint8_t output_resolution, uint32_t output_register_address, uint8_t dma_trigger_source, - bool swap_channel, - bool record); + 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); +bool audio_dma_has_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/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index 2df34395c6408..c8d90c5759591 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -211,7 +211,7 @@ void common_hal_audiobusio_i2sin_construct(audiobusio_i2sin_obj_t *self, data, 1, // in pins 0, 0, // in pulls NULL, 0, 0, 0x1f, // set pins - sideset_pin, 2, 0, 0x1f, // sideset pins + sideset_pin, 2, false, 0, 0x1f, // sideset pins false, // No sideset enable NULL, PULL_NONE, // jump pin 0, // wait gpio pins @@ -268,11 +268,7 @@ void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, common_hal_rp2pio_statemachine_restart(&self->state_machine); - // Send bit width - const uint8_t bit_width_data[1] = { self->bits_per_sample }; - common_hal_rp2pio_statemachine_write(&self->state_machine, bit_width_data, 1, 1, false); - - audio_dma_result result = audio_dma_setup_playback( + audio_dma_result result = audio_dma_setup_record( &self->dma, &self, true, @@ -280,10 +276,9 @@ void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, channel, // audio channel true, // output signed self->bits_per_sample, // output resolution - (uint32_t)&self->state_machine.pio->rxf[self->state_machine.state_machine], // output register + (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 - true); // record + false); // swap channel if (result == AUDIO_DMA_DMA_BUSY) { common_hal_rp2pio_statemachine_stop(&self->state_machine); @@ -300,8 +295,15 @@ audioio_get_buffer_result_t audiobusio_i2sin_get_buffer(audiobusio_i2sin_obj_t * uint8_t **buffer, uint32_t *buffer_length) { - // TODO - return GET_BUFFER_DONE; + // TODO: single_channel_output + + if (!audio_dma_has_buffer(&self->dma)) { + return GET_BUFFER_ERROR; + } + + *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, diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SOut.c b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c index 50177388bf7e9..90e92a4266589 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SOut.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SOut.c @@ -266,8 +266,7 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self, bits_per_sample, (uint32_t)&self->state_machine.pio->txf[self->state_machine.state_machine], // output register self->state_machine.tx_dreq, // data request line - false, // swap channel - false); // record + false); // swap channel if (result == AUDIO_DMA_DMA_BUSY) { common_hal_audiobusio_i2sout_stop(self); diff --git a/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c index 9619dcce0223d..d4cf161e61977 100644 --- a/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c +++ b/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c @@ -204,8 +204,7 @@ void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t *self, BITS_PER_SAMPLE, (uint32_t)tx_register, // output register: PWM cc register 0x3b + pacing_timer, // data request line - self->swap_channel, - false); // record + self->swap_channel); if (result == AUDIO_DMA_DMA_BUSY) { common_hal_audiopwmio_pwmaudioout_stop(self); From f1e6f01996660b9b1d5c48cbef0cf1d010978b0c Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 18 Dec 2024 13:18:48 -0600 Subject: [PATCH 05/22] Fix pointer issue with dma setup. --- ports/raspberrypi/common-hal/audiobusio/I2SIn.c | 2 +- shared-bindings/audiobusio/I2SIn.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index c8d90c5759591..9fefb798b9389 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -270,7 +270,7 @@ void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, audio_dma_result result = audio_dma_setup_record( &self->dma, - &self, + self, true, single_channel_output, // single channel channel, // audio channel diff --git a/shared-bindings/audiobusio/I2SIn.c b/shared-bindings/audiobusio/I2SIn.c index b67f4619f61d0..5513d66f2eae0 100644 --- a/shared-bindings/audiobusio/I2SIn.c +++ b/shared-bindings/audiobusio/I2SIn.c @@ -57,6 +57,7 @@ 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 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[] = { From 9a0d1c24bf2f4617ac4b3715f85b0a350793b416 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 18 Dec 2024 13:25:54 -0600 Subject: [PATCH 06/22] Only reset buffer if output is enabled. --- ports/raspberrypi/audio_dma.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 34ce92303c699..067c069f5342b 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -243,7 +243,9 @@ audio_dma_result audio_dma_setup( 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. From d0e2b0198fc3d4de841ed1d4ee115bb59716826d Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 18 Dec 2024 14:15:36 -0600 Subject: [PATCH 07/22] Rename `last_record` to `last_index`. --- ports/raspberrypi/audio_dma.c | 14 +++++++------- ports/raspberrypi/audio_dma.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 067c069f5342b..89bf45e24374c 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -411,7 +411,7 @@ audio_dma_result audio_dma_setup( irq_set_mask_enabled(1 << DMA_IRQ_1, true); } - dma->last_record = -1; + dma->input_index = -1; dma->recording_in_progress = true; dma_channel_start(dma->input_channel[0]); } @@ -671,16 +671,16 @@ bool audio_dma_get_recording(audio_dma_t *dma) { } uint8_t *audio_dma_get_buffer(audio_dma_t *dma) { - if (!dma->input_register_address || dma->last_record >= 2) { + if (!dma->input_register_address || dma->input_index >= 2) { return NULL; } - uint8_t *buffer = dma->input_buffer[dma->last_record]; - dma->last_record = -1; + uint8_t *buffer = dma->input_buffer[dma->input_index]; + dma->input_index = -1; return buffer; } bool audio_dma_has_buffer(audio_dma_t *dma) { - return dma->input_register_address && dma->last_record < 2; + return dma->input_register_address && dma->input_index < 2; } // WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls @@ -751,8 +751,8 @@ void __not_in_flash_func(isr_dma_1)(void) { dma_hw->ints1 = mask; if (MP_STATE_PORT(recording_audio)[i] != NULL) { audio_dma_t *dma = MP_STATE_PORT(recording_audio)[i]; - // Rotate buffer to continue recording. - dma->last_record = (uint8_t)(i != dma->input_channel[0]); + // Update last recorded buffer. + dma->input_index = (uint8_t)(i != dma->input_channel[0]); } if (MP_STATE_PORT(background_pio)[i] != NULL) { rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(background_pio)[i]; diff --git a/ports/raspberrypi/audio_dma.h b/ports/raspberrypi/audio_dma.h index e923a4cf006d8..e10ab3dc05380 100644 --- a/ports/raspberrypi/audio_dma.h +++ b/ports/raspberrypi/audio_dma.h @@ -36,7 +36,7 @@ typedef struct { bool playing_in_progress; bool recording_in_progress; bool swap_channel; - uint8_t last_record; + uint8_t input_index; } audio_dma_t; typedef enum { From 76ac311c589b83b99c8a0a50d99b3924f9f5fa3e Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 18 Dec 2024 14:17:29 -0600 Subject: [PATCH 08/22] Handle dma input write destination. --- ports/raspberrypi/audio_dma.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 89bf45e24374c..b7922c66e6a67 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -343,6 +343,8 @@ audio_dma_result audio_dma_setup( 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 */); } } @@ -753,6 +755,8 @@ void __not_in_flash_func(isr_dma_1)(void) { audio_dma_t *dma = MP_STATE_PORT(recording_audio)[i]; // Update last recorded buffer. dma->input_index = (uint8_t)(i != dma->input_channel[0]); + 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)[i] != NULL) { rp2pio_statemachine_obj_t *pio = MP_STATE_PORT(background_pio)[i]; From 55d3252f3795ce1031922bf9278d576a6a7d4905 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 18 Dec 2024 14:41:34 -0600 Subject: [PATCH 09/22] Send bit width to start PIO program on `reset_buffer`. --- ports/raspberrypi/audio_dma.c | 1 + ports/raspberrypi/common-hal/audiobusio/I2SIn.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index b7922c66e6a67..8985794f01be3 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -755,6 +755,7 @@ void __not_in_flash_func(isr_dma_1)(void) { 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 */); } diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index 9fefb798b9389..47fc08d81b6b0 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -268,6 +268,10 @@ void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, common_hal_rp2pio_statemachine_restart(&self->state_machine); + // Send bit width + const uint8_t bit_width_data[1] = { self->bits_per_sample }; + 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, From be48374086d2f4e07fcc7691cf08295314af0df5 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Sat, 21 Dec 2024 12:37:46 -0600 Subject: [PATCH 10/22] Fix errors within PIO program. --- .../raspberrypi/common-hal/audiobusio/I2SIn.c | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index 47fc08d81b6b0..0cf42dbab8ece 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -19,27 +19,27 @@ #include "audio_dma.h" const uint16_t i2sin_program_mono[] = { -// pull block side 0b11 ; Load OSR with bits_per_sample +// pull block side 0b11 ; Load OSR with bits_per_sample-2 0x98a0, -// mov x osr side 0b11 ; Save the value in x - 0xb827, +// out y 8 side 0b11 ; Save the value in y + 0x7848, // nop side 0b01 0xa842, -// mov y x side 0b01 - 0xa841, +// mov x y side 0b01 + 0xa822, // lbit: // nop side 0b00 [1] 0xa142, // in pins 1 side 0b01 0x4801, -// jmp y-- lbit side 0b01 - 0x0884, +// jmp x-- lbit side 0b01 + 0x0844, // nop side 0b10 [1] 0xb142, // in pins 1 side 0b11 0x5801, -// mov y x side 0b11 - 0xb841, +// mov x y side 0b11 + 0xb822, // rbit: // nop side 0b10 [1] 0xb142, @@ -54,27 +54,27 @@ const uint16_t i2sin_program_mono[] = { }; const uint16_t i2sin_program_mono_swap[] = { -// pull block side 0b11 ; Load OSR with bits_per_sample +// pull block side 0b11 ; Load OSR with bits_per_sample-2 0x98a0, -// mov x osr side 0b11 ; Save the value in x - 0xb827, +// out y 8 side 0b11 ; Save the value in y + 0x7848, // nop side 0b10 0xb042, -// mov y x side 0b10 - 0xb041, +// mov x y side 0b10 + 0xb022, // lbit: // nop side 0b00 [1] 0xa142, // in pins 1 side 0b10 0x5001, -// jmp y-- lbit side 0b10 - 0x1084, +// jmp x-- lbit side 0b10 + 0x1044, // nop side 0b01 [1] 0xa942, // in pins 1 side 0b11 0x5801, -// mov y x side 0b11 - 0xb841, +// mov x y side 0b11 + 0xb822, // rbit: // nop side 0b01 [1] 0xa942, @@ -92,27 +92,27 @@ const uint16_t i2sin_program_stereo[] = { // ; /--- LRCLK // ; |/-- BCLK // ; || -// pull block side 0b11 ; Load OSR with bits_per_sample +// pull block side 0b11 ; Load OSR with bits_per_sample-2 0x98a0, -// mov x osr side 0b11 ; Save the value in x - 0xb827, +// out y 8 side 0b11 ; Save the value in y + 0x7848, // nop side 0b01 0xa842, -// mov y x side 0b01 - 0xa841, +// mov x y side 0b01 + 0xa822, // lbit: // nop side 0b00 [1] 0xa142, // in pins 1 side 0b01 0x4801, -// jmp y-- lbit side 0b01 - 0x0884, +// jmp x-- lbit side 0b01 + 0x0844, // nop side 0b10 [1] 0xb142, // in pins 1 side 0b11 0x5801, -// mov y x side 0b11 - 0xb841, +// mov x y side 0b11 + 0xb822, // rbit: // nop side 0b10 [1] 0xb142, @@ -130,27 +130,27 @@ const uint16_t i2sin_program_stereo_swap[] = { // ; /--- LRCLK // ; |/-- BCLK // ; || -// pull block side 0b11 ; Load OSR with bits_per_sample +// pull block side 0b11 ; Load OSR with bits_per_sample-2 0x98a0, -// mov x osr side 0b11 ; Save the value in x - 0xb827, +// out y 8 side 0b11 ; Save the value in y + 0x7848, // nop side 0b10 0xb042, -// mov y x side 0b10 - 0xb041, +// mov x y side 0b10 + 0xb022, // lbit: // nop side 0b00 [1] 0xa142, // in pins 1 side 0b10 0x5001, -// jmp y-- lbit side 0b10 - 0x1084, +// jmp x-- lbit side 0b10 + 0x1044, // nop side 0b01 [1] 0xa942, // in pins 1 side 0b11 0x5801, -// mov y x side 0b11 - 0xb841, +// mov x y side 0b11 + 0xb822, // rbit: // nop side 0b01 [1] 0xa942, @@ -207,10 +207,10 @@ void common_hal_audiobusio_i2sin_construct(audiobusio_i2sin_obj_t *self, sample_rate * bits_per_sample * 2 * 4, // Frequency based on sample rate and bit width NULL, 0, // init NULL, 0, // may_exec - NULL, 1, 0, 0xffffffff, // out pin + NULL, 1, 0, 0, // out pin data, 1, // in pins 0, 0, // in pulls - NULL, 0, 0, 0x1f, // set pins + NULL, 1, 0, 0, // set pins sideset_pin, 2, false, 0, 0x1f, // sideset pins false, // No sideset enable NULL, PULL_NONE, // jump pin @@ -269,7 +269,7 @@ void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, common_hal_rp2pio_statemachine_restart(&self->state_machine); // Send bit width - const uint8_t bit_width_data[1] = { self->bits_per_sample }; + 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( From 3c97fd8df167f374037311ad64620c6be914831f Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Sat, 21 Dec 2024 12:40:04 -0600 Subject: [PATCH 11/22] Run background tasks while waiting for next buffer. --- ports/raspberrypi/common-hal/audiobusio/I2SIn.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index 0cf42dbab8ece..688f42a6c51ba 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -301,8 +301,9 @@ audioio_get_buffer_result_t audiobusio_i2sin_get_buffer(audiobusio_i2sin_obj_t * // TODO: single_channel_output - if (!audio_dma_has_buffer(&self->dma)) { - return GET_BUFFER_ERROR; + // Do other things while we wait for the buffer to fill. + while (!audio_dma_has_buffer(&self->dma)) { + RUN_BACKGROUND_TASKS; } *buffer_length = self->buffer_size; From 15384bcc04d8c23690284e4e38e74dadfda4cc48 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Mon, 23 Dec 2024 10:34:03 -0600 Subject: [PATCH 12/22] Add bidirectional I2S class. --- ports/raspberrypi/common-hal/audiobusio/I2S.c | 323 ++++++++++++++++++ ports/raspberrypi/common-hal/audiobusio/I2S.h | 45 +++ .../raspberrypi/common-hal/audiobusio/I2SIn.c | 4 + .../raspberrypi/common-hal/audiobusio/I2SIn.h | 4 + .../common-hal/audiobusio/I2SOut.c | 4 + .../common-hal/audiobusio/I2SOut.h | 4 + py/circuitpy_defns.mk | 1 + shared-bindings/audiobusio/I2S.c | 315 +++++++++++++++++ shared-bindings/audiobusio/I2S.h | 37 ++ shared-bindings/audiobusio/I2SIn.c | 12 +- shared-bindings/audiobusio/I2SIn.h | 4 +- shared-bindings/audiobusio/I2SOut.c | 8 +- shared-bindings/audiobusio/I2SOut.h | 4 +- shared-bindings/audiobusio/__init__.c | 2 + 14 files changed, 753 insertions(+), 14 deletions(-) create mode 100644 ports/raspberrypi/common-hal/audiobusio/I2S.c create mode 100644 ports/raspberrypi/common-hal/audiobusio/I2S.h create mode 100644 shared-bindings/audiobusio/I2S.c create mode 100644 shared-bindings/audiobusio/I2S.h diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c new file mode 100644 index 0000000000000..40dccf67395c1 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -0,0 +1,323 @@ +// 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) \ + { \ +/* /--- LRCLK */ \ +/* |/-- BCLK */ \ +/* || */ \ +/* 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, 0, 0xffffffff, // out pin + data_in, 1, // in pins + 0, 0, // in pulls + NULL, 1, 0, 0, // set pins + sideset_pin, 2, false, 0, 0x1f, // sideset pins + false, // No sideset enable + NULL, PULL_NONE, // jump pin + 0, // 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); +} + +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) { + + if (self->dma.output_channel[0] != NUM_DMA_CHANNELS || self->dma.input_channel[0] != NUM_DMA_CHANNELS) { + if (self->state_machine.out) { + audio_dma_stop_output(&self->dma); + } + if (self->state_machine.in) { + audio_dma_stop_input(&self->dma); + } + audio_dma_deinit(&self->dma); + } + + 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); +} + +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); + 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.")); + } + + i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample); +} + +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.")); + } + + // Do other things while we wait for the buffer to fill. + while (!audio_dma_has_buffer(&self->dma)) { + // BUG: Issue with interrupt? + if (self->state_machine.out) { + common_hal_mcu_delay_us(1000000 / self->sample_rate); + } else { + RUN_BACKGROUND_TASKS; + } + } + + *buffer_length = self->buffer_size; + *buffer = audio_dma_get_buffer(&self->dma); + 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..f03f3ff9b0a79 --- /dev/null +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.h @@ -0,0 +1,45 @@ +// 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; +} 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); diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index 688f42a6c51ba..cae80f7db5e96 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -18,6 +18,8 @@ #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, @@ -324,3 +326,5 @@ void audiobusio_i2sin_get_buffer_structure(audiobusio_i2sin_obj_t *self, bool si *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 index 7e263c559ee25..353fdd6cf4ec8 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.h +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.h @@ -14,6 +14,8 @@ #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; @@ -39,3 +41,5 @@ audioio_get_buffer_result_t audiobusio_i2sin_get_buffer(audiobusio_i2sin_obj_t * 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 90e92a4266589..1510dce8a39ec 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[] = { // ; Load the next set of samples // ; /--- LRCLK @@ -308,3 +310,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/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 2d366d22bb59b..311a0af335cc5 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -472,6 +472,7 @@ 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 \ diff --git a/shared-bindings/audiobusio/I2S.c b/shared-bindings/audiobusio/I2S.c new file mode 100644 index 0000000000000..1823f40bf0ba7 --- /dev/null +++ b/shared-bindings/audiobusio/I2S.c @@ -0,0 +1,315 @@ +// 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) +//| mic.play(output) +//| """ +//| ... +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 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_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..50abd675fef3f --- /dev/null +++ b/shared-bindings/audiobusio/I2S.h @@ -0,0 +1,37 @@ +// 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); + +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 index 5513d66f2eae0..eecc9fdc0382d 100644 --- a/shared-bindings/audiobusio/I2SIn.c +++ b/shared-bindings/audiobusio/I2SIn.c @@ -51,11 +51,11 @@ //| //| mic = audiobusio.I2SIn(board.GP0, board.GP1, board.GP2, channel_count=1, sample_rate=16000) //| dac = audiopwmio.PWMAudioOut(board.GP3) -//| mic.play(output) +//| 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 + #if !CIRCUITPY_AUDIOBUSIO_I2SIN || CIRCUITPY_AUDIOBUSIO_I2SOUT mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_I2SIn); return NULL; // Not reachable. #else @@ -66,7 +66,7 @@ static mp_obj_t audiobusio_i2sin_make_new(const mp_obj_type_t *type, size_t n_ar { 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 = 16000} }, + { 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} }, }; @@ -91,7 +91,7 @@ static mp_obj_t audiobusio_i2sin_make_new(const mp_obj_type_t *type, size_t n_ar #endif } -#if CIRCUITPY_AUDIOBUSIO_I2SIN +#if CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT //| def deinit(self) -> None: //| """Deinitialises the I2SIn and releases any hardware resources for reuse.""" @@ -122,7 +122,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiobusio_i2sin___exit___obj, 4, 4, #endif // CIRCUITPY_AUDIOBUSIO_I2SIN static const mp_rom_map_elem_t audiobusio_i2sin_locals_dict_table[] = { - #if CIRCUITPY_AUDIOBUSIO_I2SIN + #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) }, @@ -133,7 +133,7 @@ static MP_DEFINE_CONST_DICT(audiobusio_i2sin_locals_dict, audiobusio_i2sin_local static const audiosample_p_t audiobusio_i2sin_proto = { MP_PROTO_IMPLEMENT(MP_QSTR_protocol_audiosample) - #if CIRCUITPY_AUDIOBUSIO_I2SIN + #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, diff --git a/shared-bindings/audiobusio/I2SIn.h b/shared-bindings/audiobusio/I2SIn.h index d6d8840dde1d4..65d9867f8f2d9 100644 --- a/shared-bindings/audiobusio/I2SIn.h +++ b/shared-bindings/audiobusio/I2SIn.h @@ -12,7 +12,7 @@ extern const mp_obj_type_t audiobusio_i2sin_type; -#if CIRCUITPY_AUDIOBUSIO_I2SIN +#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, @@ -25,4 +25,4 @@ uint32_t common_hal_audiobusio_i2sin_get_sample_rate(audiobusio_i2sin_obj_t *sel 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 +#endif // CIRCUITPY_AUDIOBUSIO_I2SIN && !CIRCUITPY_AUDIOBUSIO_I2SOUT diff --git a/shared-bindings/audiobusio/I2SOut.c b/shared-bindings/audiobusio/I2SOut.c index 7c24e8a9efe81..2213a17eb4572 100644 --- a/shared-bindings/audiobusio/I2SOut.c +++ b/shared-bindings/audiobusio/I2SOut.c @@ -77,7 +77,7 @@ //| print("stopped")""" //| ... 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 @@ -87,7 +87,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); @@ -104,7 +104,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.""" @@ -234,7 +234,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 ecc74eaf3e5ef..2e90f9b6cce32 100644 --- a/shared-bindings/audiobusio/__init__.c +++ b/shared-bindings/audiobusio/__init__.c @@ -11,6 +11,7 @@ #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" @@ -28,6 +29,7 @@ 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) }, From dc6e67efa9d1d1657f7d5ad21e87a9f009b4e9d7 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Mon, 23 Dec 2024 10:35:00 -0600 Subject: [PATCH 13/22] Start input dma channel before output dma channel to allow blocks to load when bidirectional. --- ports/raspberrypi/audio_dma.c | 61 ++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 8985794f01be3..ae99bb0091ee6 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -348,6 +348,37 @@ audio_dma_result audio_dma_setup( } } + // 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 { + // Enable our DMA channels on DMA_IRQ_1 to the CPU. + dma_hw->inte1 |= (1 << dma->input_channel[0]) | (1 << dma->input_channel[1]); + irq_set_mask_enabled(1 << DMA_IRQ_1, true); + } + + dma->input_index = -1; + dma->recording_in_progress = true; + dma_channel_start(dma->input_channel[0]); + } + 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. @@ -388,36 +419,6 @@ audio_dma_result audio_dma_setup( dma_channel_start(dma->output_channel[0]); } - 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 { - // Enable our DMA channels on DMA_IRQ_1 to the CPU. - dma_hw->inte1 |= (1 << dma->input_channel[0]) | (1 << dma->input_channel[1]); - irq_set_mask_enabled(1 << DMA_IRQ_1, true); - } - - dma->input_index = -1; - dma->recording_in_progress = true; - dma_channel_start(dma->input_channel[0]); - } - return AUDIO_DMA_OK; } From ececb9ca4a4a08c93afa33709fef561e1b0ccc2c Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Mon, 23 Dec 2024 10:50:08 -0600 Subject: [PATCH 14/22] Remove bug comment. --- ports/raspberrypi/common-hal/audiobusio/I2S.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c index 40dccf67395c1..c1f4ae2626f9d 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -292,7 +292,6 @@ audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self // Do other things while we wait for the buffer to fill. while (!audio_dma_has_buffer(&self->dma)) { - // BUG: Issue with interrupt? if (self->state_machine.out) { common_hal_mcu_delay_us(1000000 / self->sample_rate); } else { From 1e9d455f244d544946c7f54d9be352cd1e273c6e Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Mon, 23 Dec 2024 11:23:59 -0600 Subject: [PATCH 15/22] Fix formatting errors. --- locale/circuitpython.pot | 38 +++++++++++++++---- ports/raspberrypi/common-hal/audiobusio/I2S.c | 35 ++++++++--------- shared-bindings/audiobusio/I2S.c | 5 +-- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 9ba7a0dd151e4..6e007a422d71a 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 "" @@ -218,7 +219,7 @@ msgstr "" #: ports/raspberrypi/bindings/cyw43/__init__.c py/argcheck.c py/objexcept.c #: shared-bindings/bitmapfilter/__init__.c shared-bindings/canio/CAN.c #: shared-bindings/digitalio/Pull.c shared-bindings/supervisor/__init__.c -#: shared-module/synthio/Synthesizer.c +#: shared-module/synthio/Biquad.c shared-module/synthio/Synthesizer.c msgid "%q must be of type %q or %q, not %q" msgstr "" @@ -630,6 +631,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 "" @@ -638,6 +641,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 "" @@ -1431,6 +1438,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" @@ -1463,6 +1472,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 "" @@ -1542,8 +1559,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 "" @@ -1900,6 +1917,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 "" @@ -1918,6 +1939,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 "" @@ -2020,6 +2045,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 "" @@ -2109,6 +2135,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" @@ -3383,10 +3411,6 @@ msgstr "" msgid "label redefined" msgstr "" -#: shared-bindings/audiomixer/MixerVoice.c -msgid "level must be between 0 and 1" -msgstr "" - #: py/objarray.c msgid "lhs and rhs should be compatible" msgstr "" diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c index c1f4ae2626f9d..f3952d83ff7cb 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -24,26 +24,23 @@ #define I2S_CODE(bits_per_sample, out, in, left_justified, swap) \ { \ -/* /--- LRCLK */ \ -/* |/-- BCLK */ \ -/* || */ \ -/* 00 */ pio_encode_set(pio_y, bits_per_sample - 2) | pio_encode_sideset(2, 0b01 << swap | left_justified << !swap) | pio_encode_delay(1), \ +/* 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), \ +/* 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), \ +/* 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 */ \ } @@ -171,7 +168,7 @@ void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self, 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); } diff --git a/shared-bindings/audiobusio/I2S.c b/shared-bindings/audiobusio/I2S.c index 1823f40bf0ba7..9d3c70c837748 100644 --- a/shared-bindings/audiobusio/I2S.c +++ b/shared-bindings/audiobusio/I2S.c @@ -26,7 +26,7 @@ //| data_out: Optional[microcontroller.Pin] = None, //| data_in: Optional[microcontroller.Pin] = None, //| main_clock: Optional[microcontroller.Pin] = None, -//| left_justified: bool = False +//| left_justified: bool = False, //| buffer_size: int = 512, //| channel_count: int = 2, //| sample_rate: int = 8000, @@ -90,7 +90,7 @@ //| a.play(wav) //| while a.playing: //| pass -//| print("stopped")""" +//| print("stopped") //| //| Playing an I2S input signal to a PWMAudioOut:: //| @@ -172,7 +172,6 @@ static void check_for_deinit(audiobusio_i2s_obj_t *self) { //| """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]); From c6bdd36f06284a096d32d4b609c85bd6d086ffd2 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Mon, 23 Dec 2024 11:30:27 -0600 Subject: [PATCH 16/22] Fix input playback example. --- shared-bindings/audiobusio/I2S.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-bindings/audiobusio/I2S.c b/shared-bindings/audiobusio/I2S.c index 9d3c70c837748..5f2d4b3aee81e 100644 --- a/shared-bindings/audiobusio/I2S.c +++ b/shared-bindings/audiobusio/I2S.c @@ -100,7 +100,7 @@ //| //| mic = audiobusio.I2S(board.GP0, board.GP1, data_in=board.GP2, channel_count=1, sample_rate=16000) //| dac = audiopwmio.PWMAudioOut(board.GP3) -//| mic.play(output) +//| 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) { From 05c719fa791f7e6977d9477c45bd37d552ce04dc Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Mon, 23 Dec 2024 12:06:23 -0600 Subject: [PATCH 17/22] Add empty files within other port families to fix builds. --- ports/atmel-samd/common-hal/audiobusio/I2S.c | 7 +++++++ ports/atmel-samd/common-hal/audiobusio/I2S.h | 11 +++++++++++ ports/atmel-samd/common-hal/audiobusio/I2SIn.c | 7 +++++++ ports/atmel-samd/common-hal/audiobusio/I2SIn.h | 11 +++++++++++ ports/espressif/common-hal/audiobusio/I2S.c | 7 +++++++ ports/espressif/common-hal/audiobusio/I2S.h | 11 +++++++++++ ports/espressif/common-hal/audiobusio/I2SIn.c | 7 +++++++ ports/espressif/common-hal/audiobusio/I2SIn.h | 11 +++++++++++ ports/mimxrt10xx/common-hal/audiobusio/I2S.c | 7 +++++++ ports/mimxrt10xx/common-hal/audiobusio/I2S.h | 11 +++++++++++ ports/mimxrt10xx/common-hal/audiobusio/I2SIn.c | 7 +++++++ ports/mimxrt10xx/common-hal/audiobusio/I2SIn.h | 11 +++++++++++ ports/nordic/common-hal/audiobusio/I2S.c | 7 +++++++ ports/nordic/common-hal/audiobusio/I2S.h | 11 +++++++++++ ports/nordic/common-hal/audiobusio/I2SIn.c | 7 +++++++ ports/nordic/common-hal/audiobusio/I2SIn.h | 11 +++++++++++ ports/raspberrypi/common-hal/audiobusio/I2S.c | 1 + ports/stm/common-hal/audiobusio/I2S.c | 7 +++++++ ports/stm/common-hal/audiobusio/I2S.h | 11 +++++++++++ ports/stm/common-hal/audiobusio/I2SIn.c | 7 +++++++ ports/stm/common-hal/audiobusio/I2SIn.h | 11 +++++++++++ 21 files changed, 181 insertions(+) create mode 100644 ports/atmel-samd/common-hal/audiobusio/I2S.c create mode 100644 ports/atmel-samd/common-hal/audiobusio/I2S.h create mode 100644 ports/atmel-samd/common-hal/audiobusio/I2SIn.c create mode 100644 ports/atmel-samd/common-hal/audiobusio/I2SIn.h create mode 100644 ports/espressif/common-hal/audiobusio/I2S.c create mode 100644 ports/espressif/common-hal/audiobusio/I2S.h create mode 100644 ports/espressif/common-hal/audiobusio/I2SIn.c create mode 100644 ports/espressif/common-hal/audiobusio/I2SIn.h create mode 100644 ports/mimxrt10xx/common-hal/audiobusio/I2S.c create mode 100644 ports/mimxrt10xx/common-hal/audiobusio/I2S.h create mode 100644 ports/mimxrt10xx/common-hal/audiobusio/I2SIn.c create mode 100644 ports/mimxrt10xx/common-hal/audiobusio/I2SIn.h create mode 100644 ports/nordic/common-hal/audiobusio/I2S.c create mode 100644 ports/nordic/common-hal/audiobusio/I2S.h create mode 100644 ports/nordic/common-hal/audiobusio/I2SIn.c create mode 100644 ports/nordic/common-hal/audiobusio/I2SIn.h create mode 100644 ports/stm/common-hal/audiobusio/I2S.c create mode 100644 ports/stm/common-hal/audiobusio/I2S.h create mode 100644 ports/stm/common-hal/audiobusio/I2SIn.c create mode 100644 ports/stm/common-hal/audiobusio/I2SIn.h 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/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c index f3952d83ff7cb..e0b828122c9de 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -109,6 +109,7 @@ void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, 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) { if (self->dma.output_channel[0] != NUM_DMA_CHANNELS || self->dma.input_channel[0] != NUM_DMA_CHANNELS) { + return; if (self->state_machine.out) { audio_dma_stop_output(&self->dma); } 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 From 7c6f010732f3b8f380a18c927c2ab3c354be578d Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Mon, 23 Dec 2024 14:36:22 -0600 Subject: [PATCH 18/22] Add record method. --- ports/raspberrypi/audio_dma.c | 8 +--- ports/raspberrypi/audio_dma.h | 1 - ports/raspberrypi/common-hal/audiobusio/I2S.c | 42 ++++++++++++++++++- ports/raspberrypi/common-hal/audiobusio/I2S.h | 2 + .../raspberrypi/common-hal/audiobusio/I2SIn.c | 6 +-- .../raspberrypi/common-hal/audiobusio/I2SIn.h | 1 + shared-bindings/audiobusio/I2S.c | 37 ++++++++++++++++ shared-bindings/audiobusio/I2S.h | 3 ++ 8 files changed, 88 insertions(+), 12 deletions(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index ae99bb0091ee6..eb63f4a67a6bb 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -677,13 +677,7 @@ uint8_t *audio_dma_get_buffer(audio_dma_t *dma) { if (!dma->input_register_address || dma->input_index >= 2) { return NULL; } - uint8_t *buffer = dma->input_buffer[dma->input_index]; - dma->input_index = -1; - return buffer; -} - -bool audio_dma_has_buffer(audio_dma_t *dma) { - return dma->input_register_address && dma->input_index < 2; + return dma->input_buffer[dma->input_index]; } // WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls diff --git a/ports/raspberrypi/audio_dma.h b/ports/raspberrypi/audio_dma.h index e10ab3dc05380..32b12586223ec 100644 --- a/ports/raspberrypi/audio_dma.h +++ b/ports/raspberrypi/audio_dma.h @@ -100,7 +100,6 @@ 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); -bool audio_dma_has_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 index e0b828122c9de..32da11dd40d7c 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -164,6 +164,43 @@ void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self) { audio_dma_deinit(&self->dma); } +// output_buffer may be a byte buffer or a halfword buffer. +// output_buffer_length is the number of slots, not the number of bytes. +void 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); + + size_t output_count = 0; + int16_t *buffer; + size_t buffer_length; + + while (output_count < output_buffer_length) { + // Do other things while we wait for the buffer to fill. + while (self->last_record_index == self->dma.input_index) { + if (self->state_machine.out) { + common_hal_mcu_delay_us(1000000 / self->sample_rate); + } else { + RUN_BACKGROUND_TASKS; + } + } + self->last_record_index = self->dma.input_index; + + buffer = (int16_t *)audio_dma_get_buffer(&self->dma); + buffer_length = MIN((output_buffer_length - output_count), self->buffer_size / sizeof(int16_t)); + + for (size_t i = 0; i < buffer_length; i++) { + output_buffer[i + output_count] = buffer[i]; + } + + output_count += buffer_length; + } +} + void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self, mp_obj_t sample, bool loop) { if (!self->state_machine.out) { @@ -273,6 +310,8 @@ void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self, } i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample); + self->last_record_index = -1; + self->last_sample_index = -1; } audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self, @@ -289,13 +328,14 @@ audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self } // Do other things while we wait for the buffer to fill. - while (!audio_dma_has_buffer(&self->dma)) { + while (self->last_sample_index == self->dma.input_index) { if (self->state_machine.out) { common_hal_mcu_delay_us(1000000 / self->sample_rate); } else { RUN_BACKGROUND_TASKS; } } + self->last_sample_index = self->dma.input_index; *buffer_length = self->buffer_size; *buffer = audio_dma_get_buffer(&self->dma); diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.h b/ports/raspberrypi/common-hal/audiobusio/I2S.h index f03f3ff9b0a79..55f9d1318b0c4 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.h +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.h @@ -26,6 +26,8 @@ typedef struct { uint32_t sample_rate; uint8_t bits_per_sample; bool samples_signed; + uint8_t last_sample_index; + uint8_t last_record_index; } audiobusio_i2s_obj_t; // These are not available from Python because it may be called in an interrupt. diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index cae80f7db5e96..0aa8d6070da4c 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -293,6 +293,8 @@ void audiobusio_i2sin_reset_buffer(audiobusio_i2sin_obj_t *self, 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, @@ -301,10 +303,8 @@ audioio_get_buffer_result_t audiobusio_i2sin_get_buffer(audiobusio_i2sin_obj_t * uint8_t **buffer, uint32_t *buffer_length) { - // TODO: single_channel_output - // Do other things while we wait for the buffer to fill. - while (!audio_dma_has_buffer(&self->dma)) { + while (self->last_index == self->dma.input_index) { RUN_BACKGROUND_TASKS; } diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.h b/ports/raspberrypi/common-hal/audiobusio/I2SIn.h index 353fdd6cf4ec8..746fc0b82bb57 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.h +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.h @@ -26,6 +26,7 @@ typedef struct { uint32_t sample_rate; uint8_t bits_per_sample; bool samples_signed; + uint8_t last_index; } audiobusio_i2sin_obj_t; diff --git a/shared-bindings/audiobusio/I2S.c b/shared-bindings/audiobusio/I2S.c index 5f2d4b3aee81e..926a499352b0a 100644 --- a/shared-bindings/audiobusio/I2S.c +++ b/shared-bindings/audiobusio/I2S.c @@ -179,6 +179,42 @@ static mp_obj_t audiobusio_i2s_obj___exit__(size_t n_args, const mp_obj_t *args) } 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) -> None: +//| """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. + common_hal_audiobusio_i2s_record_to_buffer(self, bufinfo.buf, length); + } + 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. @@ -280,6 +316,7 @@ static const mp_rom_map_elem_t audiobusio_i2s_locals_dict_table[] = { { 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) }, diff --git a/shared-bindings/audiobusio/I2S.h b/shared-bindings/audiobusio/I2S.h index 50abd675fef3f..3b0fcd494936f 100644 --- a/shared-bindings/audiobusio/I2S.h +++ b/shared-bindings/audiobusio/I2S.h @@ -23,6 +23,9 @@ void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self); bool common_hal_audiobusio_i2s_deinited(audiobusio_i2s_obj_t *self); +void 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); From c8499a5c79c16634b397608e42395fce94540cec Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Sat, 18 Jan 2025 00:43:49 -0600 Subject: [PATCH 19/22] Support 32-bit pio pin mask. --- ports/raspberrypi/common-hal/audiobusio/I2S.c | 10 +++++----- ports/raspberrypi/common-hal/audiobusio/I2SIn.c | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c index 32da11dd40d7c..1f20b0b16e006 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -76,14 +76,14 @@ void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, sample_rate * bits_per_sample * 16, // Frequency based on sample rate and bit width NULL, 0, // init NULL, 0, // may_exec - data_out, 1, 0, 0xffffffff, // out pin + data_out, 1, PIO_PINMASK32_NONE, PIO_PINMASK32_ALL, // out pin data_in, 1, // in pins - 0, 0, // in pulls - NULL, 1, 0, 0, // set pins - sideset_pin, 2, false, 0, 0x1f, // sideset 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 - 0, // wait gpio pins + PIO_PINMASK_NONE, // wait gpio pins true, // exclusive pin use false, 32, false, // out settings false, // Wait for txstall diff --git a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c index 0aa8d6070da4c..3cfb11166aafd 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2SIn.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2SIn.c @@ -209,14 +209,14 @@ void common_hal_audiobusio_i2sin_construct(audiobusio_i2sin_obj_t *self, sample_rate * bits_per_sample * 2 * 4, // Frequency based on sample rate and bit width NULL, 0, // init NULL, 0, // may_exec - NULL, 1, 0, 0, // out pin + NULL, 1, PIO_PINMASK32_NONE, PIO_PINMASK32_NONE, // out pin data, 1, // in pins - 0, 0, // in pulls - NULL, 1, 0, 0, // set pins - sideset_pin, 2, false, 0, 0x1f, // sideset 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 - 0, // wait gpio pins + PIO_PINMASK_NONE, // wait gpio pins true, // exclusive pin use false, 8, false, // out settings false, // Wait for txstall From 1aaf4f1eaacd7956b339ecbb3b68b7d1c19c5180 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 22 Jan 2025 08:14:21 -0600 Subject: [PATCH 20/22] Return number of samples recorded. --- ports/raspberrypi/common-hal/audiobusio/I2S.c | 4 +++- shared-bindings/audiobusio/I2S.c | 6 ++++-- shared-bindings/audiobusio/I2S.h | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c index 1f20b0b16e006..38d99c78885e8 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -166,7 +166,7 @@ void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self) { // output_buffer may be a byte buffer or a halfword buffer. // output_buffer_length is the number of slots, not the number of bytes. -void common_hal_audiobusio_i2s_record_to_buffer(audiobusio_i2s_obj_t *self, +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")); @@ -199,6 +199,8 @@ void common_hal_audiobusio_i2s_record_to_buffer(audiobusio_i2s_obj_t *self, output_count += buffer_length; } + + return output_count; } void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self, diff --git a/shared-bindings/audiobusio/I2S.c b/shared-bindings/audiobusio/I2S.c index 926a499352b0a..3e8a8087a337b 100644 --- a/shared-bindings/audiobusio/I2S.c +++ b/shared-bindings/audiobusio/I2S.c @@ -179,7 +179,7 @@ static mp_obj_t audiobusio_i2s_obj___exit__(size_t n_args, const mp_obj_t *args) } 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) -> None: +//| def record(self, destination: WriteableBuffer, destination_length: int) -> int: //| """Records destination_length bytes of samples to destination. This is //| blocking. //| @@ -210,7 +210,9 @@ static mp_obj_t audiobusio_i2s_obj_record(mp_obj_t self_obj, mp_obj_t destinatio 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. - common_hal_audiobusio_i2s_record_to_buffer(self, bufinfo.buf, length); + 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; } diff --git a/shared-bindings/audiobusio/I2S.h b/shared-bindings/audiobusio/I2S.h index 3b0fcd494936f..82509cfd6946a 100644 --- a/shared-bindings/audiobusio/I2S.h +++ b/shared-bindings/audiobusio/I2S.h @@ -23,7 +23,7 @@ void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self); bool common_hal_audiobusio_i2s_deinited(audiobusio_i2s_obj_t *self); -void common_hal_audiobusio_i2s_record_to_buffer(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); From 851765e49383c0cfcc6ef51438bc12c7b698fdf1 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 22 Jan 2025 08:15:38 -0600 Subject: [PATCH 21/22] Improve dma initialization. --- ports/raspberrypi/common-hal/audiobusio/I2S.c | 33 +++++++------------ ports/raspberrypi/common-hal/audiobusio/I2S.h | 2 +- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c index 38d99c78885e8..f8e18a3e35762 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -106,17 +106,14 @@ void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, audio_dma_init(&self->dma); } -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) { - +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) { - return; - if (self->state_machine.out) { - audio_dma_stop_output(&self->dma); + if (!force) { + return; } - if (self->state_machine.in) { - audio_dma_stop_input(&self->dma); - } - audio_dma_deinit(&self->dma); + + 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); @@ -173,7 +170,7 @@ uint32_t common_hal_audiobusio_i2s_record_to_buffer(audiobusio_i2s_obj_t *self, } // Make sure that dma is running. - i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample); + i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample, true); size_t output_count = 0; int16_t *buffer; @@ -182,11 +179,7 @@ uint32_t common_hal_audiobusio_i2s_record_to_buffer(audiobusio_i2s_obj_t *self, while (output_count < output_buffer_length) { // Do other things while we wait for the buffer to fill. while (self->last_record_index == self->dma.input_index) { - if (self->state_machine.out) { - common_hal_mcu_delay_us(1000000 / self->sample_rate); - } else { - RUN_BACKGROUND_TASKS; - } + RUN_BACKGROUND_TASKS; } self->last_record_index = self->dma.input_index; @@ -229,7 +222,7 @@ void common_hal_audiobusio_i2s_play(audiobusio_i2s_obj_t *self, } } - i2s_configure_audio_dma(self, sample, loop, sample_rate, bits_per_sample); + i2s_configure_audio_dma(self, sample, loop, sample_rate, bits_per_sample, true); self->playing = true; } @@ -311,7 +304,7 @@ void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self, mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported.")); } - i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample); + i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample, false); self->last_record_index = -1; self->last_sample_index = -1; } @@ -331,11 +324,7 @@ audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self // Do other things while we wait for the buffer to fill. while (self->last_sample_index == self->dma.input_index) { - if (self->state_machine.out) { - common_hal_mcu_delay_us(1000000 / self->sample_rate); - } else { - RUN_BACKGROUND_TASKS; - } + RUN_BACKGROUND_TASKS; } self->last_sample_index = self->dma.input_index; diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.h b/ports/raspberrypi/common-hal/audiobusio/I2S.h index 55f9d1318b0c4..818fc8d20ccea 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.h +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.h @@ -44,4 +44,4 @@ void audiobusio_i2s_get_buffer_structure(audiobusio_i2s_obj_t *self, bool single 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); + uint32_t sample_rate, uint8_t bits_per_sample, bool force); From 4b82933ba50f6ae1db3de6bcc398c286ebbac686 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Wed, 22 Jan 2025 21:19:32 -0600 Subject: [PATCH 22/22] Double-buffering and improved buffer testing. --- ports/raspberrypi/common-hal/audiobusio/I2S.c | 65 +++++++++++++------ ports/raspberrypi/common-hal/audiobusio/I2S.h | 4 +- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.c b/ports/raspberrypi/common-hal/audiobusio/I2S.c index f8e18a3e35762..a57b84f044141 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.c +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.c @@ -104,6 +104,24 @@ void common_hal_audiobusio_i2s_construct(audiobusio_i2s_obj_t *self, 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) { @@ -159,6 +177,9 @@ void common_hal_audiobusio_i2s_deinit(audiobusio_i2s_obj_t *self) { 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. @@ -173,21 +194,18 @@ uint32_t common_hal_audiobusio_i2s_record_to_buffer(audiobusio_i2s_obj_t *self, i2s_configure_audio_dma(self, self, true, self->sample_rate, self->bits_per_sample, true); size_t output_count = 0; - int16_t *buffer; - size_t buffer_length; + 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 other things while we wait for the buffer to fill. - while (self->last_record_index == self->dma.input_index) { - RUN_BACKGROUND_TASKS; - } - self->last_record_index = self->dma.input_index; - - buffer = (int16_t *)audio_dma_get_buffer(&self->dma); - buffer_length = MIN((output_buffer_length - output_count), self->buffer_size / sizeof(int16_t)); + 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[i]; + output_buffer[i + output_count] = buffer[buffer_idx][i]; } output_count += buffer_length; @@ -304,9 +322,10 @@ void audiobusio_i2s_reset_buffer(audiobusio_i2s_obj_t *self, 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); - self->last_record_index = -1; - self->last_sample_index = -1; } audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self, @@ -322,14 +341,22 @@ audioio_get_buffer_result_t audiobusio_i2s_get_buffer(audiobusio_i2s_obj_t *self mp_raise_NotImplementedError(MP_ERROR_TEXT("Single channel output not supported.")); } - // Do other things while we wait for the buffer to fill. - while (self->last_sample_index == self->dma.input_index) { - RUN_BACKGROUND_TASKS; - } - self->last_sample_index = self->dma.input_index; + // 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; - *buffer = audio_dma_get_buffer(&self->dma); + + // I2S always returns more data unless an error occured (see audiocore/__init__.h) return GET_BUFFER_MORE_DATA; } diff --git a/ports/raspberrypi/common-hal/audiobusio/I2S.h b/ports/raspberrypi/common-hal/audiobusio/I2S.h index 818fc8d20ccea..3d4af7a27128b 100644 --- a/ports/raspberrypi/common-hal/audiobusio/I2S.h +++ b/ports/raspberrypi/common-hal/audiobusio/I2S.h @@ -26,8 +26,8 @@ typedef struct { uint32_t sample_rate; uint8_t bits_per_sample; bool samples_signed; - uint8_t last_sample_index; - uint8_t last_record_index; + 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.