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. pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy