From d024df6b065c125f07c929371c94a48b058eff22 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 12 Nov 2020 14:04:50 -0600 Subject: esp32s2: Use better optimizer flags in debug builds (note that the before and after files both lack trailing newlines; this is how the esp-idf do) OPTIMIZATION_DEFAULT is -Og, which enables optimizations that do not interfere with the debugger: ``` elseif(CONFIG_COMPILER_OPTIMIZATION_DEFAULT) list(APPEND compile_options "-Og") ``` --- ports/esp32s2/esp-idf-config/sdkconfig-debug.defaults | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'ports') diff --git a/ports/esp32s2/esp-idf-config/sdkconfig-debug.defaults b/ports/esp32s2/esp-idf-config/sdkconfig-debug.defaults index f45f6ae9f..b108532aa 100644 --- a/ports/esp32s2/esp-idf-config/sdkconfig-debug.defaults +++ b/ports/esp32s2/esp-idf-config/sdkconfig-debug.defaults @@ -10,4 +10,7 @@ CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y # CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT is not set # CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set -# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set \ No newline at end of file +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y \ No newline at end of file -- cgit v1.2.3 From 05ba1431c387db97dbd66c07d3494a4dae7fc570 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 20 Nov 2020 14:53:13 -0600 Subject: esp32s2: espidf: Add IDFError this is an exception class for "generic" IDF errors that don't have their own exception type. --- ports/esp32s2/bindings/espidf/__init__.c | 27 +++++++++++++++++++++++---- ports/esp32s2/bindings/espidf/__init__.h | 1 + 2 files changed, 24 insertions(+), 4 deletions(-) (limited to 'ports') diff --git a/ports/esp32s2/bindings/espidf/__init__.c b/ports/esp32s2/bindings/espidf/__init__.c index 554bfaa39..5d3950f04 100644 --- a/ports/esp32s2/bindings/espidf/__init__.c +++ b/ports/esp32s2/bindings/espidf/__init__.c @@ -65,12 +65,12 @@ STATIC mp_obj_t espidf_heap_caps_get_largest_free_block(void) { } MP_DEFINE_CONST_FUN_OBJ_0(espidf_heap_caps_get_largest_free_block_obj, espidf_heap_caps_get_largest_free_block); -//| class MemoryError(MemoryError): -//| """Raised when an ESP IDF memory allocation fails.""" +//| class IDFError(OSError): +//| """Raised for certain generic ESP IDF errors.""" //| ... //| -NORETURN void mp_raise_espidf_MemoryError(void) { - nlr_raise(mp_obj_new_exception(&mp_type_espidf_MemoryError)); +NORETURN void mp_raise_espidf_IDFError(void) { + nlr_raise(mp_obj_new_exception(&mp_type_espidf_IDFError)); } void espidf_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kind_t kind) { @@ -83,6 +83,24 @@ void espidf_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_kin mp_obj_exception_print(print, o_in, kind); } +const mp_obj_type_t mp_type_espidf_IDFError = { + { &mp_type_type }, + .name = MP_QSTR_IDFError, + .print = espidf_exception_print, + .make_new = mp_obj_exception_make_new, + .attr = mp_obj_exception_attr, + .parent = &mp_type_OSError, +}; + + +//| class MemoryError(MemoryError): +//| """Raised when an ESP IDF memory allocation fails.""" +//| ... +//| +NORETURN void mp_raise_espidf_MemoryError(void) { + nlr_raise(mp_obj_new_exception(&mp_type_espidf_MemoryError)); +} + const mp_obj_type_t mp_type_espidf_MemoryError = { { &mp_type_type }, .name = MP_QSTR_MemoryError, @@ -99,6 +117,7 @@ STATIC const mp_rom_map_elem_t espidf_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_heap_caps_get_free_size), MP_ROM_PTR(&espidf_heap_caps_get_free_size_obj)}, { MP_ROM_QSTR(MP_QSTR_heap_caps_get_largest_free_block), MP_ROM_PTR(&espidf_heap_caps_get_largest_free_block_obj)}, + { MP_ROM_QSTR(MP_QSTR_IDFError), MP_ROM_PTR(&mp_type_espidf_IDFError) }, { MP_ROM_QSTR(MP_QSTR_MemoryError), MP_ROM_PTR(&mp_type_espidf_MemoryError) }, }; diff --git a/ports/esp32s2/bindings/espidf/__init__.h b/ports/esp32s2/bindings/espidf/__init__.h index 356c1c814..e35ec24b8 100644 --- a/ports/esp32s2/bindings/espidf/__init__.h +++ b/ports/esp32s2/bindings/espidf/__init__.h @@ -27,6 +27,7 @@ #ifndef MICROPY_INCLUDED_ESP32S2_BINDINGS_ESPIDF___INIT___H #define MICROPY_INCLUDED_ESP32S2_BINDINGS_ESPIDF___INIT___H +extern const mp_obj_type_t mp_type_espidf_IDFError; extern const mp_obj_type_t mp_type_espidf_MemoryError; NORETURN void mp_raise_espidf_MemoryError(void); -- cgit v1.2.3 From b40a579d180103d9f8a67390495d02e47554e7d7 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 30 Oct 2020 10:05:16 -0500 Subject: esp32s2: espidf: Consistent error checking of esp-idf calls By surrounding most ESP-IDF API calls with this new macro, their failure will result in a CircuitPython exception. --- ports/esp32s2/bindings/espidf/__init__.c | 61 +++++++++++++++++++++ ports/esp32s2/bindings/espidf/__init__.h | 7 +++ ports/esp32s2/esp_error.c | 91 ++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 ports/esp32s2/esp_error.c (limited to 'ports') diff --git a/ports/esp32s2/bindings/espidf/__init__.c b/ports/esp32s2/bindings/espidf/__init__.c index 5d3950f04..708c30c39 100644 --- a/ports/esp32s2/bindings/espidf/__init__.c +++ b/ports/esp32s2/bindings/espidf/__init__.c @@ -127,3 +127,64 @@ const mp_obj_module_t espidf_module = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t*)&espidf_module_globals, }; + +void raise_esp_error(esp_err_t err) { + const compressed_string_t *msg = NULL; + const mp_obj_type_t * exception_type = &mp_type_espidf_IDFError; + switch(err) { + case ESP_FAIL: + msg = translate("Generic Failure"); + break; + case ESP_ERR_NO_MEM: + exception_type = &mp_type_espidf_MemoryError; + msg = translate("Out of memory"); + break; + case ESP_ERR_INVALID_ARG: + msg = translate("Invalid argument"); + break; + case ESP_ERR_INVALID_STATE: + msg = translate("Invalid state"); + break; + case ESP_ERR_INVALID_SIZE: + msg = translate("Invalid size"); + break; + case ESP_ERR_NOT_FOUND: + msg = translate("Requested resource not found"); + break; + case ESP_ERR_NOT_SUPPORTED: + msg = translate("Operation or feature not supported"); + break; + case ESP_ERR_TIMEOUT: + msg = translate("Operation timed out"); + break; + case ESP_ERR_INVALID_RESPONSE: + msg = translate("Received response was invalid"); + break; + case ESP_ERR_INVALID_CRC: + msg = translate("CRC or checksum was invalid"); + break; + case ESP_ERR_INVALID_VERSION: + msg = translate("Version was invalid"); + break; + case ESP_ERR_INVALID_MAC: + msg = translate("MAC address was invalid"); + break; + } + if (msg) { + mp_raise_msg(exception_type, msg); + } + + const char *group = "ESP-IDF"; + + // tests must be in descending order + MP_STATIC_ASSERT( ESP_ERR_FLASH_BASE > ESP_ERR_MESH_BASE ); + MP_STATIC_ASSERT( ESP_ERR_MESH_BASE > ESP_ERR_WIFI_BASE ); + if(err >= ESP_ERR_FLASH_BASE) { + group = "Flash"; + } else if (err >= ESP_ERR_MESH_BASE) { + group = "Mesh"; + } else if (err >= ESP_ERR_WIFI_BASE) { + group = "WiFi"; + } + mp_raise_msg_varg(exception_type, translate("%s error 0x%x"), group, err); +} diff --git a/ports/esp32s2/bindings/espidf/__init__.h b/ports/esp32s2/bindings/espidf/__init__.h index e35ec24b8..5ff49970d 100644 --- a/ports/esp32s2/bindings/espidf/__init__.h +++ b/ports/esp32s2/bindings/espidf/__init__.h @@ -27,9 +27,16 @@ #ifndef MICROPY_INCLUDED_ESP32S2_BINDINGS_ESPIDF___INIT___H #define MICROPY_INCLUDED_ESP32S2_BINDINGS_ESPIDF___INIT___H +#include "esp_err.h" +#include "py/mpconfig.h" +#include "py/obj.h" + extern const mp_obj_type_t mp_type_espidf_IDFError; extern const mp_obj_type_t mp_type_espidf_MemoryError; NORETURN void mp_raise_espidf_MemoryError(void); +void raise_esp_error(esp_err_t err) NORETURN; +#define ESP_CALL_RAISE(x) do { int res = (x); if(res != ESP_OK) raise_esp_error(res); } while(0) + #endif // MICROPY_INCLUDED_ESP32S2_BINDINGS_ESPIDF___INIT___H diff --git a/ports/esp32s2/esp_error.c b/ports/esp32s2/esp_error.c new file mode 100644 index 000000000..57bc51528 --- /dev/null +++ b/ports/esp32s2/esp_error.c @@ -0,0 +1,91 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "esp_error.h" +#include "py/runtime.h" + +#include "bindings/espidf/__init__.h" + +void raise_esp_error(esp_err_t err) { + const compressed_string_t *msg = NULL; + const mp_obj_type_t * exception_type = &mp_type_espidf_IDFError; + switch(err) { + case ESP_FAIL: + msg = translate("Generic Failure"); + break; + case ESP_ERR_NO_MEM: + exception_type = &mp_type_espidf_MemoryError; + msg = translate("Out of memory"); + break; + case ESP_ERR_INVALID_ARG: + msg = translate("Invalid argument"); + break; + case ESP_ERR_INVALID_STATE: + msg = translate("Invalid state"); + break; + case ESP_ERR_INVALID_SIZE: + msg = translate("Invalid size"); + break; + case ESP_ERR_NOT_FOUND: + msg = translate("Requested resource not found"); + break; + case ESP_ERR_NOT_SUPPORTED: + msg = translate("Operation or feature not supported"); + break; + case ESP_ERR_TIMEOUT: + msg = translate("Operation timed out"); + break; + case ESP_ERR_INVALID_RESPONSE: + msg = translate("Received response was invalid"); + break; + case ESP_ERR_INVALID_CRC: + msg = translate("CRC or checksum was invalid"); + break; + case ESP_ERR_INVALID_VERSION: + msg = translate("Version was invalid"); + break; + case ESP_ERR_INVALID_MAC: + msg = translate("MAC address was invalid"); + break; + } + if (msg) { + mp_raise_msg(exception_type, msg); + } + + const char *group = "ESP-IDF"; + + // tests must be in descending order + MP_STATIC_ASSERT( ESP_ERR_FLASH_BASE > ESP_ERR_MESH_BASE ); + MP_STATIC_ASSERT( ESP_ERR_MESH_BASE > ESP_ERR_WIFI_BASE ); + if(err >= ESP_ERR_FLASH_BASE) { + group = "Flash"; + } else if (err >= ESP_ERR_MESH_BASE) { + group = "Mesh"; + } else if (err >= ESP_ERR_WIFI_BASE) { + group = "WiFi"; + } + mp_raise_msg_varg(exception_type, translate("%s error 0x%x"), group, err); +} -- cgit v1.2.3 From a7542598a0ce1fbd7722312dd863b1d40b0c2837 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 30 Oct 2020 10:05:16 -0500 Subject: esp32s2: add I2SOut --- ports/esp32s2/Makefile | 1 + ports/esp32s2/common-hal/audiobusio/I2SOut.c | 126 ++++++++++++++ ports/esp32s2/common-hal/audiobusio/I2SOut.h | 45 +++++ ports/esp32s2/common-hal/audiobusio/PDMIn.c | 0 ports/esp32s2/common-hal/audiobusio/PDMIn.h | 0 ports/esp32s2/common-hal/audiobusio/__init__.c | 0 ports/esp32s2/common-hal/audiobusio/__init__.h | 0 ports/esp32s2/i2s_common.c | 231 +++++++++++++++++++++++++ ports/esp32s2/i2s_common.h | 61 +++++++ ports/esp32s2/mpconfigport.mk | 6 +- ports/esp32s2/supervisor/port.c | 8 + shared-module/audiocore/__init__.c | 60 +++++++ shared-module/audiocore/__init__.h | 8 + 13 files changed, 545 insertions(+), 1 deletion(-) create mode 100644 ports/esp32s2/common-hal/audiobusio/I2SOut.c create mode 100644 ports/esp32s2/common-hal/audiobusio/I2SOut.h create mode 100644 ports/esp32s2/common-hal/audiobusio/PDMIn.c create mode 100644 ports/esp32s2/common-hal/audiobusio/PDMIn.h create mode 100644 ports/esp32s2/common-hal/audiobusio/__init__.c create mode 100644 ports/esp32s2/common-hal/audiobusio/__init__.h create mode 100644 ports/esp32s2/i2s_common.c create mode 100644 ports/esp32s2/i2s_common.h (limited to 'ports') diff --git a/ports/esp32s2/Makefile b/ports/esp32s2/Makefile index e08b8a792..c1e20d17a 100644 --- a/ports/esp32s2/Makefile +++ b/ports/esp32s2/Makefile @@ -175,6 +175,7 @@ CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_ESP32S2 -DCFG_TUSB_OS=OPT_OS_FREERTOS -DCFG_TUD SRC_C += \ background.c \ fatfs_port.c \ + i2s_common.c \ mphalport.c \ bindings/espidf/__init__.c \ boards/$(BOARD)/board.c \ diff --git a/ports/esp32s2/common-hal/audiobusio/I2SOut.c b/ports/esp32s2/common-hal/audiobusio/I2SOut.c new file mode 100644 index 000000000..be5b04fa9 --- /dev/null +++ b/ports/esp32s2/common-hal/audiobusio/I2SOut.c @@ -0,0 +1,126 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +#include "mpconfigport.h" + +#include "bindings/espidf/__init__.h" + +// Some boards don't implement I2SOut, so suppress any routines from here. +#if CIRCUITPY_AUDIOBUSIO_I2SOUT + +#include "extmod/vfs_fat.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/runtime.h" +#include "common-hal/audiobusio/I2SOut.h" +#include "shared-bindings/audiobusio/I2SOut.h" +#include "shared-bindings/audiocore/RawSample.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "supervisor/shared/translate.h" + +#include "driver/i2s.h" + +// Caller validates that pins are free. +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, bool left_justified) { + + port_i2s_allocate_init(&self->peripheral, left_justified); + + i2s_pin_config_t i2s_pin_config = { + .bck_io_num = bit_clock->number, + .ws_io_num = word_select->number, + .data_out_num = data->number, + .data_in_num = I2S_PIN_NO_CHANGE, + }; + ESP_CALL_RAISE(i2s_set_pin(self->peripheral.instance, &i2s_pin_config)); + self->bit_clock = bit_clock; + self->word_select = word_select; + self->data = data; +} + +bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) { + return self->peripheral.instance == -1; +} + +void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) { + if (common_hal_audiobusio_i2sout_deinited(self)) { + return; + } + + if (self->bit_clock) { + reset_pin_number(self->bit_clock->number); + } + self->bit_clock = NULL; + + if (self->word_select) { + reset_pin_number(self->word_select->number); + } + self->word_select = NULL; + + if (self->data) { + reset_pin_number(self->data->number); + } + self->data = NULL; + + if (self->peripheral.instance >= 0) { + port_i2s_reset_instance(self->peripheral.instance); + } + self->peripheral.instance = -1; +} + +void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, + mp_obj_t sample, bool loop) { + if (common_hal_audiobusio_i2sout_get_playing(self)) { + common_hal_audiobusio_i2sout_stop(self); + } + port_i2s_play(&self->peripheral, sample, loop); +} + +void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t* self) { + port_i2s_pause(&self->peripheral); +} + +void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t* self) { + port_i2s_resume(&self->peripheral); +} + +bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t* self) { + return port_i2s_paused(&self->peripheral); +} + +void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t* self) { + port_i2s_stop(&self->peripheral); +} + +bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t* self) { + return port_i2s_playing(&self->peripheral); +} + +#endif // CIRCUITPY_AUDIOBUSIO_I2SOUT diff --git a/ports/esp32s2/common-hal/audiobusio/I2SOut.h b/ports/esp32s2/common-hal/audiobusio/I2SOut.h new file mode 100644 index 000000000..90266342c --- /dev/null +++ b/ports/esp32s2/common-hal/audiobusio/I2SOut.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "supervisor/background_callback.h" +#include "common-hal/microcontroller/Pin.h" + +#include "i2s_common.h" + +// Some boards don't implement I2SOut, so suppress any routines from here. +#if CIRCUITPY_AUDIOBUSIO_I2SOUT + +typedef struct { + mp_obj_base_t base; + i2s_t peripheral; + const mcu_pin_obj_t *bit_clock; + const mcu_pin_obj_t *word_select; + const mcu_pin_obj_t *data; +} audiobusio_i2sout_obj_t; + +#endif diff --git a/ports/esp32s2/common-hal/audiobusio/PDMIn.c b/ports/esp32s2/common-hal/audiobusio/PDMIn.c new file mode 100644 index 000000000..e69de29bb diff --git a/ports/esp32s2/common-hal/audiobusio/PDMIn.h b/ports/esp32s2/common-hal/audiobusio/PDMIn.h new file mode 100644 index 000000000..e69de29bb diff --git a/ports/esp32s2/common-hal/audiobusio/__init__.c b/ports/esp32s2/common-hal/audiobusio/__init__.c new file mode 100644 index 000000000..e69de29bb diff --git a/ports/esp32s2/common-hal/audiobusio/__init__.h b/ports/esp32s2/common-hal/audiobusio/__init__.h new file mode 100644 index 000000000..e69de29bb diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c new file mode 100644 index 000000000..1933665a3 --- /dev/null +++ b/ports/esp32s2/i2s_common.c @@ -0,0 +1,231 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" + +#include "i2s_common.h" +#include "bindings/espidf/__init__.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "shared-module/audiocore/__init__.h" + +#define I2S_QUEUE_SIZE (3) + +static i2s_t *i2s_instance[I2S_NUM_MAX]; +static QueueHandle_t i2s_queues[I2S_NUM_MAX]; +static TaskHandle_t i2s_tasks[I2S_NUM_MAX]; + +static int8_t port_i2s_allocate(void) { +#if defined(I2S_NUM_1) + if(!i2s_instance[1]) return 1; +#endif + if(!i2s_instance[0]) return 0; + + mp_raise_RuntimeError(translate("Peripheral in use")); +} + +void port_i2s_reset_instance(int i) { + assert(i >= 0 && i < I2S_NUM_MAX); + if (i2s_tasks[i]) { + vTaskDelete(i2s_tasks[i]); + } + i2s_tasks[i] = NULL; + + (void)i2s_driver_uninstall(i); + i2s_instance[i] = NULL; +} + +void i2s_reset(void) { + for (int i=0; i < I2S_NUM_MAX; i++) { + port_i2s_reset_instance(i); + } +} + +static void i2s_fill_buffer(i2s_t *self) { + if (self->instance < 0 || self->instance >= I2S_NUM_MAX) { + return; + } + if (self->paused || !self->sample) { + i2s_zero_dma_buffer(self->instance); + return; + } + while (!self->stopping) { + if (self->sample_data == self->sample_end) { + uint32_t sample_buffer_length; + audioio_get_buffer_result_t get_buffer_result = + audiosample_get_buffer(self->sample, false, 0, + &self->sample_data, &sample_buffer_length); + self->sample_end = self->sample_data + sample_buffer_length; + if (get_buffer_result == GET_BUFFER_DONE) { + if (self->loop) { + audiosample_reset_buffer(self->sample, false, 0); + } else { + self->stopping = true; + break; + } + } + if (get_buffer_result == GET_BUFFER_ERROR || sample_buffer_length == 0) { + self->stopping = true; + break; + } + } + size_t bytes_written = 0; + size_t bytecount = self->sample_end - self->sample_data; + if (self->samples_signed && self->channel_count == 2) { + if (self->bytes_per_sample == 2) { + ESP_CALL_RAISE(i2s_write(self->instance, self->sample_data, bytecount, &bytes_written, 0)); + } else { + ESP_CALL_RAISE(i2s_write_expand(self->instance, self->sample_data, bytecount, 8, 16, &bytes_written, 0)); + } + } else { +#define STACK_BUFFER_SIZE (64) + size_t bytes_per_frame = self->channel_count * self->bytes_per_sample; + size_t framecount = MIN(STACK_BUFFER_SIZE / bytes_per_frame, bytecount); + int16_t signed_samples[STACK_BUFFER_SIZE / sizeof(int16_t)]; + if (self->samples_signed) { + assert(self->channel_count == 1); + if (self->bytes_per_sample == 1) { + audiosample_convert_s8m_s16s(signed_samples, (int8_t*)(void*)self->sample_data, framecount); + } else { + audiosample_convert_s16m_s16s(signed_samples, (int16_t*)(void*)self->sample_data, framecount); + } + } else { + if (self->channel_count == 1) { + if (self->bytes_per_sample == 1) { + audiosample_convert_u8m_s16s(signed_samples, (uint8_t*)(void*)self->sample_data, framecount); + } else { + audiosample_convert_u16m_s16s(signed_samples, (uint16_t*)(void*)self->sample_data, framecount); + } + } else { + if (self->bytes_per_sample == 1) { + audiosample_convert_u8s_s16s(signed_samples, (uint8_t*)(void*)self->sample_data, framecount); + } else { + audiosample_convert_u16s_s16s(signed_samples, (uint16_t*)(void*)self->sample_data, framecount); + } + } + } + size_t expanded_bytes_written = 0; + ESP_CALL_RAISE(i2s_write(self->instance, signed_samples, 4*framecount, &expanded_bytes_written, 0)); + assert(expanded_bytes_written % 4 == 0); + bytes_written = expanded_bytes_written / 4 * bytes_per_frame; + } + self->sample_data += bytes_written; + // We have filled the DMA buffer + if (!bytes_written) { + break; + } + } +} + +static void i2s_callback_fun(void *self_in) { + i2s_t *self = self_in; + i2s_fill_buffer(self); +} + +static void i2s_event_task(void *self_in) { + i2s_t *self = self_in; + while(true) { + i2s_event_type_t event; + BaseType_t result = xQueueReceive(i2s_queues[self->instance], &event, portMAX_DELAY); + if (result && event == I2S_EVENT_TX_DONE) { + background_callback_add(&self->callback, i2s_callback_fun, self_in); + } + } +} + +void port_i2s_allocate_init(i2s_t *self, bool left_justified) { + self->instance = port_i2s_allocate(); + + i2s_config_t i2s_config = { + .mode = I2S_MODE_MASTER | I2S_MODE_TX, + .sample_rate = 44100, + .bits_per_sample = 16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = left_justified ? I2S_COMM_FORMAT_STAND_I2S : I2S_COMM_FORMAT_STAND_I2S, + .dma_buf_count = 2, + .dma_buf_len = 128, // in _frames_, so 128 is 512 bytes per dma buf + .use_apll = false, + }; + ESP_CALL_RAISE(i2s_driver_install(self->instance, &i2s_config, I2S_QUEUE_SIZE, &i2s_queues[self->instance])); + + if (!xTaskCreate(i2s_event_task, "I2S_task", 3 * configMINIMAL_STACK_SIZE, self, CONFIG_PTHREAD_TASK_PRIO_DEFAULT, &i2s_tasks[self->instance])) { + mp_raise_OSError_msg(translate("xTaskCreate failed")); + } + i2s_instance[self->instance] = self; + +} + + +void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { + self->sample = sample; + self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; + self->channel_count = audiosample_channel_count(sample); + bool single_buffer; + bool samples_signed; + uint32_t max_buffer_length; + uint8_t spacing; + audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, + &max_buffer_length, &spacing); + self->samples_signed = samples_signed; + self->loop = loop; + self->playing = true; + self->paused = false; + self->stopping = false; + // We always output stereo so output twice as many bits. + // uint16_t bits_per_sample_output = bits_per_sample * 2; + ESP_CALL_RAISE(i2s_set_sample_rates(self->instance, audiosample_sample_rate(sample))); + i2s_fill_buffer(self); +} + +bool port_i2s_playing(i2s_t *self) { + return self->playing && !self->stopping; +} + +bool port_i2s_paused(i2s_t *self) { + return self->paused; +} + +void port_i2s_stop(i2s_t *self) { + self->sample = NULL; + self->paused = false; + self->playing = false; + self->stopping = false; +} + +void port_i2s_pause(i2s_t *self) { + if (!self->paused) { + self->paused = true; + ESP_CALL_RAISE(i2s_stop(self->instance)); + } +} + +void port_i2s_resume(i2s_t *self) { + if (self->paused) { + self->paused = false; + ESP_CALL_RAISE(i2s_start(self->instance)); + } +} diff --git a/ports/esp32s2/i2s_common.h b/ports/esp32s2/i2s_common.h new file mode 100644 index 000000000..7709735da --- /dev/null +++ b/ports/esp32s2/i2s_common.h @@ -0,0 +1,61 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "py/obj.h" + +#include "supervisor/background_callback.h" + +#include "driver/i2s.h" + +typedef struct { + mp_obj_t *sample; + bool left_justified; + bool loop; + bool paused; + bool playing; + bool stopping; + bool samples_signed; + int8_t bytes_per_sample; + int8_t channel_count; + int8_t instance; + uint16_t buffer_length; + uint8_t *sample_data, *sample_end; + i2s_config_t i2s_config; + background_callback_t callback; +} i2s_t; + + +void port_i2s_allocate_init(i2s_t *self, bool left_justified); +void port_i2s_reset_instance(int i); +void i2s_reset(void); +void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop); +void port_i2s_stop(i2s_t *self); +bool port_i2s_playing(i2s_t *self); +bool port_i2s_paused(i2s_t *self); +void port_i2s_pause(i2s_t *self); +void port_i2s_resume(i2s_t *self); diff --git a/ports/esp32s2/mpconfigport.mk b/ports/esp32s2/mpconfigport.mk index 2ad8afb51..562c60998 100644 --- a/ports/esp32s2/mpconfigport.mk +++ b/ports/esp32s2/mpconfigport.mk @@ -15,7 +15,11 @@ LONGINT_IMPL = MPZ # These modules are implemented in ports//common-hal: CIRCUITPY_FULL_BUILD = 1 CIRCUITPY_ALARM = 1 -CIRCUITPY_AUDIOBUSIO = 0 +CIRCUITPY_AUDIOCORE = 1 +CIRCUITPY_AUDIOMP3 = 0 +CIRCUITPY_AUDIOBUSIO = 1 +CIRCUITPY_AUDIOBUSIO_PDMIN = 0 +CIRCUITPY_AUDIOBUSIO_I2SOUT = 1 CIRCUITPY_AUDIOIO = 0 CIRCUITPY_CANIO = 1 CIRCUITPY_COUNTIO = 1 diff --git a/ports/esp32s2/supervisor/port.c b/ports/esp32s2/supervisor/port.c index ff35845f7..75492492e 100644 --- a/ports/esp32s2/supervisor/port.c +++ b/ports/esp32s2/supervisor/port.c @@ -61,6 +61,10 @@ #include "components/soc/soc/esp32s2/include/soc/cache_memory.h" #include "components/soc/soc/esp32s2/include/soc/rtc_cntl_reg.h" +#if CIRCUITPY_AUDIOBUSIO +#include "i2s_common.h" +#endif + #define HEAP_SIZE (48 * 1024) uint32_t* heap; @@ -153,6 +157,10 @@ void reset_port(void) { ps2_reset(); #endif +#if CIRCUITPY_AUDIOBUSIO + i2s_reset(); +#endif + #if CIRCUITPY_PULSEIO esp32s2_peripherals_rmt_reset(); pulsein_reset(); diff --git a/shared-module/audiocore/__init__.c b/shared-module/audiocore/__init__.c index ac9d41b60..7bdfe460c 100644 --- a/shared-module/audiocore/__init__.c +++ b/shared-module/audiocore/__init__.c @@ -70,3 +70,63 @@ void audiosample_get_buffer_structure(mp_obj_t sample_obj, bool single_channel, proto->get_buffer_structure(MP_OBJ_TO_PTR(sample_obj), single_channel, single_buffer, samples_signed, max_buffer_length, spacing); } + +void audiosample_convert_u8m_s16s(int16_t *buffer_out, const uint8_t *buffer_in, size_t nframes) { + for(;nframes--;) { + int16_t sample = (*buffer_in++ - 0x80) << 8; + *buffer_out++ = sample; + *buffer_out++ = sample; + } +} + + +void audiosample_convert_u8s_s16s(int16_t *buffer_out, const uint8_t *buffer_in, size_t nframes) { + size_t nsamples = 2*nframes; + for(;nsamples--;) { + int16_t sample = (*buffer_in++ - 0x80) << 8; + *buffer_out++ = sample; + } +} + +void audiosample_convert_s8m_s16s(int16_t *buffer_out, const int8_t *buffer_in, size_t nframes) { + for(;nframes--;) { + int16_t sample = (*buffer_in++) << 8; + *buffer_out++ = sample; + *buffer_out++ = sample; + } +} + + +void audiosample_convert_s8s_s16s(int16_t *buffer_out, const int8_t *buffer_in, size_t nframes) { + size_t nsamples = 2*nframes; + for(;nsamples--;) { + int16_t sample = (*buffer_in++) << 8; + *buffer_out++ = sample; + } +} + + +void audiosample_convert_u16m_s16s(int16_t *buffer_out, const uint16_t *buffer_in, size_t nframes) { + for(;nframes--;) { + int16_t sample = *buffer_in++ - 0x8000; + *buffer_out++ = sample; + *buffer_out++ = sample; + } +} + + +void audiosample_convert_u16s_s16s(int16_t *buffer_out, const uint16_t *buffer_in, size_t nframes) { + size_t nsamples = 2*nframes; + for(;nsamples--;) { + int16_t sample = *buffer_in++ - 0x8000; + *buffer_out++ = sample; + } +} + +void audiosample_convert_s16m_s16s(int16_t *buffer_out, const int16_t *buffer_in, size_t nframes) { + for(;nframes--;) { + int16_t sample = *buffer_in++; + *buffer_out++ = sample; + *buffer_out++ = sample; + } +} diff --git a/shared-module/audiocore/__init__.h b/shared-module/audiocore/__init__.h index 7a56454b8..ec2cf1cfa 100644 --- a/shared-module/audiocore/__init__.h +++ b/shared-module/audiocore/__init__.h @@ -74,4 +74,12 @@ void audiosample_get_buffer_structure(mp_obj_t sample_obj, bool single_channel, bool* single_buffer, bool* samples_signed, uint32_t* max_buffer_length, uint8_t* spacing); +void audiosample_convert_u8m_s16s(int16_t *buffer_out, const uint8_t *buffer_in, size_t nframes); +void audiosample_convert_u8s_s16s(int16_t *buffer_out, const uint8_t *buffer_in, size_t nframes); +void audiosample_convert_s8m_s16s(int16_t *buffer_out, const int8_t *buffer_in, size_t nframes); +void audiosample_convert_s8s_s16s(int16_t *buffer_out, const int8_t *buffer_in, size_t nframes); +void audiosample_convert_u16m_s16s(int16_t *buffer_out, const uint16_t *buffer_in, size_t nframes); +void audiosample_convert_u16s_s16s(int16_t *buffer_out, const uint16_t *buffer_in, size_t nframes); +void audiosample_convert_s16m_s16s(int16_t *buffer_out, const int16_t *buffer_in, size_t nframes); + #endif // MICROPY_INCLUDED_SHARED_MODULE_AUDIOCORE__INIT__H -- cgit v1.2.3 From 7bb196b9d2133477bf967d6b1d5dafe0971f765f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 7 Jan 2021 15:01:33 -0600 Subject: esp32s2: 'make flash': Allow customizing the esptool flags This can be useful so that e.g., on a Kaluga when programming via the FTDI chip, you can override the variable to specify "--after=hard_reset" to automatically return to running CircuitPython, choose a different baud rate (921600 is about 2s faster than 460800), etc: make BOARD=espressif_kaluga_1 ESPTOOL_FLAGS="-b 921600 --before=default_reset --after=hard_reset" --- ports/esp32s2/Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'ports') diff --git a/ports/esp32s2/Makefile b/ports/esp32s2/Makefile index 4a334a2eb..88bc8ce63 100644 --- a/ports/esp32s2/Makefile +++ b/ports/esp32s2/Makefile @@ -300,6 +300,8 @@ ESP_AUTOGEN_LD = $(BUILD)/esp-idf/esp-idf/esp32s2/esp32s2_out.ld $(BUILD)/esp-id FLASH_FLAGS = --flash_mode $(CIRCUITPY_ESP_FLASH_MODE) --flash_freq $(CIRCUITPY_ESP_FLASH_FREQ) --flash_size $(CIRCUITPY_ESP_FLASH_SIZE) +ESPTOOL_FLAGS ?= -b 460800 --before=default_reset --after=no_reset write_flash + all: $(BUILD)/firmware.bin $(BUILD)/firmware.uf2 .PHONY: esp-idf-stamp @@ -338,10 +340,10 @@ $(BUILD)/firmware.uf2: $(BUILD)/circuitpython-firmware.bin $(Q)$(PYTHON3) $(TOP)/tools/uf2/utils/uf2conv.py -f 0xbfdd4eee -b 0x0000 -c -o $@ $^ flash: $(BUILD)/firmware.bin - esptool.py --chip esp32s2 -p $(PORT) -b 460800 --before=default_reset --after=no_reset write_flash $(FLASH_FLAGS) 0x0000 $^ + esptool.py --chip esp32s2 -p $(PORT) $(ESPTOOL_FLAGS) write_flash $(FLASH_FLAGS) 0x0000 $^ flash-circuitpython-only: $(BUILD)/circuitpython-firmware.bin - esptool.py --chip esp32s2 -p $(PORT) -b 460800 --before=default_reset --after=no_reset write_flash $(FLASH_FLAGS) 0x10000 $^ + esptool.py --chip esp32s2 -p $(PORT) $(ESPTOOL_FLAGS) write_flash $(FLASH_FLAGS) 0x10000 $^ include $(TOP)/py/mkrules.mk -- cgit v1.2.3 From 010a4e7b0fefbee12f8139f7c5c13436545bb1eb Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 7 Jan 2021 16:23:21 -0600 Subject: esp32s2: port: Ensure JTAG pins are available for debugging --- ports/esp32s2/supervisor/port.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'ports') diff --git a/ports/esp32s2/supervisor/port.c b/ports/esp32s2/supervisor/port.c index ead12003d..a6b6a82aa 100644 --- a/ports/esp32s2/supervisor/port.c +++ b/ports/esp32s2/supervisor/port.c @@ -111,6 +111,14 @@ safe_mode_t port_init(void) { heap = NULL; never_reset_module_internal_pins(); + #if defined(DEBUG) || defined(ENABLE_JTAG) + // JTAG + common_hal_never_reset_pin(&pin_GPIO39); + common_hal_never_reset_pin(&pin_GPIO40); + common_hal_never_reset_pin(&pin_GPIO41); + common_hal_never_reset_pin(&pin_GPIO42); + #endif + #ifdef CONFIG_SPIRAM heap = (uint32_t*) (DRAM0_CACHE_ADDRESS_HIGH - CONFIG_SPIRAM_SIZE); heap_size = CONFIG_SPIRAM_SIZE / sizeof(uint32_t); -- cgit v1.2.3 From 352226402c6b897ec9dc776a61cff3330b8a40df Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 7 Jan 2021 16:28:33 -0600 Subject: esp32s2: i2s: fix accounting for "stretched" frames --- ports/esp32s2/i2s_common.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'ports') diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index 1933665a3..24ded295f 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -103,9 +103,9 @@ static void i2s_fill_buffer(i2s_t *self) { } } else { #define STACK_BUFFER_SIZE (64) - size_t bytes_per_frame = self->channel_count * self->bytes_per_sample; - size_t framecount = MIN(STACK_BUFFER_SIZE / bytes_per_frame, bytecount); - int16_t signed_samples[STACK_BUFFER_SIZE / sizeof(int16_t)]; + const size_t bytes_per_output_frame = 4; + size_t bytes_per_input_frame = self->channel_count * self->bytes_per_sample; + size_t framecount = MIN(STACK_BUFFER_SIZE / bytes_per_output_frame, bytecount / bytes_per_input_frame); if (self->samples_signed) { assert(self->channel_count == 1); if (self->bytes_per_sample == 1) { @@ -129,9 +129,9 @@ static void i2s_fill_buffer(i2s_t *self) { } } size_t expanded_bytes_written = 0; - ESP_CALL_RAISE(i2s_write(self->instance, signed_samples, 4*framecount, &expanded_bytes_written, 0)); + ESP_CALL_RAISE(i2s_write(self->instance, signed_samples, bytes_per_output_frame*framecount, &expanded_bytes_written, 0)); assert(expanded_bytes_written % 4 == 0); - bytes_written = expanded_bytes_written / 4 * bytes_per_frame; + bytes_written = expanded_bytes_written / bytes_per_output_frame * bytes_per_input_frame; } self->sample_data += bytes_written; // We have filled the DMA buffer @@ -181,6 +181,10 @@ void port_i2s_allocate_init(i2s_t *self, bool left_justified) { void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { + if (common_hal_audiobusio_i2sout_get_playing(self)) { + common_hal_audiobusio_i2sout_stop(self); + } + self->sample = sample; self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; self->channel_count = audiosample_channel_count(sample); -- cgit v1.2.3 From d3afda61d8172ad6ec8706782c397d9cd52c3edc Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 7 Jan 2021 16:29:08 -0600 Subject: esp32s2: i2s: must reset buffer, otherwise wave samples don't start .. and other housekeeping when starting to play a sample --- ports/esp32s2/i2s_common.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ports') diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index 24ded295f..d5c0facaa 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -186,6 +186,7 @@ void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { } self->sample = sample; + self->loop = loop; self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; self->channel_count = audiosample_channel_count(sample); bool single_buffer; @@ -195,12 +196,15 @@ void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, &max_buffer_length, &spacing); self->samples_signed = samples_signed; - self->loop = loop; self->playing = true; self->paused = false; self->stopping = false; + self->sample_data = self->sample_end = NULL; // We always output stereo so output twice as many bits. // uint16_t bits_per_sample_output = bits_per_sample * 2; + + audiosample_reset_buffer(self->sample, false, 0); + ESP_CALL_RAISE(i2s_set_sample_rates(self->instance, audiosample_sample_rate(sample))); i2s_fill_buffer(self); } -- cgit v1.2.3 From cfd8288dfa0a27bd727e3877659d9bbb990dd1ce Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 7 Jan 2021 16:29:52 -0600 Subject: esp2s2: enlarge stack-buffer, use it instead of i2s_zero_dma_buffer .. it's not clear that there was a problem with i2s_zero_dma_buffer, but just in case. --- ports/esp32s2/i2s_common.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'ports') diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index d5c0facaa..ca2a3b93e 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -24,6 +24,8 @@ * THE SOFTWARE. */ +#include + #include "py/runtime.h" #include "i2s_common.h" @@ -69,8 +71,17 @@ static void i2s_fill_buffer(i2s_t *self) { if (self->instance < 0 || self->instance >= I2S_NUM_MAX) { return; } - if (self->paused || !self->sample) { - i2s_zero_dma_buffer(self->instance); +#define STACK_BUFFER_SIZE (512) + int16_t signed_samples[STACK_BUFFER_SIZE / sizeof(int16_t)]; +mp_printf(&mp_plat_print, "playing=%d paused=%d stopping=%d sample@%p sample_data=%p..%p\n", self->playing, self->paused, self->stopping, self->sample, self->sample_data, self->sample_end); + + if (!self->playing || self->paused || !self->sample) { + memset(signed_samples, 0, sizeof(signed_samples)); + + size_t bytes_written = 0; + do { + ESP_CALL_RAISE(i2s_write(self->instance, signed_samples, sizeof(signed_samples), &bytes_written, 0)); + } while (bytes_written != 0); return; } while (!self->stopping) { @@ -102,7 +113,6 @@ static void i2s_fill_buffer(i2s_t *self) { ESP_CALL_RAISE(i2s_write_expand(self->instance, self->sample_data, bytecount, 8, 16, &bytes_written, 0)); } } else { -#define STACK_BUFFER_SIZE (64) const size_t bytes_per_output_frame = 4; size_t bytes_per_input_frame = self->channel_count * self->bytes_per_sample; size_t framecount = MIN(STACK_BUFFER_SIZE / bytes_per_output_frame, bytecount / bytes_per_input_frame); -- cgit v1.2.3 From 873a300d02316ca8b6d227574613385acc3df6b9 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 8 Jan 2021 08:46:41 -0600 Subject: i2s_fill_buffer: Need to fill with zeros when stopping --- ports/esp32s2/i2s_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ports') diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index ca2a3b93e..5316c8088 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -75,7 +75,7 @@ static void i2s_fill_buffer(i2s_t *self) { int16_t signed_samples[STACK_BUFFER_SIZE / sizeof(int16_t)]; mp_printf(&mp_plat_print, "playing=%d paused=%d stopping=%d sample@%p sample_data=%p..%p\n", self->playing, self->paused, self->stopping, self->sample, self->sample_data, self->sample_end); - if (!self->playing || self->paused || !self->sample) { + if (!self->playing || self->paused || !self->sample || self->stopping) { memset(signed_samples, 0, sizeof(signed_samples)); size_t bytes_written = 0; -- cgit v1.2.3 From 0b7a4c4b2b7e6e56e40100ab1e85d79b8a93ef33 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 8 Jan 2021 08:46:56 -0600 Subject: i2s_fill_buffer: remove debug print --- ports/esp32s2/i2s_common.c | 1 - 1 file changed, 1 deletion(-) (limited to 'ports') diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index 5316c8088..a58bdc23b 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -73,7 +73,6 @@ static void i2s_fill_buffer(i2s_t *self) { } #define STACK_BUFFER_SIZE (512) int16_t signed_samples[STACK_BUFFER_SIZE / sizeof(int16_t)]; -mp_printf(&mp_plat_print, "playing=%d paused=%d stopping=%d sample@%p sample_data=%p..%p\n", self->playing, self->paused, self->stopping, self->sample, self->sample_data, self->sample_end); if (!self->playing || self->paused || !self->sample || self->stopping) { memset(signed_samples, 0, sizeof(signed_samples)); -- cgit v1.2.3 From 12264cca34179c2e3bafad49145c9af3f827fa0f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 8 Jan 2021 08:47:13 -0600 Subject: port_i2s_play: remove build error --- ports/esp32s2/i2s_common.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'ports') diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index a58bdc23b..a948ee0f5 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -190,10 +190,6 @@ void port_i2s_allocate_init(i2s_t *self, bool left_justified) { void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { - if (common_hal_audiobusio_i2sout_get_playing(self)) { - common_hal_audiobusio_i2sout_stop(self); - } - self->sample = sample; self->loop = loop; self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; -- cgit v1.2.3 From 430bcdb59d47fc89c7788aeb300027bcf4e00bf2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 8 Jan 2021 08:49:25 -0600 Subject: port_i2s_play: fill the initial buffer via background callback There were _possibly_ problems where this routine was being entered by direct call AND by background callback. Schedule the work here, and it will be done almost immediately, without worry about interference. I don't know if this is strictly necessary, but it doesn't hurt. Since the I2S clock is being run all the time, we have to enter the background task to fill the FIFO with zeros constantly anyway. --- ports/esp32s2/i2s_common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ports') diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index a948ee0f5..552fe7424 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -211,7 +211,8 @@ void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { audiosample_reset_buffer(self->sample, false, 0); ESP_CALL_RAISE(i2s_set_sample_rates(self->instance, audiosample_sample_rate(sample))); - i2s_fill_buffer(self); + + background_callback_add(&self->callback, i2s_callback_fun, self); } bool port_i2s_playing(i2s_t *self) { -- cgit v1.2.3 From 5f0e41ad601664b37c109a5d84b2a6031a4a740f Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Fri, 8 Jan 2021 09:01:34 -0600 Subject: I2SOut: Enable ticks during audio playback .. otherwise, the background callback to load the I2S fifos does not get run. (I'm not sure this is _correct_ behavior of sleep + background tasks, but it is the current behavior) --- ports/esp32s2/common-hal/audiobusio/I2SOut.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ports') diff --git a/ports/esp32s2/common-hal/audiobusio/I2SOut.c b/ports/esp32s2/common-hal/audiobusio/I2SOut.c index be5b04fa9..694cab49d 100644 --- a/ports/esp32s2/common-hal/audiobusio/I2SOut.c +++ b/ports/esp32s2/common-hal/audiobusio/I2SOut.c @@ -63,6 +63,8 @@ void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self, self->bit_clock = bit_clock; self->word_select = word_select; self->data = data; + + supervisor_enable_tick(); } bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) { @@ -93,6 +95,8 @@ void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) { port_i2s_reset_instance(self->peripheral.instance); } self->peripheral.instance = -1; + + supervisor_disable_tick(); } void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, -- cgit v1.2.3 From 10861b403881c96cc7da6d1b8c6e80b40332fee7 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 9 Jan 2021 13:41:44 -0600 Subject: esp32s2: Rename ESP_CALL_RAISE to CHECK_ESP_RESULT Suggested by @tannewt, thanks! --- ports/esp32s2/bindings/espidf/__init__.h | 2 +- ports/esp32s2/common-hal/audiobusio/I2SOut.c | 2 +- ports/esp32s2/i2s_common.c | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'ports') diff --git a/ports/esp32s2/bindings/espidf/__init__.h b/ports/esp32s2/bindings/espidf/__init__.h index 5ff49970d..9248b01ac 100644 --- a/ports/esp32s2/bindings/espidf/__init__.h +++ b/ports/esp32s2/bindings/espidf/__init__.h @@ -37,6 +37,6 @@ extern const mp_obj_type_t mp_type_espidf_MemoryError; NORETURN void mp_raise_espidf_MemoryError(void); void raise_esp_error(esp_err_t err) NORETURN; -#define ESP_CALL_RAISE(x) do { int res = (x); if(res != ESP_OK) raise_esp_error(res); } while(0) +#define CHECK_ESP_RESULT(x) do { int res = (x); if(res != ESP_OK) raise_esp_error(res); } while(0) #endif // MICROPY_INCLUDED_ESP32S2_BINDINGS_ESPIDF___INIT___H diff --git a/ports/esp32s2/common-hal/audiobusio/I2SOut.c b/ports/esp32s2/common-hal/audiobusio/I2SOut.c index 694cab49d..ee16c6ca8 100644 --- a/ports/esp32s2/common-hal/audiobusio/I2SOut.c +++ b/ports/esp32s2/common-hal/audiobusio/I2SOut.c @@ -59,7 +59,7 @@ void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self, .data_out_num = data->number, .data_in_num = I2S_PIN_NO_CHANGE, }; - ESP_CALL_RAISE(i2s_set_pin(self->peripheral.instance, &i2s_pin_config)); + CHECK_ESP_RESULT(i2s_set_pin(self->peripheral.instance, &i2s_pin_config)); self->bit_clock = bit_clock; self->word_select = word_select; self->data = data; diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c index 552fe7424..f967e9a09 100644 --- a/ports/esp32s2/i2s_common.c +++ b/ports/esp32s2/i2s_common.c @@ -79,7 +79,7 @@ static void i2s_fill_buffer(i2s_t *self) { size_t bytes_written = 0; do { - ESP_CALL_RAISE(i2s_write(self->instance, signed_samples, sizeof(signed_samples), &bytes_written, 0)); + CHECK_ESP_RESULT(i2s_write(self->instance, signed_samples, sizeof(signed_samples), &bytes_written, 0)); } while (bytes_written != 0); return; } @@ -107,9 +107,9 @@ static void i2s_fill_buffer(i2s_t *self) { size_t bytecount = self->sample_end - self->sample_data; if (self->samples_signed && self->channel_count == 2) { if (self->bytes_per_sample == 2) { - ESP_CALL_RAISE(i2s_write(self->instance, self->sample_data, bytecount, &bytes_written, 0)); + CHECK_ESP_RESULT(i2s_write(self->instance, self->sample_data, bytecount, &bytes_written, 0)); } else { - ESP_CALL_RAISE(i2s_write_expand(self->instance, self->sample_data, bytecount, 8, 16, &bytes_written, 0)); + CHECK_ESP_RESULT(i2s_write_expand(self->instance, self->sample_data, bytecount, 8, 16, &bytes_written, 0)); } } else { const size_t bytes_per_output_frame = 4; @@ -138,7 +138,7 @@ static void i2s_fill_buffer(i2s_t *self) { } } size_t expanded_bytes_written = 0; - ESP_CALL_RAISE(i2s_write(self->instance, signed_samples, bytes_per_output_frame*framecount, &expanded_bytes_written, 0)); + CHECK_ESP_RESULT(i2s_write(self->instance, signed_samples, bytes_per_output_frame*framecount, &expanded_bytes_written, 0)); assert(expanded_bytes_written % 4 == 0); bytes_written = expanded_bytes_written / bytes_per_output_frame * bytes_per_input_frame; } @@ -179,7 +179,7 @@ void port_i2s_allocate_init(i2s_t *self, bool left_justified) { .dma_buf_len = 128, // in _frames_, so 128 is 512 bytes per dma buf .use_apll = false, }; - ESP_CALL_RAISE(i2s_driver_install(self->instance, &i2s_config, I2S_QUEUE_SIZE, &i2s_queues[self->instance])); + CHECK_ESP_RESULT(i2s_driver_install(self->instance, &i2s_config, I2S_QUEUE_SIZE, &i2s_queues[self->instance])); if (!xTaskCreate(i2s_event_task, "I2S_task", 3 * configMINIMAL_STACK_SIZE, self, CONFIG_PTHREAD_TASK_PRIO_DEFAULT, &i2s_tasks[self->instance])) { mp_raise_OSError_msg(translate("xTaskCreate failed")); @@ -210,7 +210,7 @@ void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { audiosample_reset_buffer(self->sample, false, 0); - ESP_CALL_RAISE(i2s_set_sample_rates(self->instance, audiosample_sample_rate(sample))); + CHECK_ESP_RESULT(i2s_set_sample_rates(self->instance, audiosample_sample_rate(sample))); background_callback_add(&self->callback, i2s_callback_fun, self); } @@ -233,13 +233,13 @@ void port_i2s_stop(i2s_t *self) { void port_i2s_pause(i2s_t *self) { if (!self->paused) { self->paused = true; - ESP_CALL_RAISE(i2s_stop(self->instance)); + CHECK_ESP_RESULT(i2s_stop(self->instance)); } } void port_i2s_resume(i2s_t *self) { if (self->paused) { self->paused = false; - ESP_CALL_RAISE(i2s_start(self->instance)); + CHECK_ESP_RESULT(i2s_start(self->instance)); } } -- cgit v1.2.3 From 4735cf4747764f0694e1285085e99f06a7a11a81 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 9 Jan 2021 13:48:36 -0600 Subject: esp32s2: audiobusio: move i2s_common inside Originally, I believed the implementation might be shared with AudioOut, as on the ESP32 (non-S2) the I2S peripheral was also used to drive the DAC. However, this is not the case on ESP32-S2 and appears it will not be the case with the ESP32-S3 or -C3, to the extent that there's skeletal support for either of them in esp-idf master branch. However, it could still be shared by I2SIn or PDMIn (the latter being hypothetically implemented as I2SIn + digital postprocessing like we did in the atmel-sam port, to my understanding), so I moved it to the common-hal folder. --- ports/esp32s2/Makefile | 1 - ports/esp32s2/common-hal/audiobusio/I2SOut.h | 2 +- ports/esp32s2/common-hal/audiobusio/__init__.c | 245 +++++++++++++++++++++++++ ports/esp32s2/common-hal/audiobusio/__init__.h | 61 ++++++ ports/esp32s2/i2s_common.c | 245 ------------------------- ports/esp32s2/i2s_common.h | 61 ------ ports/esp32s2/supervisor/port.c | 2 +- 7 files changed, 308 insertions(+), 309 deletions(-) delete mode 100644 ports/esp32s2/i2s_common.c delete mode 100644 ports/esp32s2/i2s_common.h (limited to 'ports') diff --git a/ports/esp32s2/Makefile b/ports/esp32s2/Makefile index 88bc8ce63..9827b555c 100644 --- a/ports/esp32s2/Makefile +++ b/ports/esp32s2/Makefile @@ -175,7 +175,6 @@ CFLAGS += -DCFG_TUSB_MCU=OPT_MCU_ESP32S2 -DCFG_TUSB_OS=OPT_OS_FREERTOS -DCFG_TUD SRC_C += \ background.c \ fatfs_port.c \ - i2s_common.c \ mphalport.c \ bindings/espidf/__init__.c \ boards/$(BOARD)/board.c \ diff --git a/ports/esp32s2/common-hal/audiobusio/I2SOut.h b/ports/esp32s2/common-hal/audiobusio/I2SOut.h index 90266342c..891e9af67 100644 --- a/ports/esp32s2/common-hal/audiobusio/I2SOut.h +++ b/ports/esp32s2/common-hal/audiobusio/I2SOut.h @@ -29,7 +29,7 @@ #include "supervisor/background_callback.h" #include "common-hal/microcontroller/Pin.h" -#include "i2s_common.h" +#include "common-hal/audiobusio/__init__.h" // Some boards don't implement I2SOut, so suppress any routines from here. #if CIRCUITPY_AUDIOBUSIO_I2SOUT diff --git a/ports/esp32s2/common-hal/audiobusio/__init__.c b/ports/esp32s2/common-hal/audiobusio/__init__.c index e69de29bb..01c77c531 100644 --- a/ports/esp32s2/common-hal/audiobusio/__init__.c +++ b/ports/esp32s2/common-hal/audiobusio/__init__.c @@ -0,0 +1,245 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" + +#include "common-hal/audiobusio/__init__.h" +#include "bindings/espidf/__init__.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "shared-module/audiocore/__init__.h" + +#define I2S_QUEUE_SIZE (3) + +static i2s_t *i2s_instance[I2S_NUM_MAX]; +static QueueHandle_t i2s_queues[I2S_NUM_MAX]; +static TaskHandle_t i2s_tasks[I2S_NUM_MAX]; + +static int8_t port_i2s_allocate(void) { +#if defined(I2S_NUM_1) + if(!i2s_instance[1]) return 1; +#endif + if(!i2s_instance[0]) return 0; + + mp_raise_RuntimeError(translate("Peripheral in use")); +} + +void port_i2s_reset_instance(int i) { + assert(i >= 0 && i < I2S_NUM_MAX); + if (i2s_tasks[i]) { + vTaskDelete(i2s_tasks[i]); + } + i2s_tasks[i] = NULL; + + (void)i2s_driver_uninstall(i); + i2s_instance[i] = NULL; +} + +void i2s_reset(void) { + for (int i=0; i < I2S_NUM_MAX; i++) { + port_i2s_reset_instance(i); + } +} + +static void i2s_fill_buffer(i2s_t *self) { + if (self->instance < 0 || self->instance >= I2S_NUM_MAX) { + return; + } +#define STACK_BUFFER_SIZE (512) + int16_t signed_samples[STACK_BUFFER_SIZE / sizeof(int16_t)]; + + if (!self->playing || self->paused || !self->sample || self->stopping) { + memset(signed_samples, 0, sizeof(signed_samples)); + + size_t bytes_written = 0; + do { + CHECK_ESP_RESULT(i2s_write(self->instance, signed_samples, sizeof(signed_samples), &bytes_written, 0)); + } while (bytes_written != 0); + return; + } + while (!self->stopping) { + if (self->sample_data == self->sample_end) { + uint32_t sample_buffer_length; + audioio_get_buffer_result_t get_buffer_result = + audiosample_get_buffer(self->sample, false, 0, + &self->sample_data, &sample_buffer_length); + self->sample_end = self->sample_data + sample_buffer_length; + if (get_buffer_result == GET_BUFFER_DONE) { + if (self->loop) { + audiosample_reset_buffer(self->sample, false, 0); + } else { + self->stopping = true; + break; + } + } + if (get_buffer_result == GET_BUFFER_ERROR || sample_buffer_length == 0) { + self->stopping = true; + break; + } + } + size_t bytes_written = 0; + size_t bytecount = self->sample_end - self->sample_data; + if (self->samples_signed && self->channel_count == 2) { + if (self->bytes_per_sample == 2) { + CHECK_ESP_RESULT(i2s_write(self->instance, self->sample_data, bytecount, &bytes_written, 0)); + } else { + CHECK_ESP_RESULT(i2s_write_expand(self->instance, self->sample_data, bytecount, 8, 16, &bytes_written, 0)); + } + } else { + const size_t bytes_per_output_frame = 4; + size_t bytes_per_input_frame = self->channel_count * self->bytes_per_sample; + size_t framecount = MIN(STACK_BUFFER_SIZE / bytes_per_output_frame, bytecount / bytes_per_input_frame); + if (self->samples_signed) { + assert(self->channel_count == 1); + if (self->bytes_per_sample == 1) { + audiosample_convert_s8m_s16s(signed_samples, (int8_t*)(void*)self->sample_data, framecount); + } else { + audiosample_convert_s16m_s16s(signed_samples, (int16_t*)(void*)self->sample_data, framecount); + } + } else { + if (self->channel_count == 1) { + if (self->bytes_per_sample == 1) { + audiosample_convert_u8m_s16s(signed_samples, (uint8_t*)(void*)self->sample_data, framecount); + } else { + audiosample_convert_u16m_s16s(signed_samples, (uint16_t*)(void*)self->sample_data, framecount); + } + } else { + if (self->bytes_per_sample == 1) { + audiosample_convert_u8s_s16s(signed_samples, (uint8_t*)(void*)self->sample_data, framecount); + } else { + audiosample_convert_u16s_s16s(signed_samples, (uint16_t*)(void*)self->sample_data, framecount); + } + } + } + size_t expanded_bytes_written = 0; + CHECK_ESP_RESULT(i2s_write(self->instance, signed_samples, bytes_per_output_frame*framecount, &expanded_bytes_written, 0)); + assert(expanded_bytes_written % 4 == 0); + bytes_written = expanded_bytes_written / bytes_per_output_frame * bytes_per_input_frame; + } + self->sample_data += bytes_written; + // We have filled the DMA buffer + if (!bytes_written) { + break; + } + } +} + +static void i2s_callback_fun(void *self_in) { + i2s_t *self = self_in; + i2s_fill_buffer(self); +} + +static void i2s_event_task(void *self_in) { + i2s_t *self = self_in; + while(true) { + i2s_event_type_t event; + BaseType_t result = xQueueReceive(i2s_queues[self->instance], &event, portMAX_DELAY); + if (result && event == I2S_EVENT_TX_DONE) { + background_callback_add(&self->callback, i2s_callback_fun, self_in); + } + } +} + +void port_i2s_allocate_init(i2s_t *self, bool left_justified) { + self->instance = port_i2s_allocate(); + + i2s_config_t i2s_config = { + .mode = I2S_MODE_MASTER | I2S_MODE_TX, + .sample_rate = 44100, + .bits_per_sample = 16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = left_justified ? I2S_COMM_FORMAT_STAND_I2S : I2S_COMM_FORMAT_STAND_I2S, + .dma_buf_count = 2, + .dma_buf_len = 128, // in _frames_, so 128 is 512 bytes per dma buf + .use_apll = false, + }; + CHECK_ESP_RESULT(i2s_driver_install(self->instance, &i2s_config, I2S_QUEUE_SIZE, &i2s_queues[self->instance])); + + if (!xTaskCreate(i2s_event_task, "I2S_task", 3 * configMINIMAL_STACK_SIZE, self, CONFIG_PTHREAD_TASK_PRIO_DEFAULT, &i2s_tasks[self->instance])) { + mp_raise_OSError_msg(translate("xTaskCreate failed")); + } + i2s_instance[self->instance] = self; + +} + + +void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { + self->sample = sample; + self->loop = loop; + self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; + self->channel_count = audiosample_channel_count(sample); + bool single_buffer; + bool samples_signed; + uint32_t max_buffer_length; + uint8_t spacing; + audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, + &max_buffer_length, &spacing); + self->samples_signed = samples_signed; + self->playing = true; + self->paused = false; + self->stopping = false; + self->sample_data = self->sample_end = NULL; + // We always output stereo so output twice as many bits. + // uint16_t bits_per_sample_output = bits_per_sample * 2; + + audiosample_reset_buffer(self->sample, false, 0); + + CHECK_ESP_RESULT(i2s_set_sample_rates(self->instance, audiosample_sample_rate(sample))); + + background_callback_add(&self->callback, i2s_callback_fun, self); +} + +bool port_i2s_playing(i2s_t *self) { + return self->playing && !self->stopping; +} + +bool port_i2s_paused(i2s_t *self) { + return self->paused; +} + +void port_i2s_stop(i2s_t *self) { + self->sample = NULL; + self->paused = false; + self->playing = false; + self->stopping = false; +} + +void port_i2s_pause(i2s_t *self) { + if (!self->paused) { + self->paused = true; + CHECK_ESP_RESULT(i2s_stop(self->instance)); + } +} + +void port_i2s_resume(i2s_t *self) { + if (self->paused) { + self->paused = false; + CHECK_ESP_RESULT(i2s_start(self->instance)); + } +} diff --git a/ports/esp32s2/common-hal/audiobusio/__init__.h b/ports/esp32s2/common-hal/audiobusio/__init__.h index e69de29bb..7709735da 100644 --- a/ports/esp32s2/common-hal/audiobusio/__init__.h +++ b/ports/esp32s2/common-hal/audiobusio/__init__.h @@ -0,0 +1,61 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include "py/obj.h" + +#include "supervisor/background_callback.h" + +#include "driver/i2s.h" + +typedef struct { + mp_obj_t *sample; + bool left_justified; + bool loop; + bool paused; + bool playing; + bool stopping; + bool samples_signed; + int8_t bytes_per_sample; + int8_t channel_count; + int8_t instance; + uint16_t buffer_length; + uint8_t *sample_data, *sample_end; + i2s_config_t i2s_config; + background_callback_t callback; +} i2s_t; + + +void port_i2s_allocate_init(i2s_t *self, bool left_justified); +void port_i2s_reset_instance(int i); +void i2s_reset(void); +void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop); +void port_i2s_stop(i2s_t *self); +bool port_i2s_playing(i2s_t *self); +bool port_i2s_paused(i2s_t *self); +void port_i2s_pause(i2s_t *self); +void port_i2s_resume(i2s_t *self); diff --git a/ports/esp32s2/i2s_common.c b/ports/esp32s2/i2s_common.c deleted file mode 100644 index f967e9a09..000000000 --- a/ports/esp32s2/i2s_common.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2020 Jeff Epler for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include - -#include "py/runtime.h" - -#include "i2s_common.h" -#include "bindings/espidf/__init__.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -#include "shared-module/audiocore/__init__.h" - -#define I2S_QUEUE_SIZE (3) - -static i2s_t *i2s_instance[I2S_NUM_MAX]; -static QueueHandle_t i2s_queues[I2S_NUM_MAX]; -static TaskHandle_t i2s_tasks[I2S_NUM_MAX]; - -static int8_t port_i2s_allocate(void) { -#if defined(I2S_NUM_1) - if(!i2s_instance[1]) return 1; -#endif - if(!i2s_instance[0]) return 0; - - mp_raise_RuntimeError(translate("Peripheral in use")); -} - -void port_i2s_reset_instance(int i) { - assert(i >= 0 && i < I2S_NUM_MAX); - if (i2s_tasks[i]) { - vTaskDelete(i2s_tasks[i]); - } - i2s_tasks[i] = NULL; - - (void)i2s_driver_uninstall(i); - i2s_instance[i] = NULL; -} - -void i2s_reset(void) { - for (int i=0; i < I2S_NUM_MAX; i++) { - port_i2s_reset_instance(i); - } -} - -static void i2s_fill_buffer(i2s_t *self) { - if (self->instance < 0 || self->instance >= I2S_NUM_MAX) { - return; - } -#define STACK_BUFFER_SIZE (512) - int16_t signed_samples[STACK_BUFFER_SIZE / sizeof(int16_t)]; - - if (!self->playing || self->paused || !self->sample || self->stopping) { - memset(signed_samples, 0, sizeof(signed_samples)); - - size_t bytes_written = 0; - do { - CHECK_ESP_RESULT(i2s_write(self->instance, signed_samples, sizeof(signed_samples), &bytes_written, 0)); - } while (bytes_written != 0); - return; - } - while (!self->stopping) { - if (self->sample_data == self->sample_end) { - uint32_t sample_buffer_length; - audioio_get_buffer_result_t get_buffer_result = - audiosample_get_buffer(self->sample, false, 0, - &self->sample_data, &sample_buffer_length); - self->sample_end = self->sample_data + sample_buffer_length; - if (get_buffer_result == GET_BUFFER_DONE) { - if (self->loop) { - audiosample_reset_buffer(self->sample, false, 0); - } else { - self->stopping = true; - break; - } - } - if (get_buffer_result == GET_BUFFER_ERROR || sample_buffer_length == 0) { - self->stopping = true; - break; - } - } - size_t bytes_written = 0; - size_t bytecount = self->sample_end - self->sample_data; - if (self->samples_signed && self->channel_count == 2) { - if (self->bytes_per_sample == 2) { - CHECK_ESP_RESULT(i2s_write(self->instance, self->sample_data, bytecount, &bytes_written, 0)); - } else { - CHECK_ESP_RESULT(i2s_write_expand(self->instance, self->sample_data, bytecount, 8, 16, &bytes_written, 0)); - } - } else { - const size_t bytes_per_output_frame = 4; - size_t bytes_per_input_frame = self->channel_count * self->bytes_per_sample; - size_t framecount = MIN(STACK_BUFFER_SIZE / bytes_per_output_frame, bytecount / bytes_per_input_frame); - if (self->samples_signed) { - assert(self->channel_count == 1); - if (self->bytes_per_sample == 1) { - audiosample_convert_s8m_s16s(signed_samples, (int8_t*)(void*)self->sample_data, framecount); - } else { - audiosample_convert_s16m_s16s(signed_samples, (int16_t*)(void*)self->sample_data, framecount); - } - } else { - if (self->channel_count == 1) { - if (self->bytes_per_sample == 1) { - audiosample_convert_u8m_s16s(signed_samples, (uint8_t*)(void*)self->sample_data, framecount); - } else { - audiosample_convert_u16m_s16s(signed_samples, (uint16_t*)(void*)self->sample_data, framecount); - } - } else { - if (self->bytes_per_sample == 1) { - audiosample_convert_u8s_s16s(signed_samples, (uint8_t*)(void*)self->sample_data, framecount); - } else { - audiosample_convert_u16s_s16s(signed_samples, (uint16_t*)(void*)self->sample_data, framecount); - } - } - } - size_t expanded_bytes_written = 0; - CHECK_ESP_RESULT(i2s_write(self->instance, signed_samples, bytes_per_output_frame*framecount, &expanded_bytes_written, 0)); - assert(expanded_bytes_written % 4 == 0); - bytes_written = expanded_bytes_written / bytes_per_output_frame * bytes_per_input_frame; - } - self->sample_data += bytes_written; - // We have filled the DMA buffer - if (!bytes_written) { - break; - } - } -} - -static void i2s_callback_fun(void *self_in) { - i2s_t *self = self_in; - i2s_fill_buffer(self); -} - -static void i2s_event_task(void *self_in) { - i2s_t *self = self_in; - while(true) { - i2s_event_type_t event; - BaseType_t result = xQueueReceive(i2s_queues[self->instance], &event, portMAX_DELAY); - if (result && event == I2S_EVENT_TX_DONE) { - background_callback_add(&self->callback, i2s_callback_fun, self_in); - } - } -} - -void port_i2s_allocate_init(i2s_t *self, bool left_justified) { - self->instance = port_i2s_allocate(); - - i2s_config_t i2s_config = { - .mode = I2S_MODE_MASTER | I2S_MODE_TX, - .sample_rate = 44100, - .bits_per_sample = 16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = left_justified ? I2S_COMM_FORMAT_STAND_I2S : I2S_COMM_FORMAT_STAND_I2S, - .dma_buf_count = 2, - .dma_buf_len = 128, // in _frames_, so 128 is 512 bytes per dma buf - .use_apll = false, - }; - CHECK_ESP_RESULT(i2s_driver_install(self->instance, &i2s_config, I2S_QUEUE_SIZE, &i2s_queues[self->instance])); - - if (!xTaskCreate(i2s_event_task, "I2S_task", 3 * configMINIMAL_STACK_SIZE, self, CONFIG_PTHREAD_TASK_PRIO_DEFAULT, &i2s_tasks[self->instance])) { - mp_raise_OSError_msg(translate("xTaskCreate failed")); - } - i2s_instance[self->instance] = self; - -} - - -void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop) { - self->sample = sample; - self->loop = loop; - self->bytes_per_sample = audiosample_bits_per_sample(sample) / 8; - self->channel_count = audiosample_channel_count(sample); - bool single_buffer; - bool samples_signed; - uint32_t max_buffer_length; - uint8_t spacing; - audiosample_get_buffer_structure(sample, false, &single_buffer, &samples_signed, - &max_buffer_length, &spacing); - self->samples_signed = samples_signed; - self->playing = true; - self->paused = false; - self->stopping = false; - self->sample_data = self->sample_end = NULL; - // We always output stereo so output twice as many bits. - // uint16_t bits_per_sample_output = bits_per_sample * 2; - - audiosample_reset_buffer(self->sample, false, 0); - - CHECK_ESP_RESULT(i2s_set_sample_rates(self->instance, audiosample_sample_rate(sample))); - - background_callback_add(&self->callback, i2s_callback_fun, self); -} - -bool port_i2s_playing(i2s_t *self) { - return self->playing && !self->stopping; -} - -bool port_i2s_paused(i2s_t *self) { - return self->paused; -} - -void port_i2s_stop(i2s_t *self) { - self->sample = NULL; - self->paused = false; - self->playing = false; - self->stopping = false; -} - -void port_i2s_pause(i2s_t *self) { - if (!self->paused) { - self->paused = true; - CHECK_ESP_RESULT(i2s_stop(self->instance)); - } -} - -void port_i2s_resume(i2s_t *self) { - if (self->paused) { - self->paused = false; - CHECK_ESP_RESULT(i2s_start(self->instance)); - } -} diff --git a/ports/esp32s2/i2s_common.h b/ports/esp32s2/i2s_common.h deleted file mode 100644 index 7709735da..000000000 --- a/ports/esp32s2/i2s_common.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2020 Jeff Epler for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include "py/obj.h" - -#include "supervisor/background_callback.h" - -#include "driver/i2s.h" - -typedef struct { - mp_obj_t *sample; - bool left_justified; - bool loop; - bool paused; - bool playing; - bool stopping; - bool samples_signed; - int8_t bytes_per_sample; - int8_t channel_count; - int8_t instance; - uint16_t buffer_length; - uint8_t *sample_data, *sample_end; - i2s_config_t i2s_config; - background_callback_t callback; -} i2s_t; - - -void port_i2s_allocate_init(i2s_t *self, bool left_justified); -void port_i2s_reset_instance(int i); -void i2s_reset(void); -void port_i2s_play(i2s_t *self, mp_obj_t sample, bool loop); -void port_i2s_stop(i2s_t *self); -bool port_i2s_playing(i2s_t *self); -bool port_i2s_paused(i2s_t *self); -void port_i2s_pause(i2s_t *self); -void port_i2s_resume(i2s_t *self); diff --git a/ports/esp32s2/supervisor/port.c b/ports/esp32s2/supervisor/port.c index a6b6a82aa..a5215fdef 100644 --- a/ports/esp32s2/supervisor/port.c +++ b/ports/esp32s2/supervisor/port.c @@ -62,7 +62,7 @@ #include "components/soc/soc/esp32s2/include/soc/rtc_cntl_reg.h" #if CIRCUITPY_AUDIOBUSIO -#include "i2s_common.h" +#include "common-hal/audiobusio/__init__.h" #endif #define HEAP_SIZE (48 * 1024) -- cgit v1.2.3 From e20c65d8f044cb9522cd1a7f4a3030efefa77cbb Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 9 Jan 2021 13:59:54 -0600 Subject: background tasks: Add, use port_wake_main_task Some ports need an extra operation to ensure that the main task is awoken so that a queued background task will execute during an ongoing light sleep. This removes the need to enable supervisor ticks while I2SOut is operating. Closes: #3952 --- ports/esp32s2/common-hal/audiobusio/I2SOut.c | 4 ---- ports/esp32s2/supervisor/port.c | 12 +++++++++++- supervisor/port.h | 4 ++++ supervisor/shared/background_callback.c | 4 ++++ 4 files changed, 19 insertions(+), 5 deletions(-) (limited to 'ports') diff --git a/ports/esp32s2/common-hal/audiobusio/I2SOut.c b/ports/esp32s2/common-hal/audiobusio/I2SOut.c index ee16c6ca8..86322be86 100644 --- a/ports/esp32s2/common-hal/audiobusio/I2SOut.c +++ b/ports/esp32s2/common-hal/audiobusio/I2SOut.c @@ -63,8 +63,6 @@ void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t* self, self->bit_clock = bit_clock; self->word_select = word_select; self->data = data; - - supervisor_enable_tick(); } bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t* self) { @@ -95,8 +93,6 @@ void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t* self) { port_i2s_reset_instance(self->peripheral.instance); } self->peripheral.instance = -1; - - supervisor_disable_tick(); } void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t* self, diff --git a/ports/esp32s2/supervisor/port.c b/ports/esp32s2/supervisor/port.c index a5215fdef..6491e7430 100644 --- a/ports/esp32s2/supervisor/port.c +++ b/ports/esp32s2/supervisor/port.c @@ -111,6 +111,12 @@ safe_mode_t port_init(void) { heap = NULL; never_reset_module_internal_pins(); + #if defined(DEBUG) + // debug UART + common_hal_never_reset_pin(&pin_GPIO43); + common_hal_never_reset_pin(&pin_GPIO44); + #endif + #if defined(DEBUG) || defined(ENABLE_JTAG) // JTAG common_hal_never_reset_pin(&pin_GPIO39); @@ -291,10 +297,14 @@ void port_disable_tick(void) { esp_timer_stop(_tick_timer); } -void sleep_timer_cb(void* arg) { +void port_wake_main_task() { xTaskNotifyGive(circuitpython_task); } +void sleep_timer_cb(void* arg) { + port_wake_main_task(); +} + void port_interrupt_after_ticks(uint32_t ticks) { uint64_t timeout_us = ticks * 1000000ull / 1024; if (esp_timer_start_once(_sleep_timer, timeout_us) != ESP_OK) { diff --git a/supervisor/port.h b/supervisor/port.h index 862400986..812cf715b 100644 --- a/supervisor/port.h +++ b/supervisor/port.h @@ -99,4 +99,8 @@ void port_background_task(void); void port_start_background_task(void); void port_finish_background_task(void); +// Some ports need special handling to wake the main task from an interrupt +// context or other task. The port must implement the necessary code in this +// function. A default weak implementation is provided that does nothing. +void port_wake_main_task(void); #endif // MICROPY_INCLUDED_SUPERVISOR_PORT_H diff --git a/supervisor/shared/background_callback.c b/supervisor/shared/background_callback.c index ef686cbab..288c9a4df 100644 --- a/supervisor/shared/background_callback.c +++ b/supervisor/shared/background_callback.c @@ -38,6 +38,8 @@ STATIC volatile background_callback_t *callback_head, *callback_tail; #define CALLBACK_CRITICAL_BEGIN (common_hal_mcu_disable_interrupts()) #define CALLBACK_CRITICAL_END (common_hal_mcu_enable_interrupts()) +MP_WEAK void port_wake_main_task(void) {} + void background_callback_add_core(background_callback_t *cb) { CALLBACK_CRITICAL_BEGIN; if (cb->prev || callback_head == cb) { @@ -55,6 +57,8 @@ void background_callback_add_core(background_callback_t *cb) { } callback_tail = cb; CALLBACK_CRITICAL_END; + + port_wake_main_task(); } void background_callback_add(background_callback_t *cb, background_callback_fun fun, void *data) { -- cgit v1.2.3