diff --git a/esp32/application.mk b/esp32/application.mk index a9878b71e3..1c2010a0be 100644 --- a/esp32/application.mk +++ b/esp32/application.mk @@ -133,6 +133,7 @@ APP_MODS_SRC_C = $(addprefix mods/,\ pybflash.c \ machspi.c \ machine_i2c.c \ + machine_i2s.c \ machpwm.c \ machcan.c \ modmachine.c \ diff --git a/esp32/mods/machine_i2s.c b/esp32/mods/machine_i2s.c new file mode 100644 index 0000000000..0c1da772ed --- /dev/null +++ b/esp32/mods/machine_i2s.c @@ -0,0 +1,447 @@ +/* + * This file is part of the MicroPython ESP32 project + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Mike Teachman + * + * 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/mpconfig.h" +#include "py/obj.h" +#include "py/runtime.h" + +#include "machine_i2s.h" + +// Adapt calls to "machine_pin_get_id" +#include "machpin.h" +#include "pins.h" +#define machine_pin_get_id(pin) pin_find(pin)->pin_number + +#include "driver/i2s.h" + +// Notes on naming conventions: +// 1. "id" versus "port" +// The MicroPython API identifies instances of a peripheral using "id", while the ESP-IDF uses "port". +// - for example, the first I2S peripheral on the ESP32 would be indicated by id=0 in MicroPython +// and port=0 in ESP-IDF +// 2. any C type, macro, or function prefaced by "i2s" is associated with an ESP-IDF I2S interface definition +// 3. any C type, macro, or function prefaced by "machine_i2s" is associated with the MicroPython implementation of I2S + +typedef struct _machine_i2s_obj_t { + mp_obj_base_t base; + i2s_port_t id; + i2s_comm_format_t standard; + uint8_t mode; + i2s_bits_per_sample_t dataformat; + i2s_channel_fmt_t channelformat; + int32_t samplerate; + int16_t dmacount; + int16_t dmalen; + int32_t apllrate; + int8_t bck; + int8_t ws; + int8_t sdout; + int8_t sdin; + bool used; +} machine_i2s_obj_t; + +// Static object mapping to I2S peripherals +// note: I2S implementation makes use of the following mapping between I2S peripheral and I2S object +// I2S peripheral 1: machine_i2s_obj[0] +// I2S peripheral 2: machine_i2s_obj[1] +STATIC machine_i2s_obj_t machine_i2s_obj[I2S_NUM_MAX] = { + [0].used = false, + [1].used = false }; + +STATIC void machine_i2s_init_helper(machine_i2s_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + + enum { + ARG_bck, + ARG_ws, + ARG_sdout, + ARG_sdin, + ARG_standard, + ARG_mode, + ARG_dataformat, + ARG_channelformat, + ARG_samplerate, + ARG_dmacount, + ARG_dmalen, + ARG_apllrate, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bck, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_sdout, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_sdin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_standard, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = I2S_COMM_FORMAT_I2S} }, + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_dataformat, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_channelformat, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_samplerate, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_dmacount, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 16} }, + { MP_QSTR_dmalen, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, + { MP_QSTR_apllrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // + // ---- Check validity of arguments ---- + // + + // are I2S pin assignments valid? + int8_t bck = args[ARG_bck].u_obj == MP_OBJ_NULL ? -1 : machine_pin_get_id(args[ARG_bck].u_obj); + int8_t ws = args[ARG_ws].u_obj == MP_OBJ_NULL ? -1 : machine_pin_get_id(args[ARG_ws].u_obj); + int8_t sdin = args[ARG_sdin].u_obj == MP_OBJ_NULL ? -1 : machine_pin_get_id(args[ARG_sdin].u_obj); + int8_t sdout = args[ARG_sdout].u_obj == MP_OBJ_NULL ? -1 : machine_pin_get_id(args[ARG_sdout].u_obj); + + if ((sdin == -1) && (args[ARG_mode].u_int == (I2S_MODE_MASTER | I2S_MODE_RX))) { + mp_raise_ValueError("sdin must be specified for RX mode"); + } + + if ((sdout == -1) && (args[ARG_mode].u_int == (I2S_MODE_MASTER | I2S_MODE_TX))) { + mp_raise_ValueError("sdout must be specified for TX mode"); + } + + if ((sdin != -1) && (sdout != -1)) { + mp_raise_ValueError("only one of sdin or sdout can be specified"); + } + + // is Standard valid? + i2s_comm_format_t i2s_commformat = args[ARG_standard].u_int; + if ((i2s_commformat != I2S_COMM_FORMAT_I2S) && + (i2s_commformat != (I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB))) { + mp_raise_ValueError("Standard is not valid"); + } + + // is Mode valid? + i2s_mode_t i2s_mode = args[ARG_mode].u_int; + if ((i2s_mode != (I2S_MODE_MASTER | I2S_MODE_RX)) && + (i2s_mode != (I2S_MODE_MASTER | I2S_MODE_TX))) { + mp_raise_ValueError("Only Master Rx or Master Tx Modes are supported"); + } + + // is Data Format valid? + i2s_bits_per_sample_t i2s_bits_per_sample = args[ARG_dataformat].u_int; + if ((i2s_bits_per_sample != I2S_BITS_PER_SAMPLE_16BIT) && + (i2s_bits_per_sample != I2S_BITS_PER_SAMPLE_24BIT) && + (i2s_bits_per_sample != I2S_BITS_PER_SAMPLE_32BIT)) { + mp_raise_ValueError("Data Format is not valid"); + } + + // is Channel Format valid? + i2s_channel_fmt_t i2s_channelformat = args[ARG_channelformat].u_int; + if ((i2s_channelformat != I2S_CHANNEL_FMT_RIGHT_LEFT) && + (i2s_channelformat != I2S_CHANNEL_FMT_ALL_RIGHT) && + (i2s_channelformat != I2S_CHANNEL_FMT_ALL_LEFT) && + (i2s_channelformat != I2S_CHANNEL_FMT_ONLY_RIGHT) && + (i2s_channelformat != I2S_CHANNEL_FMT_ONLY_LEFT)) { + mp_raise_ValueError("Channel Format is not valid"); + } + + // is Sample Rate valid? + // No validation done: ESP-IDF API does not indicate a valid range for sample rate + + // is DMA Buffer Count valid? + // ESP-IDF API code checks for buffer count in this range: [2, 128] + int16_t i2s_dmacount = args[ARG_dmacount].u_int; + if ((i2s_dmacount < 2) || (i2s_dmacount > 128)) { + mp_raise_ValueError("DMA Buffer Count is not valid. Allowed range is [2, 128]"); + } + + // is DMA Buffer Length valid? + // ESP-IDF API code checks for buffer length in this range: [8, 1024] + int16_t i2s_dmalen = args[ARG_dmalen].u_int; + if ((i2s_dmalen < 8) || (i2s_dmalen > 1024)) { + mp_raise_ValueError("DMA Buffer Length is not valid. Allowed range is [8, 1024]"); + } + + // is APLL Rate valid? + // No validation done: ESP-IDF API does not indicate a valid range for APLL rate + + self->bck = bck; + self->ws = ws; + self->sdout = sdout; + self->sdin = sdin; + self->standard = args[ARG_standard].u_int; + self->mode = args[ARG_mode].u_int; + self->dataformat = args[ARG_dataformat].u_int; + self->channelformat = args[ARG_channelformat].u_int; + self->samplerate = args[ARG_samplerate].u_int; + self->dmacount = args[ARG_dmacount].u_int; + self->dmalen = args[ARG_dmalen].u_int; + self->apllrate = args[ARG_apllrate].u_int; + + i2s_config_t i2s_config; + i2s_config.communication_format = self->standard; + i2s_config.mode = self->mode; + i2s_config.bits_per_sample = self->dataformat; + i2s_config.channel_format = self->channelformat; + i2s_config.sample_rate = self->samplerate; + i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LOWMED; // allows simultaneous use of both I2S channels + i2s_config.dma_buf_count = self->dmacount; + i2s_config.dma_buf_len = self->dmalen; + if (self->apllrate != 0) { + i2s_config.use_apll = true; + } else { + i2s_config.use_apll = false; + } + i2s_config.fixed_mclk = self->apllrate; + + // uninstall I2S driver when changes are being made to an active I2S peripheral + if (self->used) { + i2s_driver_uninstall(self->id); + } + + esp_err_t ret = i2s_driver_install(self->id, &i2s_config, 0, NULL); + switch (ret) { + case ESP_OK: + break; + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, "I2S driver install: Parameter error"); + break; + case ESP_ERR_NO_MEM: + mp_raise_msg(&mp_type_OSError, "I2S driver install: Out of memory"); + break; + default: + // this error not documented in ESP-IDF + mp_raise_msg(&mp_type_OSError, "I2S driver install: Undocumented error"); + break; + } + + i2s_pin_config_t pin_config; + pin_config.bck_io_num = self->bck; + pin_config.ws_io_num = self->ws; + pin_config.data_out_num = self->sdout; + pin_config.data_in_num = self->sdin; + + ret = i2s_set_pin(self->id, &pin_config); + switch (ret) { + case ESP_OK: + break; + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, "I2S set pin: Parameter error"); + break; + case ESP_FAIL: + mp_raise_msg(&mp_type_OSError, "I2S set pin: IO error"); + break; + default: + // this error not documented in ESP-IDF + mp_raise_msg(&mp_type_OSError, "I2S set pin: Undocumented error"); + break; + } + + self->used = true; +} + +/******************************************************************************/ +// MicroPython bindings for I2S +STATIC void machine_i2s_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "I2S(id=%u, bck=%d, ws=%d, sdout=%d, sdin=%d\n" + "standard=%u, mode=%u,\n" + "dataformat=%u, channelformat=%u,\n" + "samplerate=%d,\n" + "dmacount=%d, dmalen=%d,\n" + "apllrate=%d)", + self->id, self->bck, self->ws, self->sdout, self->sdin, + self->standard, self->mode, + self->dataformat, self->channelformat, + self->samplerate, + self->dmacount, self->dmalen, + self->apllrate + ); +} + +STATIC mp_obj_t machine_i2s_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { + mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); + + machine_i2s_obj_t *self; + + // note: it is safe to assume that the arg pointer below references a positional argument because the arg check above + // guarantees that at least one positional argument has been provided + i2s_port_t i2s_id = mp_obj_get_int(args[0]); + if (i2s_id == I2S_NUM_0) { + self = &machine_i2s_obj[0]; + } else if (i2s_id == I2S_NUM_1) { + self = &machine_i2s_obj[1]; + } else { + mp_raise_ValueError("I2S ID is not valid"); + } + + self->base.type = &machine_i2s_type; + self->id = i2s_id; + + // is I2S peripheral already in use? + if (self->used) { + mp_raise_ValueError("I2S port is already in use"); + } + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); + // note: "args + 1" below has the effect of skipping over the ID argument + machine_i2s_init_helper(self, n_pos_args - 1, args + 1, &kw_args); + + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t machine_i2s_init(mp_uint_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // note: "pos_args + 1" below has the effect of skipping over "self" + machine_i2s_init_helper(pos_args[0], n_pos_args - 1, pos_args + 1, kw_args); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_init_obj, 1, machine_i2s_init); + +STATIC mp_obj_t machine_i2s_readinto(mp_uint_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buf, ARG_timeout }; + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(args), allowed_args, args); + + machine_i2s_obj_t *self = pos_args[0]; + + if (!self->used) { + mp_raise_ValueError("I2S port is not initialized"); + } + + if (self->mode != (I2S_MODE_MASTER | I2S_MODE_RX)) { + mp_raise_ValueError("I2S not configured for read method"); + } + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_WRITE); + + TickType_t timeout_in_ticks = portMAX_DELAY; + if (args[ARG_timeout].u_int != -1) { + timeout_in_ticks = pdMS_TO_TICKS(args[ARG_timeout].u_int); + } + + uint32_t num_bytes_read = 0; + esp_err_t ret = i2s_read(self->id, bufinfo.buf, bufinfo.len, &num_bytes_read, timeout_in_ticks); + switch (ret) { + case ESP_OK: + break; + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, "I2S read: Parameter error"); + break; + default: + // this error not documented in ESP-IDF + mp_raise_msg(&mp_type_OSError, "I2S read: Undocumented error"); + break; + } + + return mp_obj_new_int(num_bytes_read); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_readinto_obj, 2, machine_i2s_readinto); + +STATIC mp_obj_t machine_i2s_write(mp_uint_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buf, ARG_timeout }; + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_pos_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(args), allowed_args, args); + + machine_i2s_obj_t *self = pos_args[0]; + + if (!self->used) { + mp_raise_ValueError("I2S port is not initialized"); + } + + if (self->mode != (I2S_MODE_MASTER | I2S_MODE_TX)) { + mp_raise_ValueError("I2S not configured for write method"); + } + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_WRITE); + + TickType_t timeout_in_ticks = portMAX_DELAY; + if (args[ARG_timeout].u_int != -1) { + timeout_in_ticks = pdMS_TO_TICKS(args[ARG_timeout].u_int); + } + + uint32_t num_bytes_written = 0; + esp_err_t ret = i2s_write(self->id, bufinfo.buf, bufinfo.len, &num_bytes_written, timeout_in_ticks); + switch (ret) { + case ESP_OK: + break; + case ESP_ERR_INVALID_ARG: + mp_raise_msg(&mp_type_OSError, "I2S write: Parameter error"); + break; + default: + // this error not documented in ESP-IDF + mp_raise_msg(&mp_type_OSError, "I2S write: Undocumented error"); + break; + } + + return mp_obj_new_int(num_bytes_written); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_write_obj, 2, machine_i2s_write); + +STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in) { + machine_i2s_obj_t *self = self_in; + i2s_driver_uninstall(self->id); + self->used = false; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_i2s_deinit_obj, machine_i2s_deinit); + +STATIC const mp_rom_map_elem_t machine_i2s_locals_dict_table[] = { + // Methods + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2s_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&machine_i2s_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&machine_i2s_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2s_deinit_obj) }, + + // Constants + { MP_ROM_QSTR(MP_QSTR_NUM0), MP_ROM_INT(I2S_NUM_0) }, + { MP_ROM_QSTR(MP_QSTR_NUM1), MP_ROM_INT(I2S_NUM_1) }, + { MP_ROM_QSTR(MP_QSTR_PHILIPS), MP_ROM_INT(I2S_COMM_FORMAT_I2S) }, + { MP_ROM_QSTR(MP_QSTR_LSB), MP_ROM_INT(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB) }, + // note: ESP-IDF does not implement the MSB standard (even though the Macro I2S_COMM_FORMAT_I2S_MSB is defined) + { MP_ROM_QSTR(MP_QSTR_MASTER_RX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_RX) }, + { MP_ROM_QSTR(MP_QSTR_MASTER_TX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_TX) }, + { MP_ROM_QSTR(MP_QSTR_B16), MP_ROM_INT(I2S_BITS_PER_SAMPLE_16BIT) }, + { MP_ROM_QSTR(MP_QSTR_B24), MP_ROM_INT(I2S_BITS_PER_SAMPLE_24BIT) }, + { MP_ROM_QSTR(MP_QSTR_B32), MP_ROM_INT(I2S_BITS_PER_SAMPLE_32BIT) }, + { MP_ROM_QSTR(MP_QSTR_RIGHT_LEFT), MP_ROM_INT(I2S_CHANNEL_FMT_RIGHT_LEFT) }, + { MP_ROM_QSTR(MP_QSTR_ALL_RIGHT), MP_ROM_INT(I2S_CHANNEL_FMT_ALL_RIGHT) }, + { MP_ROM_QSTR(MP_QSTR_ALL_LEFT), MP_ROM_INT(I2S_CHANNEL_FMT_ALL_LEFT) }, + { MP_ROM_QSTR(MP_QSTR_ONLY_RIGHT), MP_ROM_INT(I2S_CHANNEL_FMT_ONLY_RIGHT) }, + { MP_ROM_QSTR(MP_QSTR_ONLY_LEFT), MP_ROM_INT(I2S_CHANNEL_FMT_ONLY_LEFT) }, +}; +MP_DEFINE_CONST_DICT(machine_i2s_locals_dict, machine_i2s_locals_dict_table); + +const mp_obj_type_t machine_i2s_type = { + { &mp_type_type }, + .name = MP_QSTR_I2S, + .print = machine_i2s_print, + .make_new = machine_i2s_make_new, + .locals_dict = (mp_obj_dict_t *) &machine_i2s_locals_dict, +}; diff --git a/esp32/mods/machine_i2s.h b/esp32/mods/machine_i2s.h new file mode 100644 index 0000000000..ecf128597a --- /dev/null +++ b/esp32/mods/machine_i2s.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython ESP32 project + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Mike Teachman + * + * 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. + */ + +#ifndef __MICROPY_INCLUDED_EXTMOD_MACHINE_I2S_H__ +#define __MICROPY_INCLUDED_EXTMOD_MACHINE_I2S_H__ + +#include "py/obj.h" + +extern const mp_obj_type_t machine_i2s_type; + +#endif // __MICROPY_INCLUDED_EXTMOD_MACHINE_I2S_H__ diff --git a/esp32/mods/modmachine.c b/esp32/mods/modmachine.c index f855570cdb..a3941947a3 100644 --- a/esp32/mods/modmachine.c +++ b/esp32/mods/modmachine.c @@ -61,6 +61,7 @@ #include "machuart.h" #include "machtimer.h" #include "machine_i2c.h" +#include "machine_i2s.h" #include "machspi.h" #include "machpwm.h" #include "machrtc.h" @@ -410,6 +411,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_UART), (mp_obj_t)&mach_uart_type }, { MP_OBJ_NEW_QSTR(MP_QSTR_SPI), (mp_obj_t)&mach_spi_type }, { MP_OBJ_NEW_QSTR(MP_QSTR_I2C), (mp_obj_t)&machine_i2c_type }, + { MP_OBJ_NEW_QSTR(MP_QSTR_I2S), (mp_obj_t)&machine_i2s_type }, { MP_OBJ_NEW_QSTR(MP_QSTR_PWM), (mp_obj_t)&mach_pwm_timer_type }, { MP_OBJ_NEW_QSTR(MP_QSTR_ADC), (mp_obj_t)&pyb_adc_type }, { MP_OBJ_NEW_QSTR(MP_QSTR_DAC), (mp_obj_t)&pyb_dac_type }, pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy