diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index feca2dfdf07..336acfbb5a8 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -86,6 +86,7 @@ jobs: name: Build with ESP-IDF ${{ matrix.idf_ver }} for ${{ matrix.idf_target }} runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: # The version names here correspond to the versions of espressif/idf Docker image. # See https://hub.docker.com/r/espressif/idf/tags and diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ed5ded6b08..9eb7f51ac2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,9 @@ set(LIBRARY_SRCS libraries/BluetoothSerial/src/BTScanResultsSet.cpp libraries/DNSServer/src/DNSServer.cpp libraries/EEPROM/src/EEPROM.cpp + libraries/ESP_I2S/src/ESP_I2S.cpp + libraries/ESP_SR/src/ESP_SR.cpp + libraries/ESP_SR/src/esp32-hal-sr.c libraries/ESPmDNS/src/ESPmDNS.cpp libraries/Ethernet/src/ETH.cpp libraries/FFat/src/FFat.cpp @@ -91,7 +94,6 @@ set(LIBRARY_SRCS libraries/HTTPUpdate/src/HTTPUpdate.cpp libraries/LittleFS/src/LittleFS.cpp libraries/Insights/src/Insights.cpp - libraries/I2S/src/I2S.cpp libraries/NetBIOS/src/NetBIOS.cpp libraries/Networking/src/ESP_Network_Interface.cpp libraries/Preferences/src/Preferences.cpp @@ -180,6 +182,8 @@ set(includedirs libraries/BluetoothSerial/src libraries/DNSServer/src libraries/EEPROM/src + libraries/ESP_I2S/src + libraries/ESP_SR/src libraries/ESP32/src libraries/ESPmDNS/src libraries/Ethernet/src @@ -189,7 +193,6 @@ set(includedirs libraries/HTTPUpdate/src libraries/LittleFS/src libraries/Insights/src - libraries/I2S/src libraries/NetBIOS/src libraries/Networking/src libraries/Preferences/src diff --git a/cores/esp32/HardwareSerial.cpp b/cores/esp32/HardwareSerial.cpp index 1780da41213..086b57d8367 100644 --- a/cores/esp32/HardwareSerial.cpp +++ b/cores/esp32/HardwareSerial.cpp @@ -183,7 +183,7 @@ _eventTask(NULL) HardwareSerial::~HardwareSerial() { - end(); + end(true); // explicit Full UART termination #if !CONFIG_DISABLE_HAL_LOCKS if(_lock != NULL){ vSemaphoreDelete(_lock); @@ -398,7 +398,7 @@ void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, in if(_uart) { // in this case it is a begin() over a previous begin() - maybe to change baud rate // thus do not disable debug output - end(false); + end(false); // disables IDF UART driver and UART event Task + sets _uart to NULL } // IDF UART driver keeps Pin setting on restarting. Negative Pin number will keep it unmodified. @@ -413,7 +413,7 @@ void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, in yield(); } - end(false); + end(false); // disables IDF UART driver and UART event Task + sets _uart to NULL if(detectedBaudRate) { delay(100); // Give some time... @@ -470,10 +470,8 @@ void HardwareSerial::end(bool fullyTerminate) // do not invalidate callbacks, detach pins, invalidate DBG output uart_driver_delete(_uart_nr); } - - uartEnd(_uart_nr); - _uart = 0; - _destroyEventTask(); + _destroyEventTask(); // when IDF uart driver is deleted, _eventTask must finish too + _uart = NULL; } void HardwareSerial::setDebugOutput(bool en) diff --git a/cores/esp32/esp32-hal-i2c.c b/cores/esp32/esp32-hal-i2c.c index 4fe09ff3759..c2e3892febf 100644 --- a/cores/esp32/esp32-hal-i2c.c +++ b/cores/esp32/esp32-hal-i2c.c @@ -30,6 +30,13 @@ #include "driver/i2c.h" #include "esp32-hal-periman.h" +#if SOC_I2C_SUPPORT_APB || SOC_I2C_SUPPORT_XTAL +#include "esp_private/esp_clk.h" +#endif +#if SOC_I2C_SUPPORT_RTC +#include "clk_ctrl_os.h" +#endif + typedef volatile struct { bool initialized; uint32_t frequency; @@ -303,11 +310,6 @@ esp_err_t i2cSetClock(uint8_t i2c_num, uint32_t frequency){ } else if(frequency > 1000000UL){ frequency = 1000000UL; } - // Freq limitation when using different clock sources - #define I2C_CLK_LIMIT_REF_TICK (1 * 1000 * 1000 / 20) /*!< Limited by REF_TICK, no more than REF_TICK/20*/ - #define I2C_CLK_LIMIT_APB (80 * 1000 * 1000 / 20) /*!< Limited by APB, no more than APB/20*/ - #define I2C_CLK_LIMIT_RTC (20 * 1000 * 1000 / 20) /*!< Limited by RTC, no more than RTC/20*/ - #define I2C_CLK_LIMIT_XTAL (40 * 1000 * 1000 / 20) /*!< Limited by RTC, no more than XTAL/20*/ typedef struct { soc_module_clk_t clk; /*!< I2C source clock */ @@ -332,22 +334,22 @@ esp_err_t i2cSetClock(uint8_t i2c_num, uint32_t frequency){ } i2c_sclk_t; // i2c clock characteristic, The order is the same as i2c_sclk_t. - static i2c_clk_alloc_t i2c_clk_alloc[I2C_SCLK_MAX] = { + i2c_clk_alloc_t i2c_clk_alloc[I2C_SCLK_MAX] = { {0, 0}, #if SOC_I2C_SUPPORT_APB - {SOC_MOD_CLK_APB, I2C_CLK_LIMIT_APB}, /*!< I2C APB clock characteristic*/ + {SOC_MOD_CLK_APB, esp_clk_apb_freq()}, /*!< I2C APB clock characteristic*/ #endif #if SOC_I2C_SUPPORT_XTAL - {SOC_MOD_CLK_XTAL, I2C_CLK_LIMIT_XTAL}, /*!< I2C XTAL characteristic*/ + {SOC_MOD_CLK_XTAL, esp_clk_xtal_freq()}, /*!< I2C XTAL characteristic*/ #endif #if SOC_I2C_SUPPORT_RTC - {SOC_MOD_CLK_RC_FAST, I2C_CLK_LIMIT_RTC}, /*!< I2C 20M RTC characteristic*/ + {SOC_MOD_CLK_RC_FAST, periph_rtc_dig_clk8m_get_freq()}, /*!< I2C 20M RTC characteristic*/ #endif #if SOC_I2C_SUPPORT_REF_TICK - {SOC_MOD_CLK_REF_TICK, I2C_CLK_LIMIT_REF_TICK},/*!< I2C REF_TICK characteristic*/ + {SOC_MOD_CLK_REF_TICK, REF_CLK_FREQ},/*!< I2C REF_TICK characteristic*/ #endif }; - + i2c_sclk_t src_clk = I2C_SCLK_DEFAULT; ret = ESP_OK; for (i2c_sclk_t clk = I2C_SCLK_DEFAULT + 1; clk < I2C_SCLK_MAX; clk++) { @@ -367,6 +369,11 @@ esp_err_t i2cSetClock(uint8_t i2c_num, uint32_t frequency){ } else { i2c_hal_context_t hal; hal.dev = I2C_LL_GET_HW(i2c_num); +#if SOC_I2C_SUPPORT_RTC + if(src_clk == I2C_SCLK_RTC){ + periph_rtc_dig_clk8m_enable(); + } +#endif i2c_hal_set_bus_timing(&(hal), frequency, i2c_clk_alloc[src_clk].clk, i2c_clk_alloc[src_clk].clk_freq); bus[i2c_num].frequency = frequency; //Clock Stretching Timeout: 20b:esp32, 5b:esp32-c3, 24b:esp32-s2 diff --git a/cores/esp32/esp32-hal-uart.c b/cores/esp32/esp32-hal-uart.c index 243eb16a454..0fc3a430612 100644 --- a/cores/esp32/esp32-hal-uart.c +++ b/cores/esp32/esp32-hal-uart.c @@ -884,12 +884,18 @@ void uartStartDetectBaudrate(uart_t *uart) { return; } -#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 +// Baud rate detection only works for ESP32 and ESP32S2 +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 + uart_dev_t *hw = UART_LL_GET_HW(uart->num); + hw->auto_baud.glitch_filt = 0x08; + hw->auto_baud.en = 0; + hw->auto_baud.en = 1; +#else // ESP32-C3 requires further testing // Baud rate detection returns wrong values - log_e("ESP32-C3 baud rate detection is not supported."); + log_e("baud rate detection for this SoC is not supported."); return; // Code bellow for C3 kept for future recall @@ -897,19 +903,10 @@ void uartStartDetectBaudrate(uart_t *uart) { //hw->rx_filt.glitch_filt_en = 1; //hw->conf0.autobaud_en = 0; //hw->conf0.autobaud_en = 1; -#elif CONFIG_IDF_TARGET_ESP32S3 - log_e("ESP32-S3 baud rate detection is not supported."); - return; -#else - uart_dev_t *hw = UART_LL_GET_HW(uart->num); - hw->auto_baud.glitch_filt = 0x08; - hw->auto_baud.en = 0; - hw->auto_baud.en = 1; #endif } -unsigned long -uartDetectBaudrate(uart_t *uart) +unsigned long uartDetectBaudrate(uart_t *uart) { if(uart == NULL) { return 0; @@ -955,11 +952,7 @@ uartDetectBaudrate(uart_t *uart) return default_rates[i]; #else -#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 - log_e("ESP32-C3 baud rate detection is not supported."); -#else - log_e("ESP32-S3 baud rate detection is not supported."); -#endif + log_e("baud rate detection this SoC is not supported."); return 0; #endif } diff --git a/docs/source/api/ble.rst b/docs/source/api/ble.rst index 12a8e1280b7..05e765f8feb 100644 --- a/docs/source/api/ble.rst +++ b/docs/source/api/ble.rst @@ -15,13 +15,13 @@ To get started with BLE, you can try: BLE Scan ******** -.. literalinclude:: ../../../libraries/BLE/examples/BLE_scan/BLE_scan.ino +.. literalinclude:: ../../../libraries/BLE/examples/Scan/Scan.ino :language: arduino BLE UART ******** -.. literalinclude:: ../../../libraries/BLE/examples/BLE_uart/BLE_uart.ino +.. literalinclude:: ../../../libraries/BLE/examples/UART/UART.ino :language: arduino Complete list of `BLE examples `_. diff --git a/docs/source/api/ledc.rst b/docs/source/api/ledc.rst index 330ad07cff7..b69f581f5bf 100644 --- a/docs/source/api/ledc.rst +++ b/docs/source/api/ledc.rst @@ -17,6 +17,7 @@ ESP32-S2 8 ESP32-S3 8 ESP32-C3 6 ESP32-C6 6 +ESP32-H2 6 ========= ======================= Arduino-ESP32 LEDC API @@ -125,10 +126,13 @@ This function is used to detach the pin from LEDC. .. code-block:: arduino - void ledcDetach(uint8_t pin); + bool ledcDetach(uint8_t pin); * ``pin`` select LEDC pin. +This function returns ``true`` if detaching was successful. +If ``false`` is returned, an error occurred and the pin was not detached. + ledcChangeFrequency ******************* diff --git a/docs/source/api/sigmadelta.rst b/docs/source/api/sigmadelta.rst index 537c5894d6c..4505c553cf2 100644 --- a/docs/source/api/sigmadelta.rst +++ b/docs/source/api/sigmadelta.rst @@ -14,67 +14,61 @@ ESP32 SoC Number of SigmaDelta channels ========= ============================= ESP32 8 ESP32-S2 8 -ESP32-C3 4 ESP32-S3 8 +ESP32-C3 4 +ESP32-C6 4 +ESP32-H2 4 ========= ============================= Arduino-ESP32 SigmaDelta API ---------------------------- -sigmaDeltaSetup -*************** +sigmaDeltaAttach +**************** -This function is used to setup the SigmaDelta channel frequency and resolution. +This function is used to set up the SigmaDelta channel with the selected frequency and attach it to the selected pin. .. code-block:: arduino - uint32_t sigmaDeltaSetup(uint8_t pin, uint8_t channel, uint32_t freq); + bool sigmaDeltaAttach(uint8_t pin, uint32_t freq); * ``pin`` select GPIO pin. -* ``channel`` select SigmaDelta channel. * ``freq`` select frequency. * range is 1-14 bits (1-20 bits for ESP32). -This function will return ``frequency`` configured for the SigmaDelta channel. -If ``0`` is returned, error occurs and the SigmaDelta channel was not configured. +This function returns ``true`` if the configuration was successful. +If ``false`` is returned, an error occurred and the SigmaDelta channel was not configured. sigmaDeltaWrite *************** -This function is used to set duty for the SigmaDelta channel. - -.. code-block:: arduino - - void sigmaDeltaWrite(uint8_t channel, uint8_t duty); - -* ``channel`` select SigmaDelta channel. -* ``duty`` select duty to be set for selected channel. - -sigmaDeltaRead -************** - -This function is used to get configured duty for the SigmaDelta channel. +This function is used to set duty for the SigmaDelta pin. .. code-block:: arduino - uint8_t sigmaDeltaRead(uint8_t channel) + bool sigmaDeltaWrite(uint8_t pin, uint8_t duty); -* ``channnel`` select SigmaDelta channel. +* ``pin`` selects the GPIO pin. +* ``duty`` selects the duty to be set for selected pin. -This function will return ``duty`` configured for the selected SigmaDelta channel. +This function returns ``true`` if setting the duty was successful. +If ``false`` is returned, error occurs and duty was not set. -sigmaDeltaDetachPin -******************* +sigmaDeltaDetach +**************** -This function is used to detach pin from SigmaDelta. +This function is used to detach a pin from SigmaDelta and deinitialize the channel that was attached to the pin. .. code-block:: arduino - void sigmaDeltaDetachPin(uint8_t pin); + bool sigmaDeltaDetach(uint8_t pin); * ``pin`` select GPIO pin. +This function returns ``true`` if detaching was successful. +If ``false`` is returned, an error occurred and pin was not detached. + Example Applications ******************** diff --git a/docs/source/api/timer.rst b/docs/source/api/timer.rst index f20aa624740..07acd4d502a 100644 --- a/docs/source/api/timer.rst +++ b/docs/source/api/timer.rst @@ -14,8 +14,10 @@ ESP32 SoC Number of timers ========= ================ ESP32 4 ESP32-S2 4 -ESP32-C3 2 ESP32-S3 4 +ESP32-C3 2 +ESP32-C6 2 +ESP32-H2 2 ========= ================ Arduino-ESP32 Timer API diff --git a/docs/source/index.rst b/docs/source/index.rst index f504dc342e0..4016f36f3d4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,6 +16,7 @@ Here you will find all the relevant information about the project. Guides Tutorials Advanced Utilities + Migration Guides FAQ Troubleshooting Contributing diff --git a/docs/source/migration_guides/2.x_to_3.0.rst b/docs/source/migration_guides/2.x_to_3.0.rst new file mode 100644 index 00000000000..3ccbfba5ffb --- /dev/null +++ b/docs/source/migration_guides/2.x_to_3.0.rst @@ -0,0 +1,196 @@ +######################### +Migration from 2.x to 3.0 +######################### + +Introduction +------------ + +This is a guide to highlight **breaking changes** in the API and to help the migration of projects from versions 2.X (based on ESP-IDF 4.4) to version 3.0 (based on ESP-IDF 5.1) of the Arduino ESP32 core. + +All the examples on the version 3.0.0 were updated to be compatible to the new API. The old examples from the versions below 3.0.0 will be not compatible with the version 3.0.0 or newer releases. + +For more information about all changes and new features, check project `RELEASE NOTES `_. + +ADC +--- + +Removed APIs +************ + +* ``analogSetClockDiv`` +* ``adcAttachPin`` +* ``analogSetVRefPin`` + +BLE +--- + +Changes in APIs +*************** + +* Changed APIs return and parameter type from ``std::string`` to Arduino style ``String``. +* Changed UUID data type from ``uint16_t`` to ``BLEUUID`` class. +* ``BLEScan::start`` and ``BLEScan::getResults`` methods return type changed from ``BLEScanResults`` to ``BLEScanResults*``. + +Hall Sensor +----------- + +Hall sensor is no longer supported. + +Removed APIs +************ + +* ``hallRead`` + +I2S +--- + +The I2S driver has been completely redesigned and refactored to use the new ESP-IDF driver. +For more information about the new API, check :doc:`/api/i2s`. + +LEDC +---- + +The LEDC API has been changed in order to support the Peripheral Manager and make it easier to use, as LEDC channels are now automatically assigned to pins. +For more information about the new API, check :doc:`/api/ledc`. + +Removed APIs +************ + +* ``ledcSetup`` +* ``ledcAttachPin`` + +New APIs +******** + +* ``ledcAttach`` used to set up the LEDC pin (merged ``ledcSetup`` and ``ledcAttachPin`` functions). +* ``timerGetFrequency`` used to get the actual frequency of the timer. +* ``timerAttachInterruptArg`` used to attach the interrupt to a timer using arguments. + +Changes in APIs +*************** + +* ``ledcDetachPin`` renamed to ``ledcDetach``. +* In all functions, input parameter ``channel`` has been changed to ``pin``. + +RMT +--- + +For more information about the new API, check :doc:`/api/rmt`. + +Removed APIs +************ + +* ``_rmtDumpStatus`` +* ``rmtSetTick`` +* ``rmtWriteBlocking`` +* ``rmtEnd`` +* ``rmtBeginReceive`` +* ``rmtReadData`` + +New APIs +******** + +* ``rmtWriteAsync`` +* ``rmtTransmitCompleted`` +* ``rmtSetRxMinThreshold`` + + +Changes in APIs +*************** + +* In all functions, input parameter ``rmt_obj_t* rmt`` has been changed to ``int pin``. +* ``rmtInit`` return parameter changed to bool. +* ``rmtInit`` input parameter ``bool tx_not_rx`` has been changed to ``rmt_ch_dir_t channel_direction``. +* ``rmtInit`` new input parameter ``uint32_t frequency_Hz`` to set frequency of RMT channel (as function ``rmtSetTick`` was removed). +* ``rmtWrite`` now sending data in blocking mode. It only returns after sending all data or through a timeout. For Async mode use the new ``rmtWriteAsync`` function. +* ``rmtWrite`` new input parameter ``uint32_t timeout_ms``. +* ``rmtLoop`` renamed to ``rmtWriteLooping``. +* ``rmtRead`` input parameters changed to ``int pin, rmt_data_t* data, size_t *num_rmt_symbols, uint32_t timeout_ms``. +* ``rmtReadAsync`` input parameters changed to ``int pin, rmt_data_t* data, size_t *num_rmt_symbols``. +* ``rmtSetRxThreshold`` renamed to ``rmtSetRxMaxThreshold`` and input parameter ``uint32_t value`` has been changed to ``uint16_t idle_thres_ticks``. +* ``rmtSetCarrier`` input parameters ``uint32_t low, uint32_t high`` have been changed to ``uint32_t frequency_Hz, float duty_percent``. + +SigmaDelta +---------- + +SigmaDelta has been refactored to use the new ESP-IDF driver. +For more information about the new API, check :doc:`/api/sigmadelta`. + +Removed APIs +************ + +* ``sigmaDeltaSetup`` +* ``sigmaDeltaRead`` + +New APIs +******** + +* ``sigmaDeltaAttach`` used to set up the SigmaDelta pin (channel is acquired automatically). +* ``timerGetFrequency`` used to get the actual frequency of the timer. +* ``timerAttachInterruptArg`` used to attach the interrupt to a timer using arguments. + +Changes in APIs +*************** + +* ``sigmaDeltaDetachPin`` renamed to ``sigmaDeltaDetach``. +* ``sigmaDeltaWrite`` input parameter ``channel`` has been changed to ``pin``. + +Timer +----- + +Timer has been refactored to use the new ESP-IDF driver and its API got simplified. For more information about the new API check :doc:`/api/timer`. + +Removed APIs +************ + +* ``timerGetConfig`` +* ``timerSetConfig`` +* ``timerSetDivider`` +* ``timerSetCountUp`` +* ``timerSetAutoReload`` +* ``timerGetDivider`` +* ``timerGetCountUp`` +* ``timerGetAutoReload`` +* ``timerAlarmEnable`` +* ``timerAlarmDisable`` +* ``timerAlarmWrite`` +* ``timerAlarmEnabled`` +* ``timerAlarmRead`` +* ``timerAlarmReadMicros`` +* ``timerAlarmReadSeconds`` +* ``timerAttachInterruptFlag`` + +New APIs +******** + +* ``timerAlarm`` used to set up Alarm for the timer and enable it automatically (merged ``timerAlarmWrite`` and ``timerAlarmEnable`` functions). +* ``timerGetFrequency`` used to get the actual frequency of the timer. +* ``timerAttachInterruptArg`` used to attach the interrupt to a timer using arguments. + +Changes in APIs +*************** + +* ``timerBegin`` has now only 1 parameter (frequency). There is an automatic calculation of the divider using different clock sources + to achieve the selected frequency. +* ``timerAttachInterrupt`` has now only 2 parameters. The ``edge`` parameter has been removed. + +UART (HardwareSerial) +--------------------- + +Changes in APIs +*************** + +* ``setHwFlowCtrlMode`` input parameter ``uint8_t mode`` has been changed to ``SerialHwFlowCtrl mode``. +* ``setMode`` input parameter ``uint8_t mode`` has been changed to ``SerialMode mode``. + +Functional changes +****************** + +* It is now possible to detach UART0 pins by calling ``end()`` with no previous call of ``begin()``. +* It is now possible to call ``setPins()`` before ``begin()`` or in any order. +* ``setPins(``) will detach any previous pins that have been changed. +* ``begin(baud, rx, tx)`` will detach any previous attached pins. +* ``setPins()`` or ``begin(baud, rx, tx)`` when called at first, will detach console RX0/TX0, attached in boot. +* Any pin set as -1 in ``begin()`` or ``setPins()`` won't be changed nor detached. +* ``begin(baud)`` will not change any pins that have been set before this call, through a previous ``begin(baud, rx, tx)`` or ``setPin()``. +* If the application only uses RX or TX, ``begin(baud, -1, tx)`` or ``begin(baud, rx)`` will change only the assigned pin and keep the other unchanged. diff --git a/docs/source/migration_guides/migration_guides.rst b/docs/source/migration_guides/migration_guides.rst new file mode 100644 index 00000000000..e1254c19ba5 --- /dev/null +++ b/docs/source/migration_guides/migration_guides.rst @@ -0,0 +1,10 @@ +################ +Migration Guides +################ + +.. toctree:: + :caption: Migration Guides: + :maxdepth: 1 + :glob: + + * diff --git a/libraries/BLE/examples/Beacon_Scanner/Beacon_Scanner.ino b/libraries/BLE/examples/Beacon_Scanner/Beacon_Scanner.ino index ce36dd7ac4d..71864fb845c 100644 --- a/libraries/BLE/examples/Beacon_Scanner/Beacon_Scanner.ino +++ b/libraries/BLE/examples/Beacon_Scanner/Beacon_Scanner.ino @@ -111,9 +111,9 @@ void setup() void loop() { // put your main code here, to run repeatedly: - BLEScanResults foundDevices = pBLEScan->start(scanTime, false); + BLEScanResults *foundDevices = pBLEScan->start(scanTime, false); Serial.print("Devices found: "); - Serial.println(foundDevices.getCount()); + Serial.println(foundDevices->getCount()); Serial.println("Scan done!"); pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory delay(2000); diff --git a/libraries/BLE/examples/Scan/Scan.ino b/libraries/BLE/examples/Scan/Scan.ino index 094f7933ac7..fee31b76da9 100644 --- a/libraries/BLE/examples/Scan/Scan.ino +++ b/libraries/BLE/examples/Scan/Scan.ino @@ -31,9 +31,9 @@ void setup() { void loop() { // put your main code here, to run repeatedly: - BLEScanResults foundDevices = pBLEScan->start(scanTime, false); + BLEScanResults *foundDevices = pBLEScan->start(scanTime, false); Serial.print("Devices found: "); - Serial.println(foundDevices.getCount()); + Serial.println(foundDevices->getCount()); Serial.println("Scan done!"); pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory delay(2000); diff --git a/libraries/BLE/src/BLEScan.cpp b/libraries/BLE/src/BLEScan.cpp index 87d0deb2e47..5162d0b1852 100644 --- a/libraries/BLE/src/BLEScan.cpp +++ b/libraries/BLE/src/BLEScan.cpp @@ -426,11 +426,11 @@ bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), b * @param [in] duration The duration in seconds for which to scan. * @return The BLEScanResults. */ -BLEScanResults BLEScan::start(uint32_t duration, bool is_continue) { +BLEScanResults* BLEScan::start(uint32_t duration, bool is_continue) { if(start(duration, nullptr, is_continue)) { m_semaphoreScanEnd.wait("start"); // Wait for the semaphore to release. } - return m_scanResults; + return &m_scanResults; } // start @@ -500,8 +500,8 @@ BLEAdvertisedDevice BLEScanResults::getDevice(uint32_t i) { return dev; } -BLEScanResults BLEScan::getResults() { - return m_scanResults; +BLEScanResults* BLEScan::getResults() { + return &m_scanResults; } void BLEScan::clearResults() { diff --git a/libraries/BLE/src/BLEScan.h b/libraries/BLE/src/BLEScan.h index 1e35586f7be..4a00cafff4c 100644 --- a/libraries/BLE/src/BLEScan.h +++ b/libraries/BLE/src/BLEScan.h @@ -71,10 +71,10 @@ class BLEScan { void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), bool is_continue = false); - BLEScanResults start(uint32_t duration, bool is_continue = false); + BLEScanResults* start(uint32_t duration, bool is_continue = false); void stop(); void erase(BLEAddress address); - BLEScanResults getResults(); + BLEScanResults* getResults(); void clearResults(); #ifdef SOC_BLE_50_SUPPORTED diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s3 b/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino b/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino deleted file mode 100644 index a1f5c3088c3..00000000000 --- a/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino +++ /dev/null @@ -1,136 +0,0 @@ -/* - This example demonstrates I2S ADC capability to sample high frequency analog signals. - The PWM signal generated with ledc is only for ease of use when first trying out. - To sample the generated signal connect default pins 27(PWM) and 32(Sampling) together. - If you do not wish to generate PWM simply comment out the definition of constant GENERATE_PWM - Try to change the PWM_DUTY_PERCENT and see how to averaged value changes. - - The maximum for I2S ADC sampling frequency is 5MHz (value 5000000), however there will be many values repeated because the real - sampling frequency is much lower - - - By default this example will print values compatible with Arduino plotter - 1. signal - all values - 2. signal - averaged value - - You can change the number of sample over which is the signal averaged by changing value of AVERAGE_EVERY_N_SAMPLES - If you comment the definition altogether the averaging will not be performed nor printed. - - If you do not wish to print every value, simply comment definition of constant PRINT_ALL_VALUES - - Note: ESP prints messages at startup which will pollute Arduino IDE Serial plotter legend. - To avoid this pollution, start the plotter after startup (op restart) -*/ -#include - -// I2S -#define I2S_SAMPLE_RATE (277777) // Max sampling frequency = 277.777 kHz -#define ADC_INPUT (ADC1_CHANNEL_4) //pin 32 -#define I2S_DMA_BUF_LEN (1024) - -// PWM -#define GENERATE_PWM -#define OUTPUT_PIN (27) -#define PWM_FREQUENCY ((I2S_SAMPLE_RATE)/4) -#define PWM_DUTY_PERCENT (50) -#define PWM_RESOLUTION_BITS (2) // Lower bit resolution enables higher frequency -#define PWM_DUTY_VALUE ((((1<<(PWM_RESOLUTION_BITS)))*(PWM_DUTY_PERCENT))/100) // Duty value used for setup function based on resolution - -// Sample post processing -#define PRINT_ALL_VALUES -#define AVERAGE_EVERY_N_SAMPLES (100) - -void i2sInit(){ - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), - .sample_rate = I2S_SAMPLE_RATE, // The format of the signal using ADC_BUILT_IN - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, - .dma_buf_count = 8, - .dma_buf_len = I2S_DMA_BUF_LEN, - .use_apll = false, - .tx_desc_auto_clear = false, - .fixed_mclk = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_128, - .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT - }; - Serial.printf("Attempting to setup I2S ADC with sampling frequency %d Hz\n", I2S_SAMPLE_RATE); - if(ESP_OK != i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)){ - Serial.printf("Error installing I2S. Halt!"); - while(1); - } - if(ESP_OK != i2s_set_adc_mode(ADC_UNIT_1, ADC_INPUT)){ - Serial.printf("Error setting up ADC. Halt!"); - while(1); - } - if(ESP_OK != adc1_config_channel_atten(ADC_INPUT, ADC_ATTEN_DB_11)){ - Serial.printf("Error setting up ADC attenuation. Halt!"); - while(1); - } - - if(ESP_OK != i2s_adc_enable(I2S_NUM_0)){ - Serial.printf("Error enabling ADC. Halt!"); - while(1); - } - Serial.printf("I2S ADC setup ok\n"); -} - -void setup() { - Serial.begin(115200); - -#ifdef GENERATE_PWM - // PWM setup - Serial.printf("Setting up PWM: frequency = %d; resolution bits %d; Duty cycle = %d; duty value = %d, Output pin = %d\n", PWM_FREQUENCY, PWM_RESOLUTION_BITS, PWM_DUTY_PERCENT, PWM_DUTY_VALUE, OUTPUT_PIN); - ledcAttach(OUTPUT_PIN, PWM_FREQUENCY, PWM_RESOLUTION_BITS); - uint32_t freq = ledcReadFreq(OUTPUT_PIN); - - if(freq != PWM_FREQUENCY){ - Serial.printf("Error setting up PWM. Halt!"); - while(1); - } - ledcWrite(OUTPUT_PIN, PWM_DUTY_VALUE); - Serial.printf("PWM setup ok\n"); -#endif - - // Initialize the I2S peripheral - i2sInit(); -} - -void loop(){ -// The 4 high bits are the channel, and the data is inverted - size_t bytes_read; - uint16_t buffer[I2S_DMA_BUF_LEN] = {0}; - -#ifdef AVERAGE_EVERY_N_SAMPLES - uint32_t read_counter = 0; - uint32_t averaged_reading = 0; - uint64_t read_sum = 0; -#endif - - while(1){ - i2s_read(I2S_NUM_0, &buffer, sizeof(buffer), &bytes_read, 15); - //Serial.printf("read %d Bytes\n", bytes_read); - - for(int i = 0; i < bytes_read/2; ++i){ -#ifdef PRINT_ALL_VALUES - //Serial.printf("[%d] = %d\n", i, buffer[i] & 0x0FFF); // Print with indexes - Serial.printf("Signal:%d ", buffer[i] & 0x0FFF); // Print compatible with Arduino Plotter -#endif -#ifdef AVERAGE_EVERY_N_SAMPLES - read_sum += buffer[i] & 0x0FFF; - ++read_counter; - if(read_counter == AVERAGE_EVERY_N_SAMPLES){ - averaged_reading = read_sum / AVERAGE_EVERY_N_SAMPLES; - //Serial.printf("averaged_reading = %d over %d samples\n", averaged_reading, read_counter); // Print with additional info - Serial.printf("Averaged_signal:%ld", averaged_reading); // Print compatible with Arduino Plotter - read_counter = 0; - read_sum = 0; - } -#endif -#if defined(PRINT_ALL_VALUES) || defined (AVERAGE_EVERY_N_SAMPLES) - Serial.printf("\n"); -#endif - } // for - } // while -} diff --git a/libraries/ESP32/examples/Serial/BaudRateDetect_Demo/BaudRateDetect_Demo.ino b/libraries/ESP32/examples/Serial/BaudRateDetect_Demo/BaudRateDetect_Demo.ino new file mode 100644 index 00000000000..5c63960487e --- /dev/null +++ b/libraries/ESP32/examples/Serial/BaudRateDetect_Demo/BaudRateDetect_Demo.ino @@ -0,0 +1,37 @@ +/* + This Sketch demonstrates how to detect and set the baud rate when the UART0 is connected to + some port that is sending data. It can be used with the Arduino IDE Serial Monitor to send the data. + + Serial.begin(0) will start the baud rate detection. Valid range is 300 to 230400 baud. + It will try to detect for 20 seconds, by default, while reading RX. + This timeout of 20 seconds can be changed in the begin() function through <> parameter: + + void HardwareSerial::begin(baud, config, rxPin, txPin, invert, <>, rxfifo_full_thrhd) + + It is necessary that the other end sends some data within <>, otherwise the detection won't work. + + IMPORTANT NOTE: baud rate detection seem to only work with ESP32 and ESP32-S2. + In other other SoCs, it doesn't work. + +*/ + +// Open the Serial Monitor with testing baud start typing and sending caracters +void setup() { + Serial.begin(0); // it will try to detect the baud rate for 20 seconds + + Serial.print("\n==>The baud rate is "); + Serial.println(Serial.baudRate()); + + //after 20 seconds timeout, when not detected, it will return zero - in this case, we set it back to 115200. + if (Serial.baudRate() == 0) { + // Trying to set Serial to a safe state at 115200 + Serial.end(); + Serial.begin(115200); + Serial.setDebugOutput(true); + delay(1000); + log_e("Baud rate detection failed."); + } + } + +void loop() { +} diff --git a/libraries/ESP_I2S/keywords.txt b/libraries/ESP_I2S/keywords.txt new file mode 100644 index 00000000000..f30b7ad1d9a --- /dev/null +++ b/libraries/ESP_I2S/keywords.txt @@ -0,0 +1,21 @@ +####################################### +# Syntax Coloring Map For ESP_I2S +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP_I2S KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +onEvent KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +SR_EVENT_WAKEWORD LITERAL1 diff --git a/libraries/ESP_I2S/library.properties b/libraries/ESP_I2S/library.properties new file mode 100755 index 00000000000..d4e3e409071 --- /dev/null +++ b/libraries/ESP_I2S/library.properties @@ -0,0 +1,9 @@ +name=ESP_I2S +version=1.0.0 +author=me-no-dev +maintainer=me-no-dev +sentence=Library for ESP I2S communication +paragraph=Supports ESP32 Arduino platforms. +category=Sound +url=https://github.com/espressif/arduino-esp32/ +architectures=esp32 \ No newline at end of file diff --git a/libraries/ESP_I2S/src/ESP_I2S.cpp b/libraries/ESP_I2S/src/ESP_I2S.cpp new file mode 100644 index 00000000000..112c85aaccf --- /dev/null +++ b/libraries/ESP_I2S/src/ESP_I2S.cpp @@ -0,0 +1,807 @@ +#include "ESP_I2S.h" + +#if SOC_I2S_SUPPORTED + +#include "esp32-hal-periman.h" +#include "wav_header.h" +#include "mp3dec.h" + +#define I2S_READ_CHUNK_SIZE 1920 + +#define I2S_DEFAULT_CFG() { \ + .id = I2S_NUM_AUTO, \ + .role = I2S_ROLE_MASTER, \ + .dma_desc_num = 6, \ + .dma_frame_num = 240, \ + .auto_clear = true, \ +} + +#define I2S_STD_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .mclk = (gpio_num_t)_mclk, \ + .bclk = (gpio_num_t)_bclk, \ + .ws = (gpio_num_t)_ws, \ + .dout = (gpio_num_t)_dout, \ + .din = (gpio_num_t)_din, \ + .invert_flags = { \ + .mclk_inv = _mclk_inv, \ + .bclk_inv = _bclk_inv, \ + .ws_inv = _ws_inv, \ + }, \ + }, \ + } + +#if SOC_I2S_SUPPORTS_TDM +#define I2S_TDM_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode, _mask) \ + { \ + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_TDM_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode, _mask), \ + .gpio_cfg = { \ + .mclk = (gpio_num_t)_mclk, \ + .bclk = (gpio_num_t)_bclk, \ + .ws = (gpio_num_t)_ws, \ + .dout = (gpio_num_t)_dout, \ + .din = (gpio_num_t)_din, \ + .invert_flags = { \ + .mclk_inv = _mclk_inv, \ + .bclk_inv = _bclk_inv, \ + .ws_inv = _ws_inv, \ + }, \ + }, \ + } +#endif +#if SOC_I2S_SUPPORTS_PDM_TX +#if (SOC_I2S_PDM_MAX_TX_LINES > 1) + #define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_tx_clk, \ + .dout = (gpio_num_t)_tx_dout0, \ + .dout2 = (gpio_num_t)_tx_dout1, \ + .invert_flags = { \ + .clk_inv = _tx_clk_inv, \ + }, \ + }, \ + } +#else + #define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_tx_clk, \ + .dout = (gpio_num_t)_tx_dout0, \ + .invert_flags = { \ + .clk_inv = _tx_clk_inv, \ + }, \ + }, \ + } +#endif +#endif + +#if SOC_I2S_SUPPORTS_PDM_RX +#if (SOC_I2S_PDM_MAX_RX_LINES > 1) + #define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_rx_clk, \ + .dins = { \ + (gpio_num_t)_rx_din0, \ + (gpio_num_t)_rx_din1, \ + (gpio_num_t)_rx_din2, \ + (gpio_num_t)_rx_din3, \ + }, \ + .invert_flags = { \ + .clk_inv = _rx_clk_inv, \ + }, \ + }, \ + } +#else + #define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ + { \ + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ + .gpio_cfg = { \ + .clk = (gpio_num_t)_rx_clk, \ + .din = (gpio_num_t)_rx_din0, \ + .invert_flags = { \ + .clk_inv = _rx_clk_inv, \ + }, \ + }, \ + } +#endif +#endif + +#define I2S_ERROR_CHECK_RETURN(x,r) do { last_error = (x); if(unlikely(last_error != ESP_OK)){ log_e("ERROR: %s", esp_err_to_name(last_error)); return (r); } } while(0) +#define I2S_ERROR_CHECK_RETURN_FALSE(x) I2S_ERROR_CHECK_RETURN(x,false) + +// Default read, no resmpling and temp buffer necessary +static esp_err_t i2s_channel_read_default(i2s_chan_handle_t handle, char * tmp_buf, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + return i2s_channel_read(handle, (char *)dst, len, bytes_read, timeout_ms); +} + +// Resample the 32bit SPH0645 microphone data into 16bit. SPH0645 is actually 18 bit, but this trick helps save some space +static esp_err_t i2s_channel_read_32_to_16(i2s_chan_handle_t handle, char * read_buff, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + size_t out_len = 0; + size_t read_buff_len = len * 2; + if(read_buff == NULL){ + log_e("Temp buffer is NULL!"); + return ESP_FAIL; + } + esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms); + if (err != ESP_OK){ + *bytes_read = 0; + return err; + } + out_len /= 4; + uint16_t * ds = (uint16_t*)dst; + uint32_t * src = (uint32_t*)read_buff; + for (size_t i = 0; i < out_len; i++){ + ds[i] = src[i] >> 16; + } + *bytes_read = out_len * 2; + return ESP_OK; +} + +// Resample the 16bit stereo microphone data into 16bit mono. +static esp_err_t i2s_channel_read_16_stereo_to_mono(i2s_chan_handle_t handle, char * read_buff, void * dst, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + size_t out_len = 0; + size_t read_buff_len = len * 2; + if(read_buff == NULL){ + log_e("Temp buffer is NULL!"); + return ESP_FAIL; + } + esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms); + if (err != ESP_OK){ + *bytes_read = 0; + return err; + } + out_len /= 2; + uint16_t * ds = (uint16_t*)dst; + uint16_t * src = (uint16_t*)read_buff; + for (size_t i = 0; i < out_len; i+=2){ + *ds++ = src[i]; + } + *bytes_read = out_len; + return ESP_OK; +} + +I2SClass::I2SClass(){ + last_error = ESP_OK; + + tx_chan = NULL; + tx_sample_rate = 0; + tx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; + tx_slot_mode = I2S_SLOT_MODE_STEREO; + + rx_fn = i2s_channel_read_default; + rx_transform = I2S_RX_TRANSFORM_NONE; + rx_transform_buf = NULL; + rx_transform_buf_len = 0; + + rx_chan = NULL; + rx_sample_rate = 0; + rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; + rx_slot_mode = I2S_SLOT_MODE_STEREO; + + _mclk = -1; + _bclk = -1; + _ws = -1; + _dout = -1; + _din = -1; + _mclk_inv = false; + _bclk_inv = false; + _ws_inv = false; + +#if SOC_I2S_SUPPORTS_PDM_TX + _tx_clk = -1; + _tx_dout0 = -1; + _tx_dout1 = -1; + _tx_clk_inv = false; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + _rx_clk = -1; + _rx_din0 = -1; + _rx_din1 = -1; + _rx_din2 = -1; + _rx_din3 = -1; + _rx_clk_inv = false; +#endif +} + +I2SClass::~I2SClass(){ + end(); +} + +bool I2SClass::i2sDetachBus(void * bus_pointer){ + I2SClass *bus = (I2SClass *) bus_pointer; + if(bus->tx_chan != NULL || bus->tx_chan != NULL){ + bus->end(); + } + return true; +} + +// Set pins for STD and TDM mode +void I2SClass::setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din, int8_t mclk){ + _mclk = mclk; + _bclk = bclk; + _ws = ws; + _dout = dout; + _din = din; +} + +void I2SClass::setInverted(bool bclk, bool ws, bool mclk){ + _mclk_inv = mclk; + _bclk_inv = bclk; + _ws_inv = ws; +} + +// Set pins for PDM TX mode +#if SOC_I2S_SUPPORTS_PDM_TX +void I2SClass::setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1){ + _tx_clk = clk; + _tx_dout0 = dout0; +#if (SOC_I2S_PDM_MAX_TX_LINES > 1) + _tx_dout1 = dout1; +#endif +} +#endif + +// Set pins for PDM RX mode +#if SOC_I2S_SUPPORTS_PDM_RX +void I2SClass::setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1, int8_t din2, int8_t din3){ + _rx_clk = clk; + _rx_din0 = din0; +#if (SOC_I2S_PDM_MAX_RX_LINES > 1) + _rx_din1 = din1; + _rx_din2 = din2; + _rx_din3 = din3; +#endif +} +#endif + +#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX +void I2SClass::setInvertedPdm(bool clk){ +#if SOC_I2S_SUPPORTS_PDM_TX + _tx_clk_inv = clk; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + _rx_clk_inv = clk; +#endif +} +#endif + +bool I2SClass::initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + // Peripheral manager deinit previous peripheral if pin was used + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + if (_dout >= 0 && _din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); + } else if (_dout >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); + } else if (_din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); + } + + i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); + if (tx_chan != NULL) { + tx_sample_rate = rate; + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(tx_chan, &i2s_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + } + if (rx_chan != NULL) { + rx_sample_rate = rate; + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(rx_chan, &i2s_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_STD, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_STD"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} + +#if SOC_I2S_SUPPORTS_TDM +bool I2SClass::initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask){ + // Peripheral manager deinit previous peripheral if pin was used + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + if (_dout >= 0 && _din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); + } else if (_dout >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); + } else if (_din >= 0) { + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); + } + + i2s_tdm_config_t i2s_tdm_config = I2S_TDM_CHAN_CFG(rate, bits_cfg, ch, (i2s_tdm_slot_mask_t)slot_mask); + if (tx_chan != NULL) { + tx_sample_rate = rate; + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(tx_chan, &i2s_tdm_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + } + if (rx_chan != NULL) { + rx_sample_rate = rate; + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(rx_chan, &i2s_tdm_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_mclk >= 0) if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_bclk >= 0) if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_ws >= 0) if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_dout >= 0) if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + if (_din >= 0) if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_TDM, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_TDM"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} +#endif + +#if SOC_I2S_SUPPORTS_PDM_TX +bool I2SClass::initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + // Peripheral manager deinit previous peripheral if pin was used + if (_tx_clk >= 0) if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_tx_dout0 >= 0) if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_tx_dout1 >= 0) if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); + + i2s_pdm_tx_config_t i2s_pdm_tx_config = I2S_PDM_TX_CHAN_CFG(rate, bits_cfg, ch); + if (tx_chan != NULL) { + tx_sample_rate = rate; + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_tx_mode(tx_chan, &i2s_pdm_tx_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_tx_clk >= 0) if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_tx_dout0 >= 0) if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_tx_dout1 >= 0) if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_TDM"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} +#endif + +#if SOC_I2S_SUPPORTS_PDM_RX +bool I2SClass::initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + // Peripheral manager deinit previous peripheral if pin was used + if (_rx_clk >= 0) if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din0 >= 0) if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din1 >= 0) if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din2 >= 0) if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + if (_rx_din3 >= 0) if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_INIT, NULL)){ return false; } + + // I2S configuration + i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); + + i2s_pdm_rx_config_t i2s_pdf_rx_config = I2S_PDM_RX_CHAN_CFG(rate, bits_cfg, ch); + if (rx_chan != NULL) { + rx_sample_rate = rate; + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_rx_mode(rx_chan, &i2s_pdf_rx_config)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + } + + // Peripheral manager set bus type to I2S + if (_rx_clk >= 0) if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din0 >= 0) if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din1 >= 0) if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din2 >= 0) if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + if (_rx_din3 >= 0) if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_I2S_PDM, (void *)(this))){ goto err; } + + return true; +err: + log_e("Failed to set all pins bus to I2S_TDM"); + I2SClass::i2sDetachBus((void *)(this)); + return false; +} +#endif + +bool I2SClass::begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch +#if SOC_I2S_SUPPORTS_TDM + , int8_t slot_mask +#endif +){ + /* Setup I2S peripheral */ + if (mode >= I2S_MODE_MAX){ + log_e("Invalid I2S mode selected."); + return false; + } + _mode = mode; + + bool init = false; + switch (_mode){ + case I2S_MODE_STD: + init = initSTD(rate, bits_cfg, ch); + break; +#if SOC_I2S_SUPPORTS_TDM + case I2S_MODE_TDM: + init = initTDM(rate, bits_cfg, ch, slot_mask); + break; +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + case I2S_MODE_PDM_TX: + init = initPDMtx(rate, bits_cfg, ch); + break; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + case I2S_MODE_PDM_RX: + init = initPDMrx(rate, bits_cfg, ch); + break; +#endif + default: + break; + } + + if (init == false){ + log_e("I2S initialization failed."); + return false; + } + return true; +} + +bool I2SClass::end(){ + if (tx_chan != NULL){ + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(tx_chan)); + tx_chan = NULL; + } + if (rx_chan != NULL){ + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(rx_chan)); + rx_chan = NULL; + } + if(rx_transform_buf != NULL){ + free(rx_transform_buf); + rx_transform_buf = NULL; + rx_transform_buf_len = 0; + } + + //Peripheral manager deinit used pins + switch (_mode){ + case I2S_MODE_STD: +#if SOC_I2S_SUPPORTS_TDM + case I2S_MODE_TDM: +#endif + perimanSetPinBus(_mclk, ESP32_BUS_TYPE_INIT, NULL); + perimanSetPinBus(_bclk, ESP32_BUS_TYPE_INIT, NULL); + perimanSetPinBus(_ws, ESP32_BUS_TYPE_INIT, NULL); + if (_dout >= 0) perimanSetPinBus(_dout, ESP32_BUS_TYPE_INIT, NULL); + if (_din >= 0) perimanSetPinBus(_din, ESP32_BUS_TYPE_INIT, NULL); + break; +#if SOC_I2S_SUPPORTS_PDM_TX + case I2S_MODE_PDM_TX: + perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_INIT, NULL); + if (_tx_dout0 >= 0) perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_INIT, NULL); + if (_tx_dout1 >= 0) perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_INIT, NULL); + break; +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + case I2S_MODE_PDM_RX: + perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din0 >= 0) perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din1 >= 0) perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din2 >= 0) perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_INIT, NULL); + if (_rx_din3 >= 0) perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_INIT, NULL); + break; +#endif + default: + break; + } + return true; +} + +bool I2SClass::configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch){ + /* Setup I2S channels */ + if (tx_chan != NULL) { + if(tx_sample_rate == rate && tx_data_bit_width == bits_cfg && tx_slot_mode == ch){ + return true; + } + i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(tx_chan, &i2s_config.clk_cfg)); + tx_sample_rate = rate; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(tx_chan, &i2s_config.slot_cfg)); + tx_data_bit_width = bits_cfg; + tx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); + return true; + } + return false; +} + +bool I2SClass::configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform){ + /* Setup I2S channels */ + if (rx_chan != NULL) { + if(rx_sample_rate != rate || rx_data_bit_width != bits_cfg || rx_slot_mode != ch){ + i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan)); + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(rx_chan, &i2s_config.clk_cfg)); + rx_sample_rate = rate; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(rx_chan, &i2s_config.slot_cfg)); + rx_data_bit_width = bits_cfg; + rx_slot_mode = ch; + I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); + return transformRX(transform); + } + if(rx_transform != transform){ + return transformRX(transform); + } + return true; + } + return false; +} + +size_t I2SClass::readBytes(char *buffer, size_t size){ + size_t bytes_read = 0; + size_t total_size = 0; + last_error = ESP_FAIL; + if(rx_chan == NULL){ + return total_size; + } + while (total_size < size) { + bytes_read = size - total_size; + if(rx_transform_buf != NULL && bytes_read > I2S_READ_CHUNK_SIZE){ bytes_read = I2S_READ_CHUNK_SIZE; } + I2S_ERROR_CHECK_RETURN(rx_fn(rx_chan, rx_transform_buf, (char*)(buffer + total_size), bytes_read, &bytes_read, _timeout), 0); + total_size += bytes_read; + } + return total_size; +} + +size_t I2SClass::write(uint8_t *buffer, size_t size){ + size_t written = 0; + size_t bytes_sent = 0; + last_error = ESP_FAIL; + if(tx_chan == NULL){ + return written; + } + while(written < size){ + bytes_sent = size - written; + esp_err_t err = i2s_channel_write(tx_chan, (char*)(buffer + written), bytes_sent, &bytes_sent, _timeout); + setWriteError(err); + I2S_ERROR_CHECK_RETURN(err, written); + written += bytes_sent; + } + return written; +} + +i2s_chan_handle_t I2SClass::txChan(){ return tx_chan; } +uint32_t I2SClass::txSampleRate(){ return tx_sample_rate; } +i2s_data_bit_width_t I2SClass::txDataWidth(){ return tx_data_bit_width; } +i2s_slot_mode_t I2SClass::txSlotMode(){ return tx_slot_mode; } + +i2s_chan_handle_t I2SClass::rxChan(){ return rx_chan; } +uint32_t I2SClass::rxSampleRate(){ return rx_sample_rate; } +i2s_data_bit_width_t I2SClass::rxDataWidth(){ return rx_data_bit_width; } +i2s_slot_mode_t I2SClass::rxSlotMode(){ return rx_slot_mode; } + +int I2SClass::lastError(){ + return (int)last_error; +} + +int I2SClass::available(){ + if(rx_chan == NULL){ + return -1; + } + return I2S_READ_CHUNK_SIZE;// / (rx_data_bit_width/8); +}; + +int I2SClass::peek(){ + return -1; +}; + +int I2SClass::read(){ + int out = 0; + if(readBytes((char *)&out, rx_data_bit_width/8) == (rx_data_bit_width/8)){ + return out; + } + return -1; +}; + +size_t I2SClass::write(uint8_t d){ + return write(&d, 1); +} + +bool I2SClass::transformRX(i2s_rx_transform_t transform){ + switch(transform){ + case I2S_RX_TRANSFORM_NONE: + allocTranformRX(0); + rx_fn = i2s_channel_read_default; + break; + + case I2S_RX_TRANSFORM_32_TO_16: + if(rx_data_bit_width != I2S_DATA_BIT_WIDTH_32BIT){ + log_e("Wrong data width. Should be 32bit"); + return false; + } + if(!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)){ + return false; + } + rx_fn = i2s_channel_read_32_to_16; + rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; + break; + + case I2S_RX_TRANSFORM_16_STEREO_TO_MONO: + if(rx_slot_mode != I2S_SLOT_MODE_STEREO){ + log_e("Wrong slot mode. Should be Stereo"); + return false; + } + if(!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)){ + return false; + } + rx_fn = i2s_channel_read_16_stereo_to_mono; + rx_slot_mode = I2S_SLOT_MODE_MONO; + break; + + default: + log_e("Unknown RX Transform %d", transform); + return false; + } + rx_transform = transform; + return true; +} + +bool I2SClass::allocTranformRX(size_t buf_len){ + char* buf = NULL; + if(buf_len == 0){ + if(rx_transform_buf != NULL){ + free(rx_transform_buf); + rx_transform_buf = NULL; + rx_transform_buf_len = 0; + } + return true; + } + if(rx_transform_buf == NULL || rx_transform_buf_len != buf_len){ + buf = (char*)malloc(buf_len); + if(buf == NULL){ + log_e("malloc %u failed!", buf_len); + return false; + } + if(rx_transform_buf != NULL){ + free(rx_transform_buf); + } + rx_transform_buf = buf; + rx_transform_buf_len = buf_len; + } + return true; +} + +const int WAVE_HEADER_SIZE = PCM_WAV_HEADER_SIZE; + +//Record PCM WAV with current RX settings +uint8_t * I2SClass::recordWAV(size_t rec_seconds, size_t * out_size){ + uint32_t sample_rate = rxSampleRate(); + uint16_t sample_width = (uint16_t)rxDataWidth(); + uint16_t num_channels = (uint16_t)rxSlotMode(); + size_t rec_size = rec_seconds * ((sample_rate * (sample_width / 8)) * num_channels); + const pcm_wav_header_t wav_header = PCM_WAV_HEADER_DEFAULT(rec_size, sample_width, sample_rate, num_channels); + *out_size = 0; + + log_d("Record WAV: rate:%lu, bits:%u, channels:%u, size:%lu", sample_rate, sample_width, num_channels, rec_size); + + uint8_t * wav_buf = (uint8_t*)malloc(rec_size + WAVE_HEADER_SIZE); + if(wav_buf == NULL){ + log_e("Failed to allocate WAV buffer with size %u", rec_size + WAVE_HEADER_SIZE); + return NULL; + } + memcpy(wav_buf, &wav_header, WAVE_HEADER_SIZE); + size_t wav_size = readBytes((char*)(wav_buf + WAVE_HEADER_SIZE), rec_size); + if(wav_size < rec_size){ + log_e("Recorded %u bytes from %u", wav_size, rec_size); + } else if(lastError()){ + log_e("Read Failed! %d", lastError()); + } else { + *out_size = rec_size + WAVE_HEADER_SIZE; + return wav_buf; + } + free(wav_buf); + return NULL; +} + +void I2SClass::playWAV(uint8_t * data, size_t len){ + pcm_wav_header_t * header = (pcm_wav_header_t*)data; + if(header->fmt_chunk.audio_format != 1){ + log_e("Audio format is not PCM!"); + return; + } + wav_data_chunk_t * data_chunk = &header->data_chunk; + size_t data_offset = 0; + while(memcmp(data_chunk->subchunk_id, "data", 4) != 0){ + log_d("Skip chunk: %c%c%c%c, len: %lu", data_chunk->subchunk_id[0], data_chunk->subchunk_id[1], data_chunk->subchunk_id[2], data_chunk->subchunk_id[3], data_chunk->subchunk_size + 8); + data_offset += data_chunk->subchunk_size + 8; + data_chunk = (wav_data_chunk_t *)(data + WAVE_HEADER_SIZE + data_offset - 8); + } + log_d("Play WAV: rate:%lu, bits:%d, channels:%d, size:%lu", header->fmt_chunk.sample_rate, header->fmt_chunk.bits_per_sample, header->fmt_chunk.num_of_channels, data_chunk->subchunk_size); + configureTX(header->fmt_chunk.sample_rate, (i2s_data_bit_width_t)header->fmt_chunk.bits_per_sample, (i2s_slot_mode_t)header->fmt_chunk.num_of_channels); + write(data + WAVE_HEADER_SIZE + data_offset, data_chunk->subchunk_size); +} + +bool I2SClass::playMP3(uint8_t *src, size_t src_len) +{ + int16_t outBuf[MAX_NCHAN * MAX_NGRAN * MAX_NSAMP]; + uint8_t *readPtr=NULL; + int bytesAvailable=0, err=0, offset=0; + MP3FrameInfo frameInfo; + HMP3Decoder decoder=NULL; + + bytesAvailable = src_len; + readPtr = src; + + decoder = MP3InitDecoder(); + if (decoder == NULL){ + log_e("Could not allocate decoder"); + return false; + } + + do { + offset = MP3FindSyncWord(readPtr, bytesAvailable); + if (offset < 0) { + break; + } + readPtr += offset; + bytesAvailable -= offset; + err = MP3Decode(decoder, &readPtr, &bytesAvailable, outBuf, 0); + if (err) { + log_e("Decode ERROR: %d", err); + MP3FreeDecoder(decoder); + return false; + } else { + MP3GetLastFrameInfo(decoder, &frameInfo); + configureTX(frameInfo.samprate, (i2s_data_bit_width_t)frameInfo.bitsPerSample, (i2s_slot_mode_t)frameInfo.nChans); + write((uint8_t*)outBuf, (size_t)((frameInfo.bitsPerSample / 8) * frameInfo.outputSamps)); + } + } while (true); + MP3FreeDecoder(decoder); + return true; +} + +#endif /* SOC_I2S_SUPPORTED */ diff --git a/libraries/ESP_I2S/src/ESP_I2S.h b/libraries/ESP_I2S/src/ESP_I2S.h new file mode 100644 index 00000000000..f291b9ade22 --- /dev/null +++ b/libraries/ESP_I2S/src/ESP_I2S.h @@ -0,0 +1,145 @@ +#pragma once + +#include "soc/soc_caps.h" +#if SOC_I2S_SUPPORTED + +#include "Arduino.h" +#include "esp_err.h" +#include "driver/i2s_std.h" +#if SOC_I2S_SUPPORTS_TDM +#include "driver/i2s_tdm.h" +#endif +#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX +#include "driver/i2s_pdm.h" +#endif + +typedef esp_err_t (*i2s_channel_read_fn)(i2s_chan_handle_t handle, char * tmp_buf, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms); + +typedef enum { + I2S_MODE_STD, +#if SOC_I2S_SUPPORTS_TDM + I2S_MODE_TDM, +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + I2S_MODE_PDM_TX, +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + I2S_MODE_PDM_RX, +#endif + I2S_MODE_MAX +} i2s_mode_t; + +typedef enum { + I2S_RX_TRANSFORM_NONE, + I2S_RX_TRANSFORM_32_TO_16, + I2S_RX_TRANSFORM_16_STEREO_TO_MONO, + I2S_RX_TRANSFORM_MAX +} i2s_rx_transform_t; + +class I2SClass: public Stream { + public: + I2SClass(); + ~I2SClass(); + + //STD + TDM mode + void setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din=-1, int8_t mclk=-1); + void setInverted(bool bclk, bool ws, bool mclk=false); + + //PDM TX + PDM RX mode +#if SOC_I2S_SUPPORTS_PDM_TX + void setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1=-1); +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + void setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1=-1, int8_t din2=-1, int8_t din3=-1); +#endif +#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX + void setInvertedPdm(bool clk); +#endif + + bool begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch +#if SOC_I2S_SUPPORTS_TDM + , int8_t slot_mask=-1 +#endif + ); + bool configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); + bool configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform=I2S_RX_TRANSFORM_NONE); + bool end(); + + size_t readBytes(char *buffer, size_t size); + size_t write(uint8_t *buffer, size_t size); + + i2s_chan_handle_t txChan(); + uint32_t txSampleRate(); + i2s_data_bit_width_t txDataWidth(); + i2s_slot_mode_t txSlotMode(); + + i2s_chan_handle_t rxChan(); + uint32_t rxSampleRate(); + i2s_data_bit_width_t rxDataWidth(); + i2s_slot_mode_t rxSlotMode(); + + int lastError(); + + int available(); + int peek(); + int read(); + size_t write(uint8_t d); + + // Record short PCM WAV to memory with current RX settings. Returns buffer that must be freed by the user. + uint8_t * recordWAV(size_t rec_seconds, size_t * out_size); + // Play short PCM WAV from memory + void playWAV(uint8_t * data, size_t len); + // Play short MP3 from memory + bool playMP3(uint8_t *src, size_t src_len); + + + private: + esp_err_t last_error; + i2s_mode_t _mode; + + i2s_chan_handle_t tx_chan; + uint32_t tx_sample_rate; + i2s_data_bit_width_t tx_data_bit_width; + i2s_slot_mode_t tx_slot_mode; + + i2s_channel_read_fn rx_fn; + i2s_rx_transform_t rx_transform; + char * rx_transform_buf; + size_t rx_transform_buf_len; + + i2s_chan_handle_t rx_chan; + uint32_t rx_sample_rate; + i2s_data_bit_width_t rx_data_bit_width; + i2s_slot_mode_t rx_slot_mode; + + //STD and TDM mode + int8_t _mclk, _bclk, _ws, _dout, _din; + bool _mclk_inv, _bclk_inv, _ws_inv; + + //PDM mode +#if SOC_I2S_SUPPORTS_PDM_RX + int8_t _rx_clk, _rx_din0, _rx_din1, _rx_din2, _rx_din3; //TODO: soc_caps.h 1/4 + bool _rx_clk_inv; +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + int8_t _tx_clk, _tx_dout0, _tx_dout1; + bool _tx_clk_inv; +#endif + + bool allocTranformRX(size_t buf_len); + bool transformRX(i2s_rx_transform_t transform); + + static bool i2sDetachBus(void * bus_pointer); + bool initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); +#if SOC_I2S_SUPPORTS_TDM + bool initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask); +#endif +#if SOC_I2S_SUPPORTS_PDM_TX + bool initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + bool initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch); +#endif +}; + +#endif /* SOC_I2S_SUPPORTED */ \ No newline at end of file diff --git a/libraries/ESP_I2S/src/wav_header.h b/libraries/ESP_I2S/src/wav_header.h new file mode 100644 index 00000000000..3969cac4043 --- /dev/null +++ b/libraries/ESP_I2S/src/wav_header.h @@ -0,0 +1,91 @@ +#pragma once +#include + +/** + * @brief Header structure for WAV file with only one data chunk + * + * @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/ + * + * @note Assignment to variables in this struct directly is only possible for little endian architectures + * (including Xtensa & RISC-V) + */ + +typedef struct { + char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */ + uint32_t chunk_size; /*!< This is the size of the rest of the chunk following this number */ + char chunk_format[4]; /*!< Contains the letters "WAVE" */ +} __attribute__((packed)) wav_descriptor_chunk_t; /*!< Canonical WAVE format starts with the RIFF header */ + +typedef struct { + char subchunk_id[4]; /*!< Contains the letters "fmt " */ + uint32_t subchunk_size; /*!< PCM = 16, This is the size of the rest of the Subchunk which follows this number */ + uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form of compression */ + uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */ + uint32_t sample_rate; /*!< 8000, 44100, etc. */ + uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */ + uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */ + uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */ +} __attribute__((packed)) pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */ + +typedef struct { + char subchunk_id[4]; /*!< Contains the letters "fmt " */ + uint32_t subchunk_size; /*!< ALAW/MULAW = 18, This is the size of the rest of the Subchunk which follows this number */ + uint16_t audio_format; /*!< ALAW = 6, MULAW = 7, values other than 1 indicate some form of compression */ + uint16_t num_of_channels; /*!< ALAW/MULAW = 1, Mono = 1, Stereo = 2, etc. */ + uint32_t sample_rate; /*!< ALAW/MULAW = 8000, 8000, 44100, etc. */ + uint32_t byte_rate; /*!< ALAW/MULAW = 8000, ==SampleRate * NumChannels * BitsPerSample s/ 8 */ + uint16_t block_align; /*!< ALAW/MULAW = 1, ==NumChannels * BitsPerSample / 8 */ + uint16_t bits_per_sample; /*!< ALAW/MULAW = 8, 8 bits = 8, 16 bits = 16, etc. */ + uint16_t ext_size; /*!< ALAW/MULAW = 0, Size of the extension (0 or 22) */ +} __attribute__((packed)) non_pcm_wav_fmt_chunk_t; /*!< The "fmt " subchunk describes the sound data's format */ + +typedef struct { + char subchunk_id[4]; /*!< Contains the letters "data" */ + uint32_t subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */ +} __attribute__((packed)) wav_data_chunk_t; /*!< The "data" subchunk contains the size of the data and the actual sound */ + +typedef struct { + wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */ + pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */ + wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */ +} __attribute__((packed)) pcm_wav_header_t; + +typedef struct { + wav_descriptor_chunk_t descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */ + non_pcm_wav_fmt_chunk_t fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */ + wav_data_chunk_t data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */ +} __attribute__((packed)) non_pcm_wav_header_t; + +#define WAVE_FORMAT_PCM 1 // PCM +#define WAVE_FORMAT_IEEE_FLOAT 3 // IEEE float +#define WAVE_FORMAT_ALAW 6 // 8-bit ITU-T G.711 A-law +#define WAVE_FORMAT_MULAW 7 // 8-bit ITU-T G.711 µ-law + +#define PCM_WAV_HEADER_SIZE 44 +#define NON_PCM_WAV_HEADER_SIZE 46 + +/** + * @brief Default header for PCM format WAV files + * + */ +#define PCM_WAV_HEADER_DEFAULT(wav_sample_size, wav_sample_bits, wav_sample_rate, wav_channel_num) { \ + .descriptor_chunk = { \ + .chunk_id = {'R', 'I', 'F', 'F'}, \ + .chunk_size = (wav_sample_size) + sizeof(pcm_wav_header_t) - 8, \ + .chunk_format = {'W', 'A', 'V', 'E'} \ + }, \ + .fmt_chunk = { \ + .subchunk_id = {'f', 'm', 't', ' '}, \ + .subchunk_size = 16, /* 16 for PCM */ \ + .audio_format = WAVE_FORMAT_PCM, /* 1 for PCM */ \ + .num_of_channels = (uint16_t)(wav_channel_num), \ + .sample_rate = (wav_sample_rate), \ + .byte_rate = (wav_sample_bits) * (wav_sample_rate) * (wav_channel_num) / 8, \ + .block_align = (uint16_t)((wav_sample_bits) * (wav_channel_num) / 8), \ + .bits_per_sample = (uint16_t)(wav_sample_bits)\ + }, \ + .data_chunk = { \ + .subchunk_id = {'d', 'a', 't', 'a'}, \ + .subchunk_size = (wav_sample_size) \ + } \ +} diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c3 b/libraries/ESP_SR/examples/Basic/.skip.esp32 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c3 rename to libraries/ESP_SR/examples/Basic/.skip.esp32 diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32c3 b/libraries/ESP_SR/examples/Basic/.skip.esp32c3 similarity index 100% rename from libraries/I2S/examples/ADCPlotter/.skip.esp32c3 rename to libraries/ESP_SR/examples/Basic/.skip.esp32c3 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c6 b/libraries/ESP_SR/examples/Basic/.skip.esp32c6 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32c6 rename to libraries/ESP_SR/examples/Basic/.skip.esp32c6 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32h2 b/libraries/ESP_SR/examples/Basic/.skip.esp32h2 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32h2 rename to libraries/ESP_SR/examples/Basic/.skip.esp32h2 diff --git a/libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s2 b/libraries/ESP_SR/examples/Basic/.skip.esp32s2 similarity index 100% rename from libraries/ESP32/examples/I2S/HiFreq_ADC/.skip.esp32s2 rename to libraries/ESP_SR/examples/Basic/.skip.esp32s2 diff --git a/libraries/ESP_SR/examples/Basic/Basic.ino b/libraries/ESP_SR/examples/Basic/Basic.ino new file mode 100644 index 00000000000..c5e7641e7d8 --- /dev/null +++ b/libraries/ESP_SR/examples/Basic/Basic.ino @@ -0,0 +1,92 @@ + +#include "ESP_I2S.h" +#include "ESP_SR.h" + +#define I2S_PIN_BCK 17 +#define I2S_PIN_WS 47 +#define I2S_PIN_DIN 16 + +#define LIGHT_PIN 40 +#define FAN_PIN 41 + +I2SClass i2s; + +// Generated using the following command: +// python3 tools/gen_sr_commands.py "Turn on the light,Switch on the light;Turn off the light,Switch off the light,Go dark;Start fan;Stop fan" +enum { + SR_CMD_TURN_ON_THE_LIGHT, + SR_CMD_TURN_OFF_THE_LIGHT, + SR_CMD_START_FAN, + SR_CMD_STOP_FAN, +}; +static const sr_cmd_t sr_commands[] = { + { 0, "Turn on the light", "TkN nN jc LiT"}, + { 0, "Switch on the light", "SWgp nN jc LiT"}, + { 1, "Turn off the light", "TkN eF jc LiT"}, + { 1, "Switch off the light", "SWgp eF jc LiT"}, + { 1, "Go dark", "Gb DnRK"}, + { 2, "Start fan", "STnRT FaN"}, + { 3, "Stop fan", "STnP FaN"}, +}; + +void onSrEvent(sr_event_t event, int command_id, int phrase_id){ + switch(event){ + case SR_EVENT_WAKEWORD: + Serial.println("WakeWord Detected!"); + break; + case SR_EVENT_WAKEWORD_CHANNEL: + Serial.printf("WakeWord Channel %d Verified!\n", command_id); + ESP_SR.setMode(SR_MODE_COMMAND); // Switch to Command detection + break; + case SR_EVENT_TIMEOUT: + Serial.println("Timeout Detected!"); + ESP_SR.setMode(SR_MODE_WAKEWORD); // Switch back to WakeWord detection + break; + case SR_EVENT_COMMAND: + Serial.printf("Command %d Detected! %s\n", command_id, sr_commands[phrase_id].str); + switch(command_id){ + case SR_CMD_TURN_ON_THE_LIGHT: + digitalWrite(LIGHT_PIN, HIGH); + break; + case SR_CMD_TURN_OFF_THE_LIGHT: + digitalWrite(LIGHT_PIN, LOW); + break; + case SR_CMD_START_FAN: + digitalWrite(FAN_PIN, HIGH); + break; + case SR_CMD_STOP_FAN: + digitalWrite(FAN_PIN, LOW); + break; + default: + Serial.println("Unknown Command!"); + break; + } + ESP_SR.setMode(SR_MODE_COMMAND); // Allow for more commands to be given, before timeout + // ESP_SR.setMode(SR_MODE_WAKEWORD); // Switch back to WakeWord detection + break; + default: + Serial.println("Unknown Event!"); + break; + } +} + +void setup(){ + Serial.begin(115200); + + pinMode(LIGHT_PIN, OUTPUT); + digitalWrite(LIGHT_PIN, LOW); + pinMode(FAN_PIN, OUTPUT); + digitalWrite(FAN_PIN, LOW); + + i2s.setPins(I2S_PIN_BCK, I2S_PIN_WS, -1, I2S_PIN_DIN); + i2s.setTimeout(1000); + i2s.begin(I2S_MODE_STD, 16000, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + + + ESP_SR.onEvent(onSrEvent); + ESP_SR.begin(i2s, sr_commands, sizeof(sr_commands) / sizeof(sr_cmd_t), SR_CHANNELS_STEREO, SR_MODE_WAKEWORD); +} + +void loop(){ + +} diff --git a/libraries/ESP_SR/keywords.txt b/libraries/ESP_SR/keywords.txt new file mode 100644 index 00000000000..30a3925187c --- /dev/null +++ b/libraries/ESP_SR/keywords.txt @@ -0,0 +1,40 @@ +####################################### +# Syntax Coloring Map For ESP_SR +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP_SR KEYWORD1 +ESP_SR_Class KEYWORD1 +sr_cmd_t KEYWORD1 +sr_event_t KEYWORD1 +sr_mode_t KEYWORD1 +sr_channels_t KEYWORD1 +sr_cb KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +onEvent KEYWORD2 +setMode KEYWORD2 +pause KEYWORD2 +resume KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +SR_EVENT_WAKEWORD LITERAL1 +SR_EVENT_WAKEWORD_CHANNEL LITERAL1 +SR_EVENT_COMMAND LITERAL1 +SR_EVENT_TIMEOUT LITERAL1 +SR_MODE_OFF LITERAL1 +SR_MODE_WAKEWORD LITERAL1 +SR_MODE_COMMAND LITERAL1 +SR_MODE_MAX LITERAL1 +SR_CHANNELS_MONO LITERAL1 +SR_CHANNELS_STEREO LITERAL1 +SR_CHANNELS_MAX LITERAL1 diff --git a/libraries/ESP_SR/library.properties b/libraries/ESP_SR/library.properties new file mode 100755 index 00000000000..518cc915fcf --- /dev/null +++ b/libraries/ESP_SR/library.properties @@ -0,0 +1,9 @@ +name=ESP_SR +version=1.0.0 +author=me-no-dev +maintainer=me-no-dev +sentence=Library for ESP Sound Recognition +paragraph=Supports ESP32 Arduino platforms. +category=Sound +url=https://github.com/espressif/arduino-esp32/ +architectures=esp32 \ No newline at end of file diff --git a/libraries/ESP_SR/src/ESP_SR.cpp b/libraries/ESP_SR/src/ESP_SR.cpp new file mode 100644 index 00000000000..f020d915f7a --- /dev/null +++ b/libraries/ESP_SR/src/ESP_SR.cpp @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) +#include "ESP_SR.h" + +static esp_err_t on_sr_fill(void * arg, void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + return ((ESP_SR_Class*)arg)->_fill(out, len, bytes_read, timeout_ms); +} + +static void on_sr_event(void * arg, sr_event_t event, int command_id, int phrase_id){ + ((ESP_SR_Class*)arg)->_sr_event(event, command_id, phrase_id); +} + +ESP_SR_Class::ESP_SR_Class() + : cb(NULL) + , i2s(NULL) +{ + +} + +ESP_SR_Class::~ESP_SR_Class(){ + end(); +} + +void ESP_SR_Class::onEvent(sr_cb event_cb){ + cb = event_cb; +} + +bool ESP_SR_Class::begin(I2SClass & _i2s, const sr_cmd_t * sr_commands, size_t sr_commands_len, sr_channels_t rx_chan, sr_mode_t mode){ + i2s = &_i2s; + esp_err_t err = sr_start(on_sr_fill, this, rx_chan, mode, sr_commands, sr_commands_len, on_sr_event, this); + return (err == ESP_OK); +} + +bool ESP_SR_Class::end(void){ + return sr_stop() == ESP_OK; +} + +bool ESP_SR_Class::setMode(sr_mode_t mode){ + return sr_set_mode(mode) == ESP_OK; +} + +bool ESP_SR_Class::pause(void){ + return sr_pause() == ESP_OK; +} + +bool ESP_SR_Class::resume(void){ + return sr_resume() == ESP_OK; +} + +void ESP_SR_Class::_sr_event(sr_event_t event, int command_id, int phrase_id){ + if(cb){ + cb(event, command_id, phrase_id); + } +} + +esp_err_t ESP_SR_Class::_fill(void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms){ + if(i2s == NULL){ + return ESP_FAIL; + } + i2s->setTimeout(timeout_ms); + *bytes_read = i2s->readBytes((char *)out, len); + return (esp_err_t)i2s->lastError(); +} + +ESP_SR_Class ESP_SR; + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/src/ESP_SR.h b/libraries/ESP_SR/src/ESP_SR.h new file mode 100644 index 00000000000..4af4276b6e3 --- /dev/null +++ b/libraries/ESP_SR/src/ESP_SR.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) + +#include "ESP_I2S.h" +#include "esp32-hal-sr.h" + +typedef void (*sr_cb)(sr_event_t event, int command_id, int phrase_id); + +class ESP_SR_Class { + private: + sr_cb cb; + I2SClass * i2s; + public: + ESP_SR_Class(); + ~ESP_SR_Class(); + + void onEvent(sr_cb cb); + bool begin(I2SClass & i2s, const sr_cmd_t * sr_commands, size_t sr_commands_len, sr_channels_t rx_chan=SR_CHANNELS_STEREO, sr_mode_t mode=SR_MODE_WAKEWORD); + bool end(void); + bool setMode(sr_mode_t mode); + bool pause(void); + bool resume(void); + + void _sr_event(sr_event_t event, int command_id, int phrase_id); + esp_err_t _fill(void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms); +}; + +extern ESP_SR_Class ESP_SR; + + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/src/esp32-hal-sr.c b/libraries/ESP_SR/src/esp32-hal-sr.c new file mode 100644 index 00000000000..d33a52d1c81 --- /dev/null +++ b/libraries/ESP_SR/src/esp32-hal-sr.c @@ -0,0 +1,445 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) + +#if !defined(ARDUINO_PARTITION_esp_sr_32) && !defined(ARDUINO_PARTITION_esp_sr_16) && !defined(ARDUINO_PARTITION_esp_sr_8) +#warning Compatible partition must be selected for ESP_SR to work +#endif + +#include +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/event_groups.h" +#include "freertos/task.h" +#include "esp_task_wdt.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_mn_speech_commands.h" +#include "esp_process_sdkconfig.h" +#include "esp_afe_sr_models.h" +#include "esp_mn_models.h" +#include "esp_wn_iface.h" +#include "esp_wn_models.h" +#include "esp_afe_sr_iface.h" +#include "esp_mn_iface.h" +#include "model_path.h" + +#include "driver/i2s_common.h" +#include "esp32-hal-sr.h" +#include "esp32-hal-log.h" + +#undef ESP_GOTO_ON_FALSE +#define ESP_GOTO_ON_FALSE(a, err_code, goto_tag, format, ...) do { \ + if (unlikely(!(a))) { \ + log_e(format, ##__VA_ARGS__); \ + ret = err_code; \ + goto goto_tag; \ + } \ + } while (0) + +#undef ESP_RETURN_ON_FALSE +#define ESP_RETURN_ON_FALSE(a, err_code, format, ...) do { \ + if (unlikely(!(a))) { \ + log_e(format, ##__VA_ARGS__); \ + return err_code; \ + } \ + } while(0) + +#define NEED_DELETE BIT0 +#define FEED_DELETED BIT1 +#define DETECT_DELETED BIT2 +#define PAUSE_FEED BIT3 +#define PAUSE_DETECT BIT4 +#define RESUME_FEED BIT5 +#define RESUME_DETECT BIT6 + +typedef struct { + wakenet_state_t wakenet_mode; + esp_mn_state_t state; + int command_id; + int phrase_id; +} sr_result_t; + +typedef struct { + model_iface_data_t *model_data; + const esp_mn_iface_t *multinet; + const esp_afe_sr_iface_t *afe_handle; + esp_afe_sr_data_t *afe_data; + int16_t *afe_in_buffer; + sr_mode_t mode; + uint8_t i2s_rx_chan_num; + sr_event_cb user_cb; + void * user_cb_arg; + sr_fill_cb fill_cb; + void * fill_cb_arg; + TaskHandle_t feed_task; + TaskHandle_t detect_task; + TaskHandle_t handle_task; + QueueHandle_t result_que; + EventGroupHandle_t event_group; +} sr_data_t; + +static int SR_CHANNEL_NUM = 3; + +static srmodel_list_t *models = NULL; +static sr_data_t *g_sr_data = NULL; + +esp_err_t sr_set_mode(sr_mode_t mode); + +void sr_handler_task(void *pvParam) +{ + while (true) { + sr_result_t result; + if(xQueueReceive(g_sr_data->result_que, &result, portMAX_DELAY) != pdTRUE){ + continue; + } + + if (WAKENET_DETECTED == result.wakenet_mode) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_WAKEWORD, -1, -1); + } + continue; + } + + if (WAKENET_CHANNEL_VERIFIED == result.wakenet_mode) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_WAKEWORD_CHANNEL, result.command_id, -1); + } + continue; + } + + if (ESP_MN_STATE_DETECTED == result.state) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_COMMAND, result.command_id, result.phrase_id); + } + continue; + } + + if (ESP_MN_STATE_TIMEOUT == result.state) { + if(g_sr_data->user_cb){ + g_sr_data->user_cb(g_sr_data->user_cb_arg, SR_EVENT_TIMEOUT, -1, -1); + } + continue; + } + } + vTaskDelete(NULL); +} + +static void audio_feed_task(void *arg) +{ + size_t bytes_read = 0; + int audio_chunksize = g_sr_data->afe_handle->get_feed_chunksize(g_sr_data->afe_data); + log_i("audio_chunksize=%d, feed_channel=%d", audio_chunksize, SR_CHANNEL_NUM); + + /* Allocate audio buffer and check for result */ + int16_t *audio_buffer = heap_caps_malloc(audio_chunksize * sizeof(int16_t) * SR_CHANNEL_NUM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (NULL == audio_buffer) { + esp_system_abort("No mem for audio buffer"); + } + g_sr_data->afe_in_buffer = audio_buffer; + + while (true) { + EventBits_t bits = xEventGroupGetBits(g_sr_data->event_group); + if (NEED_DELETE & bits) { + xEventGroupSetBits(g_sr_data->event_group, FEED_DELETED); + break; + } + if (PAUSE_FEED & bits) { + xEventGroupWaitBits(g_sr_data->event_group, PAUSE_FEED | RESUME_FEED, 1, 1, portMAX_DELAY); + } + + /* Read audio data from I2S bus */ + //ToDo: handle error + if(g_sr_data->fill_cb == NULL){ + vTaskDelay(100); + continue; + } + esp_err_t err = g_sr_data->fill_cb(g_sr_data->fill_cb_arg, (char *)audio_buffer, audio_chunksize * g_sr_data->i2s_rx_chan_num * sizeof(int16_t), &bytes_read, portMAX_DELAY); + if(err != ESP_OK){ + vTaskDelay(100); + continue; + } + + /* Channel Adjust */ + if(g_sr_data->i2s_rx_chan_num == 1){ + for (int i = audio_chunksize - 1; i >= 0; i--) { + audio_buffer[i * SR_CHANNEL_NUM + 2] = 0; + audio_buffer[i * SR_CHANNEL_NUM + 1] = 0; + audio_buffer[i * SR_CHANNEL_NUM + 0] = audio_buffer[i]; + } + } else if(g_sr_data->i2s_rx_chan_num == 2){ + for (int i = audio_chunksize - 1; i >= 0; i--) { + audio_buffer[i * SR_CHANNEL_NUM + 2] = 0; + audio_buffer[i * SR_CHANNEL_NUM + 1] = audio_buffer[i * 2 + 1]; + audio_buffer[i * SR_CHANNEL_NUM + 0] = audio_buffer[i * 2 + 0]; + } + } else { + vTaskDelay(100); + continue; + } + + /* Feed samples of an audio stream to the AFE_SR */ + g_sr_data->afe_handle->feed(g_sr_data->afe_data, audio_buffer); + } + vTaskDelete(NULL); +} + +static void audio_detect_task(void *arg) +{ + int afe_chunksize = g_sr_data->afe_handle->get_fetch_chunksize(g_sr_data->afe_data); + int mu_chunksize = g_sr_data->multinet->get_samp_chunksize(g_sr_data->model_data); + assert(mu_chunksize == afe_chunksize); + log_i("------------detect start------------"); + + while (true) { + EventBits_t bits = xEventGroupGetBits(g_sr_data->event_group); + if (NEED_DELETE & bits) { + xEventGroupSetBits(g_sr_data->event_group, DETECT_DELETED); + break; + } + if (PAUSE_DETECT & bits) { + xEventGroupWaitBits(g_sr_data->event_group, PAUSE_DETECT | RESUME_DETECT, 1, 1, portMAX_DELAY); + } + + afe_fetch_result_t* res = g_sr_data->afe_handle->fetch(g_sr_data->afe_data); + if (!res || res->ret_value == ESP_FAIL) { + continue; + } + + if(g_sr_data->mode == SR_MODE_WAKEWORD){ + if (res->wakeup_state == WAKENET_DETECTED) { + log_d("wakeword detected"); + sr_result_t result = { + .wakenet_mode = WAKENET_DETECTED, + .state = ESP_MN_STATE_DETECTING, + .command_id = 0, + .phrase_id = 0, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + } + else if (res->wakeup_state == WAKENET_CHANNEL_VERIFIED) { + sr_set_mode(SR_MODE_OFF); + log_d("AFE_FETCH_CHANNEL_VERIFIED, channel index: %d", res->trigger_channel_id); + sr_result_t result = { + .wakenet_mode = WAKENET_CHANNEL_VERIFIED, + .state = ESP_MN_STATE_DETECTING, + .command_id = res->trigger_channel_id, + .phrase_id = 0, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + } + } + + if (g_sr_data->mode == SR_MODE_COMMAND) { + + esp_mn_state_t mn_state = ESP_MN_STATE_DETECTING; + mn_state = g_sr_data->multinet->detect(g_sr_data->model_data, res->data); + + if (ESP_MN_STATE_DETECTING == mn_state) { + continue; + } + + if (ESP_MN_STATE_TIMEOUT == mn_state) { + sr_set_mode(SR_MODE_OFF); + log_d("Time out"); + sr_result_t result = { + .wakenet_mode = WAKENET_NO_DETECT, + .state = mn_state, + .command_id = 0, + .phrase_id = 0, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + continue; + } + + if (ESP_MN_STATE_DETECTED == mn_state) { + sr_set_mode(SR_MODE_OFF); + esp_mn_results_t *mn_result = g_sr_data->multinet->get_results(g_sr_data->model_data); + for (int i = 0; i < mn_result->num; i++) { + log_d("TOP %d, command_id: %d, phrase_id: %d, prob: %f", + i + 1, mn_result->command_id[i], mn_result->phrase_id[i], mn_result->prob[i]); + } + + int sr_command_id = mn_result->command_id[0]; + int sr_phrase_id = mn_result->phrase_id[0]; + log_d("Deteted command : %d, phrase: %d", sr_command_id, sr_phrase_id); + sr_result_t result = { + .wakenet_mode = WAKENET_NO_DETECT, + .state = mn_state, + .command_id = sr_command_id, + .phrase_id = sr_phrase_id, + }; + xQueueSend(g_sr_data->result_que, &result, 0); + continue; + } + log_e("Exception unhandled"); + } + } + vTaskDelete(NULL); +} + +esp_err_t sr_set_mode(sr_mode_t mode) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + switch(mode){ + case SR_MODE_OFF: + if(g_sr_data->mode == SR_MODE_WAKEWORD){ + g_sr_data->afe_handle->disable_wakenet(g_sr_data->afe_data); + } + break; + case SR_MODE_WAKEWORD: + if(g_sr_data->mode != SR_MODE_WAKEWORD){ + g_sr_data->afe_handle->enable_wakenet(g_sr_data->afe_data); + } + break; + case SR_MODE_COMMAND: + if(g_sr_data->mode == SR_MODE_WAKEWORD){ + g_sr_data->afe_handle->disable_wakenet(g_sr_data->afe_data); + } + break; + default: + return ESP_FAIL; + } + g_sr_data->mode = mode; + return ESP_OK; +} + +esp_err_t sr_start(sr_fill_cb fill_cb, void * fill_cb_arg, sr_channels_t rx_chan, sr_mode_t mode, const sr_cmd_t sr_commands[], size_t cmd_number, sr_event_cb cb, void * cb_arg) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(NULL == g_sr_data, ESP_ERR_INVALID_STATE, "SR already running"); + + g_sr_data = heap_caps_calloc(1, sizeof(sr_data_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_NO_MEM, "Failed create sr data"); + + g_sr_data->result_que = xQueueCreate(3, sizeof(sr_result_t)); + ESP_GOTO_ON_FALSE(NULL != g_sr_data->result_que, ESP_ERR_NO_MEM, err, "Failed create result queue"); + + g_sr_data->event_group = xEventGroupCreate(); + ESP_GOTO_ON_FALSE(NULL != g_sr_data->event_group, ESP_ERR_NO_MEM, err, "Failed create event_group"); + + BaseType_t ret_val; + g_sr_data->user_cb = cb; + g_sr_data->user_cb_arg = cb_arg; + g_sr_data->fill_cb = fill_cb; + g_sr_data->fill_cb_arg = fill_cb_arg; + g_sr_data->i2s_rx_chan_num = rx_chan + 1; + g_sr_data->mode = mode; + + // Init Model + log_d("init model"); + models = esp_srmodel_init("model"); + + // Load WakeWord Detection + g_sr_data->afe_handle = (esp_afe_sr_iface_t *)&ESP_AFE_SR_HANDLE; + afe_config_t afe_config = AFE_CONFIG_DEFAULT(); + afe_config.wakenet_model_name = esp_srmodel_filter(models, ESP_WN_PREFIX, "hiesp"); + afe_config.aec_init = false; + log_d("load wakenet '%s'", afe_config.wakenet_model_name); + g_sr_data->afe_data = g_sr_data->afe_handle->create_from_config(&afe_config); + + // Load Custom Command Detection + char *mn_name = esp_srmodel_filter(models, ESP_MN_PREFIX, ESP_MN_ENGLISH); + log_d("load multinet '%s'", mn_name); + g_sr_data->multinet = esp_mn_handle_from_name(mn_name); + log_d("load model_data '%s'", mn_name); + g_sr_data->model_data = g_sr_data->multinet->create(mn_name, 5760); + + + // Add commands + esp_mn_commands_alloc((esp_mn_iface_t *)g_sr_data->multinet, (model_iface_data_t *)g_sr_data->model_data); + log_i("add %d commands", cmd_number); + for (size_t i = 0; i < cmd_number; i++) { + esp_mn_commands_add(sr_commands[i].command_id, (char *)(sr_commands[i].phoneme)); + log_i(" cmd[%d] phrase[%d]:'%s'", sr_commands[i].command_id, i, sr_commands[i].str); + } + + // Load commands + esp_mn_error_t *err_id = esp_mn_commands_update(); + if(err_id){ + for (int i = 0; i < err_id->num; i++) { + log_e("err cmd id:%d", err_id->phrases[i]->command_id); + } + } + + //Start tasks + log_d("start tasks"); + ret_val = xTaskCreatePinnedToCore(&audio_feed_task, "SR Feed Task", 4 * 1024, NULL, 5, &g_sr_data->feed_task, 0); + ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio feed task"); + vTaskDelay(10); + ret_val = xTaskCreatePinnedToCore(&audio_detect_task, "SR Detect Task", 8 * 1024, NULL, 5, &g_sr_data->detect_task, 1); + ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio detect task"); + ret_val = xTaskCreatePinnedToCore(&sr_handler_task, "SR Handler Task", 6 * 1024, NULL, configMAX_PRIORITIES - 1, &g_sr_data->handle_task, 1); + //ret_val = xTaskCreatePinnedToCore(&sr_handler_task, "SR Handler Task", 6 * 1024, NULL, configMAX_PRIORITIES - 1, &g_sr_data->handle_task, 0); + ESP_GOTO_ON_FALSE(pdPASS == ret_val, ESP_FAIL, err, "Failed create audio handler task"); + + return ESP_OK; +err: + sr_stop(); + return ret; +} + +esp_err_t sr_stop(void) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + + /** + * Waiting for all task stoped + * TODO: A task creation failure cannot be handled correctly now + * */ + vTaskDelete(g_sr_data->handle_task); + xEventGroupSetBits(g_sr_data->event_group, NEED_DELETE); + xEventGroupWaitBits(g_sr_data->event_group, NEED_DELETE | FEED_DELETED | DETECT_DELETED, 1, 1, portMAX_DELAY); + + if (g_sr_data->result_que) { + vQueueDelete(g_sr_data->result_que); + g_sr_data->result_que = NULL; + } + + if (g_sr_data->event_group) { + vEventGroupDelete(g_sr_data->event_group); + g_sr_data->event_group = NULL; + } + + if (g_sr_data->model_data) { + g_sr_data->multinet->destroy(g_sr_data->model_data); + } + + if (g_sr_data->afe_data) { + g_sr_data->afe_handle->destroy(g_sr_data->afe_data); + } + + if (g_sr_data->afe_in_buffer) { + heap_caps_free(g_sr_data->afe_in_buffer); + } + + heap_caps_free(g_sr_data); + g_sr_data = NULL; + return ESP_OK; +} + +esp_err_t sr_pause(void) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + xEventGroupSetBits(g_sr_data->event_group, PAUSE_FEED | PAUSE_DETECT); + return ESP_OK; +} + +esp_err_t sr_resume(void) +{ + ESP_RETURN_ON_FALSE(NULL != g_sr_data, ESP_ERR_INVALID_STATE, "SR is not running"); + xEventGroupSetBits(g_sr_data->event_group, RESUME_FEED | RESUME_DETECT); + return ESP_OK; +} + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/src/esp32-hal-sr.h b/libraries/ESP_SR/src/esp32-hal-sr.h new file mode 100644 index 00000000000..33d22a79d93 --- /dev/null +++ b/libraries/ESP_SR/src/esp32-hal-sr.h @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S3 && (CONFIG_USE_WAKENET || CONFIG_USE_MULTINET) + +#include "driver/i2s_types.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SR_CMD_STR_LEN_MAX 64 +#define SR_CMD_PHONEME_LEN_MAX 64 + +typedef struct sr_cmd_t { + int command_id; + char str[SR_CMD_STR_LEN_MAX]; + char phoneme[SR_CMD_PHONEME_LEN_MAX]; +} sr_cmd_t; + +typedef enum { + SR_EVENT_WAKEWORD,//WakeWord Detected + SR_EVENT_WAKEWORD_CHANNEL,//WakeWord Channel Verified + SR_EVENT_COMMAND,//Command Detected + SR_EVENT_TIMEOUT,//Command Timeout + SR_EVENT_MAX +} sr_event_t; + +typedef enum { + SR_MODE_OFF,//Detection Off + SR_MODE_WAKEWORD,//WakeWord Detection + SR_MODE_COMMAND,//Command Detection + SR_MODE_MAX +} sr_mode_t; + +typedef enum { + SR_CHANNELS_MONO, + SR_CHANNELS_STEREO, + SR_CHANNELS_MAX +} sr_channels_t; + +typedef void (*sr_event_cb)(void * arg, sr_event_t event, int command_id, int phrase_id); +typedef esp_err_t (*sr_fill_cb)(void * arg, void * out, size_t len, size_t *bytes_read, uint32_t timeout_ms); + +esp_err_t sr_start(sr_fill_cb fill_cb, void * fill_cb_arg, sr_channels_t rx_chan, sr_mode_t mode, const sr_cmd_t * sr_commands, size_t cmd_number, sr_event_cb cb, void * cb_arg); +esp_err_t sr_stop(void); +esp_err_t sr_pause(void); +esp_err_t sr_resume(void); +esp_err_t sr_set_mode(sr_mode_t mode); + +// static const sr_cmd_t sr_commands[] = { +// {0, "Turn On the Light", "TkN nN jc LiT"}, +// {0, "Switch On the Light", "SWgp nN jc LiT"}, +// {1, "Switch Off the Light", "SWgp eF jc LiT"}, +// {1, "Turn Off the Light", "TkN eF jc LiT"}, +// {2, "Turn Red", "TkN RfD"}, +// {3, "Turn Green", "TkN GRmN"}, +// {4, "Turn Blue", "TkN BLo"}, +// {5, "Customize Color", "KcSTcMiZ KcLk"}, +// {6, "Sing a song", "Sgl c Sel"}, +// {7, "Play Music", "PLd MYoZgK"}, +// {8, "Next Song", "NfKST Sel"}, +// {9, "Pause Playing", "PeZ PLdgl"}, +// }; + +#ifdef __cplusplus +} +#endif + +#endif // CONFIG_IDF_TARGET_ESP32S3 diff --git a/libraries/ESP_SR/tools/gen_sr_commands.py b/libraries/ESP_SR/tools/gen_sr_commands.py new file mode 100644 index 00000000000..f3629b71bcb --- /dev/null +++ b/libraries/ESP_SR/tools/gen_sr_commands.py @@ -0,0 +1,73 @@ +# pip3 install g2p_en +from g2p_en import G2p +import argparse + +# python3 gen_sr_commands.py "Turn on the light,Switch on the light;Turn off the light,Switch off the light,Go dark;Start fan;Stop fan;Volume down,Turn down;Mute sound;Next song;Pause playback" +# enum { +# SR_CMD_TURN_ON_THE_LIGHT, +# SR_CMD_TURN_OFF_THE_LIGHT, +# SR_CMD_START_FAN, +# SR_CMD_STOP_FAN, +# SR_CMD_VOLUME_DOWN, +# SR_CMD_MUTE_SOUND, +# SR_CMD_NEXT_SONG, +# SR_CMD_PAUSE_PLAYBACK, +# }; +# static const sr_cmd_t sr_commands[] = { +# { 0, "Turn on the light", "TkN nN jc LiT"}, +# { 0, "Switch on the light", "SWgp nN jc LiT"}, +# { 1, "Turn off the light", "TkN eF jc LiT"}, +# { 1, "Switch off the light", "SWgp eF jc LiT"}, +# { 1, "Go dark", "Gb DnRK"}, +# { 2, "Start fan", "STnRT FaN"}, +# { 3, "Stop fan", "STnP FaN"}, +# { 4, "Volume down", "VnLYoM DtN"}, +# { 4, "Turn down", "TkN DtN"}, +# { 5, "Mute sound", "MYoT StND"}, +# { 6, "Next song", "NfKST Sel"}, +# { 7, "Pause playback", "PeZ PLdBaK"}, +# }; + +def english_g2p(text): + g2p = G2p() + out = "static const sr_cmd_t sr_commands[] = {\n" + enum = "enum {\n" + alphabet={"AE1": "a", "N": "N", " ": " ", "OW1": "b", "V": "V", "AH0": "c", "L": "L", "F": "F", "EY1": "d", "S": "S", "B": "B", "R": "R", "AO1": "e", "D": "D", "AH1": "c", "EH1": "f", "OW0": "b", "IH0": "g", "G": "G", "HH": "h", "K": "K", "IH1": "g", "W": "W", "AY1": "i", "T": "T", "M": "M", "Z": "Z", "DH": "j", "ER0": "k", "P": "P", "NG": "l", "IY1": "m", "AA1": "n", "Y": "Y", "UW1": "o", "IY0": "m", "EH2": "f", "CH": "p", "AE0": "a", "JH": "q", "ZH": "r", "AA2": "n", "SH": "s", "AW1": "t", "OY1": "u", "AW2": "t", "IH2": "g", "AE2": "a", "EY2": "d", "ER1": "k", "TH": "v", "UH1": "w", "UW2": "o", "OW2": "b", "AY2": "i", "UW0": "o", "AH2": "c", "EH0": "f", "AW0": "t", "AO2": "e", "AO0": "e", "UH0": "w", "UH2": "w", "AA0": "n", "AY0": "i", "IY2": "m", "EY0": "d", "ER2": "k", "OY2": "u", "OY0": "u"} + + cmd_id = 0 + phrase_id = 0 + text_list = text.split(";") + for item in text_list: + item = item.split(",") + phrase_id = 0 + for phrase in item: + labels = g2p(phrase) + phoneme = "" + for char in labels: + if char not in alphabet: + print("skip %s, not found in alphabet") + continue + else: + phoneme += alphabet[char] + out += " { "+str(cmd_id)+", \""+phrase+"\", \""+phoneme+"\"},\n" + if phrase_id == 0: + enum += " SR_CMD_"+phrase.upper().replace(" ", "_")+",\n" + phrase_id += 1 + cmd_id += 1 + out += "};" + enum += "};" + # print(text) + print(enum) + print(out) + + return out + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(prog="English Speech Commands G2P") + parser.add_argument("text", type=str, default=None, help="input text") + args = parser.parse_args() + + if args.text is not None: + english_g2p(args.text) + diff --git a/libraries/FS/src/FSImpl.h b/libraries/FS/src/FSImpl.h index dc7b81b3899..24fb09f9e52 100644 --- a/libraries/FS/src/FSImpl.h +++ b/libraries/FS/src/FSImpl.h @@ -43,9 +43,9 @@ class FileImpl virtual const char* name() const = 0; virtual boolean isDirectory(void) = 0; virtual FileImplPtr openNextFile(const char* mode) = 0; - virtual boolean seekDir(long position); - virtual String getNextFileName(void); - virtual String getNextFileName(bool *isDir); + virtual boolean seekDir(long position) = 0; + virtual String getNextFileName(void) = 0; + virtual String getNextFileName(bool *isDir) = 0; virtual void rewindDirectory(void) = 0; virtual operator bool() = 0; }; diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32c6 b/libraries/I2S/examples/ADCPlotter/.skip.esp32c6 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/ADCPlotter/.skip.esp32h2 b/libraries/I2S/examples/ADCPlotter/.skip.esp32h2 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino b/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino deleted file mode 100644 index 5f3bd93ca9d..00000000000 --- a/libraries/I2S/examples/ADCPlotter/ADCPlotter.ino +++ /dev/null @@ -1,86 +0,0 @@ -/* - This example is only for ESP devices. - - This example demonstrates usage of integrated Digital to Analog Converter (DAC) - You can display sound wave from audio device, or just measure voltage. - - To display audio prepare circuit found in following link or drafted as ASCII art - https://forum.arduino.cc/index.php?topic=567581.0 - (!) Note that unlike in the link we are connecting the supply line to 3.3V (not 5V) - because ADC can measure only up to around 3V. Anything above 3V will be very inaccurate. - - ^ +3.3V - | - _ - | |10k - |_| - | | |10uF - GPIO34-------------*------------| |----------- line in -(Default ADC pin) | +| | - | - _ - | |10k - |_| - | - | - V GND - -Connect hot wire of your audio to Line in and GNd wire of audio cable to common ground (GND) - -Second option to measure voltage on trimmer / potentiometer has following connection - ^ +3.3V - | - _ - | | - GPIO34----------->| | -(Default ADC pin) |_| - | - | - _ - | | optional resistor - |_| - | - | - V GND - Optional resistor will decrease read value. - - Steps to run: - 1. Select target board: - Tools -> Board -> ESP32 Arduino -> your board - 2. Upload sketch - Press upload button (arrow in top left corner) - When you see in console line like this: "Connecting........_____.....__" - On your board press and hold Boot button and press EN button shortly. Now you can release both buttons. - You should see lines like this: "Writing at 0x00010000... (12 %)" with rising percentage on each line. - If this fails, try the board buttons right after pressing upload button, or reconnect the USB cable. - 3. Open plotter - Tools -> Serial Plotter - Enjoy - -Created by Tomas Pilny -on 17th June 2021 -*/ - -#include - -void setup() { - // Open serial communications and wait for port to open: - // A baud rate of 115200 is used instead of 9600 for a faster data rate - // on non-native USB ports - Serial.begin(115200); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start I2S at 8 kHz with 32-bits per sample - if (!I2S.begin(ADC_DAC_MODE, 8000, 16)) { - Serial.println("Failed to initialize I2S!"); - while (1); // do nothing - } -} - -void loop() { - // read a sample - int sample = I2S.read(); - Serial.println(sample); -} diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32c3 b/libraries/I2S/examples/FullDuplex/.skip.esp32c3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32c6 b/libraries/I2S/examples/FullDuplex/.skip.esp32c6 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/FullDuplex/.skip.esp32h2 b/libraries/I2S/examples/FullDuplex/.skip.esp32h2 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/FullDuplex/FullDuplex.ino b/libraries/I2S/examples/FullDuplex/FullDuplex.ino deleted file mode 100644 index 9b3625fbd60..00000000000 --- a/libraries/I2S/examples/FullDuplex/FullDuplex.ino +++ /dev/null @@ -1,59 +0,0 @@ -/* - This example is only for ESP - This example demonstrates simultaneous usage of microphone and speaker using single I2S module. - The application transfers data from input to output - - Circuit: - * ESP32 - * GND connected GND - * VIN connected 5V - * SCK 5 - * FS 25 - * DIN 35 - * DOUT 26 - * I2S microphone - * I2S decoder + headphones / speaker - - created 8 October 2021 - by Tomas Pilny - */ - -#include -const long sampleRate = 16000; -const int bitsPerSample = 32; -uint8_t *buffer; - -void setup() { - Serial.begin(115200); - //I2S.setAllPins(5, 25, 35, 26); // you can change default pins; order of pins = (CLK, WS, IN, OUT) - if(!I2S.setDuplex()){ - Serial.println("ERROR - could not set duplex"); - while(true){ - vTaskDelay(10); // Cannot continue - } - } - if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, bitsPerSample)) { - Serial.println("Failed to initialize I2S!"); - while(true){ - vTaskDelay(10); // Cannot continue - } - } - buffer = (uint8_t*) malloc(I2S.getBufferSize() * (bitsPerSample / 8)); - if(buffer == NULL){ - Serial.println("Failed to allocate buffer!"); - while(true){ - vTaskDelay(10); // Cannot continue - } - } - Serial.println("Setup done"); -} - -void loop() { - //I2S.write(I2S.read()); // primitive implementation sample-by-sample - - // Buffer based implementation - I2S.read(buffer, I2S.getBufferSize() * (bitsPerSample / 8)); - I2S.write(buffer, I2S.getBufferSize() * (bitsPerSample / 8)); - - //optimistic_yield(1000); // yield if last yield occurred before CPU clock cycles ago -} diff --git a/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c3 b/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c6 b/libraries/I2S/examples/InputSerialPlotter/.skip.esp32c6 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/InputSerialPlotter/.skip.esp32h2 b/libraries/I2S/examples/InputSerialPlotter/.skip.esp32h2 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino b/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino deleted file mode 100644 index db9f7d0d4f8..00000000000 --- a/libraries/I2S/examples/InputSerialPlotter/InputSerialPlotter.ino +++ /dev/null @@ -1,44 +0,0 @@ -/* - This example reads audio data from an Invensense's ICS43432 I2S microphone - breakout board, and prints out the samples to the Serial console. The - Serial Plotter built into the Arduino IDE can be used to plot the audio - data (Tools -> Serial Plotter) - - Circuit: - * Arduino/Genuino Zero, MKR family and Nano 33 IoT - * ICS43432: - * GND connected GND - * 3.3V connected to 3.3V (Zero, Nano, ESP32), VCC (MKR) - * WS connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32) - * CLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32) - * SD connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32) - created 17 November 2016 - by Sandeep Mistry - */ - -#include - -void setup() { - // Open serial communications and wait for port to open: - // A baud rate of 115200 is used instead of 9600 for a faster data rate - // on non-native USB ports - Serial.begin(115200); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start I2S at 8 kHz with 32-bits per sample - if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) { - Serial.println("Failed to initialize I2S!"); - while (1); // do nothing - } -} - -void loop() { - // read a sample - int sample = I2S.read(); - - if (sample && sample != -1 && sample != 1) { - Serial.println(sample); - } -} diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32c3 b/libraries/I2S/examples/SimpleTone/.skip.esp32c3 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32c6 b/libraries/I2S/examples/SimpleTone/.skip.esp32c6 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/SimpleTone/.skip.esp32h2 b/libraries/I2S/examples/SimpleTone/.skip.esp32h2 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libraries/I2S/examples/SimpleTone/SimpleTone.ino b/libraries/I2S/examples/SimpleTone/SimpleTone.ino deleted file mode 100644 index 49cae77238f..00000000000 --- a/libraries/I2S/examples/SimpleTone/SimpleTone.ino +++ /dev/null @@ -1,79 +0,0 @@ -/* - This example generates a square wave based tone at a specified frequency - and sample rate. Then outputs the data using the I2S interface to a - MAX08357 I2S Amp Breakout board. - - I2S Circuit: - * Arduino/Genuino Zero, MKR family and Nano 33 IoT - * MAX08357: - * GND connected GND - * VIN connected 5V - * LRC connected to pin 0 (Zero) or 3 (MKR), A2 (Nano) or 25 (ESP32) - * BCLK connected to pin 1 (Zero) or 2 (MKR), A3 (Nano) or 5 (ESP32) - * DIN connected to pin 9 (Zero) or A6 (MKR), 4 (Nano) or 26 (ESP32) - - DAC Circuit: - * ESP32 or ESP32-S2 - * Audio amplifier - - Note: - - ESP32 has DAC on GPIO pins 25 and 26. - - ESP32-S2 has DAC on GPIO pins 17 and 18. - - Connect speaker(s) or headphones. - - created 17 November 2016 - by Sandeep Mistry - For ESP extended - Tomas Pilny - 2nd September 2021 - */ - -#include -const int frequency = 440; // frequency of square wave in Hz -const int amplitude = 500; // amplitude of square wave -const int sampleRate = 8000; // sample rate in Hz -const int bps = 16; - -const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave - -int32_t sample = amplitude; // current sample value -int count = 0; - -i2s_mode_t mode = I2S_PHILIPS_MODE; // I2S decoder is needed -// i2s_mode_t mode = ADC_DAC_MODE; // Audio amplifier is needed - -// Mono channel input -// This is ESP specific implementation - -// samples will be automatically copied to both channels inside I2S driver -// If you want to have true mono output use I2S_PHILIPS_MODE and interlay -// second channel with 0-value samples. -// The order of channels is RIGH followed by LEFT -//i2s_mode_t mode = I2S_RIGHT_JUSTIFIED_MODE; // I2S decoder is needed - -void setup() { - Serial.begin(115200); - Serial.println("I2S simple tone"); - - // start I2S at the sample rate with 16-bits per sample - if (!I2S.begin(mode, sampleRate, bps)) { - Serial.println("Failed to initialize I2S!"); - while (1); // do nothing - } -} - -void loop() { - if (count % halfWavelength == 0 ) { - // invert the sample every half wavelength count multiple to generate square wave - sample = -1 * sample; - } - - if(mode == I2S_PHILIPS_MODE || mode == ADC_DAC_MODE){ // write the same sample twice, once for Right and once for Left channel - I2S.write(sample); // Right channel - I2S.write(sample); // Left channel - }else if(mode == I2S_RIGHT_JUSTIFIED_MODE || mode == I2S_LEFT_JUSTIFIED_MODE){ - // write the same only once - it will be automatically copied to the other channel - I2S.write(sample); - } - - // increment the counter for the next sample - count++; -} diff --git a/libraries/I2S/keywords.txt b/libraries/I2S/keywords.txt deleted file mode 100644 index ad1b8028d42..00000000000 --- a/libraries/I2S/keywords.txt +++ /dev/null @@ -1,61 +0,0 @@ -####################################### -# Syntax Coloring Map I2S -####################################### - -####################################### -# Datatypes (KEYWORD1) -####################################### - -I2S KEYWORD1 - -####################################### -# Methods and Functions (KEYWORD2) -####################################### -I2SClass KEYWORD2 -begin KEYWORD2 -end KEYWORD2 - -onReceive KEYWORD2 -onTransmit KEYWORD2 - -setSckPin KEYWORD2 -setFsPin KEYWORD2 -setDataInPin KEYWORD2 -setDataOutPin KEYWORD2 -setAllPins KEYWORD2 - -getSckPin KEYWORD2 -getFsPin KEYWORD2 -getDataPin KEYWORD2 -getDataInPin KEYWORD2 -getDataOutPin KEYWORD2 - -setDuplex KEYWORD2 -setSimplex KEYWORD2 -isDuplex KEYWORD2 - -setBufferSize KEYWORD2 -getBufferSize KEYWORD2 - -write KEYWORD2 -availableForWrite KEYWORD2 - -read KEYWORD2 -available KEYWORD2 - -gpioToAdcUnit KEYWORD2 -gpioToAdcChannel KEYWORD2 - -####################################### -# Constants (LITERAL1) -####################################### -I2S_PHILIPS_MODE LITERAL1 -I2S_RIGHT_JUSTIFIED_MODE LITERAL1 -I2S_LEFT_JUSTIFIED_MODE LITERAL1 -I2S_ADC_DAC LITERAL1 -I2S_PDM LITERAL1 - -PIN_I2S_SCK LITERAL1 -PIN_I2S_FS LITERAL1 -PIN_I2S_SD LITERAL1 -PIN_I2S_SD_OUT LITERAL1 diff --git a/libraries/I2S/library.properties b/libraries/I2S/library.properties deleted file mode 100644 index bb77e306158..00000000000 --- a/libraries/I2S/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=I2S -version=1.0 -author=Tomas Pilny -maintainer=Tomas Pilny -sentence=Enables the communication with devices that use the Inter-IC Sound (I2S) Bus. Specific implementation for ESP. -paragraph= -category=Communication -url=http://www.arduino.cc/en/Reference/I2S -architectures=esp32 diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp deleted file mode 100644 index 7414067eb87..00000000000 --- a/libraries/I2S/src/I2S.cpp +++ /dev/null @@ -1,1222 +0,0 @@ -/* - Copyright (c) 2016 Arduino LLC. All right reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#include -#include -#include "I2S.h" -#include "freertos/semphr.h" - -#define _I2S_EVENT_QUEUE_LENGTH 16 -#define _I2S_DMA_BUFFER_COUNT 2 // BUFFER COUNT must be between 2 and 128 -#define I2S_INTERFACES_COUNT SOC_I2S_NUM - -#ifndef I2S_DEVICE - #define I2S_DEVICE 0 -#endif - -#ifndef I2S_CLOCK_GENERATOR - #define I2S_CLOCK_GENERATOR 0 // does nothing for ESP -#endif - -I2SClass::I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin) : - _deviceIndex(deviceIndex), - _sdPin(sdPin), // shared data pin - _inSdPin(PIN_I2S_SD_IN), // input data pin - _outSdPin(PIN_I2S_SD), // output data pin - _sckPin(sckPin), // clock pin - _fsPin(fsPin), // frame (word) select pin - - _state(I2S_STATE_IDLE), - _bitsPerSample(0), - _sampleRate(0), - _mode(I2S_PHILIPS_MODE), - - _buffer_byte_size(0), - - _driverInstalled(false), - _initialized(false), - _callbackTaskHandle(NULL), - _i2sEventQueue(NULL), - _i2s_general_mutex(NULL), - _input_ring_buffer(NULL), - _output_ring_buffer(NULL), - _i2s_dma_buffer_size(128), // Number of frames in each DMA buffer. Frame size = number of channels * Bytes per sample; Must be between 8 and 1024 - _driveClock(true), - _peek_buff(0), - _peek_buff_valid(false), - _nesting_counter(0), - - _onTransmit(NULL), - _onReceive(NULL) -{ - _i2s_general_mutex = xSemaphoreCreateMutex(); - if(_i2s_general_mutex == NULL){ - log_e("I2S could not create internal mutex!"); - } -} - -int I2SClass::_createCallbackTask(){ - int stack_size = 20000; - if(_callbackTaskHandle != NULL){ - log_e("Callback task already exists!"); - return 0; // ERR - } - - xTaskCreate( - onDmaTransferComplete, // Function to implement the task - "onDmaTransferComplete", // Name of the task - stack_size, // Stack size in words - NULL, // Task input parameter - 2, // Priority of the task - &_callbackTaskHandle // Task handle. - ); - if(_callbackTaskHandle == NULL){ - log_e("Could not create callback task"); - return 0; // ERR - } - return 1; // OK -} - -int I2SClass::_installDriver(){ - if(_driverInstalled){ - log_e("I2S driver is already installed"); - return 0; // ERR - } - - esp_i2s::i2s_mode_t i2s_mode = (esp_i2s::i2s_mode_t)(esp_i2s::I2S_MODE_RX | esp_i2s::I2S_MODE_TX); - - if(_driveClock){ - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_MASTER); - }else{ - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_SLAVE); - } - - if(_mode == ADC_DAC_MODE){ - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_bitsPerSample != 16){ // ADC/DAC can only work in 16-bit sample mode - log_e("ERROR invalid bps for ADC/DAC. Allowed only 16, requested %d", _bitsPerSample); - return 0; // ERR - } - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_DAC_BUILT_IN | esp_i2s::I2S_MODE_ADC_BUILT_IN); - #else - log_e("This chip does not support ADC / DAC mode"); - return 0; // ERR - #endif - }else if(_mode == I2S_PHILIPS_MODE || - _mode == I2S_RIGHT_JUSTIFIED_MODE || - _mode == I2S_LEFT_JUSTIFIED_MODE){ // End of ADC/DAC mode; start of Normal Philips mode - if(_bitsPerSample != 8 && _bitsPerSample != 16 && _bitsPerSample != 24 && _bitsPerSample != 32){ - log_e("Invalid bits per sample for normal mode (requested %d)\nAllowed bps = 8 | 16 | 24 | 32", _bitsPerSample); - return 0; // ERR - } - if(_bitsPerSample == 24){ - log_w("Original Arduino library does not support 24 bits per sample.\nKeep that in mind if you should switch back to Arduino"); - } - }else if(_mode == PDM_STEREO_MODE || _mode == PDM_MONO_MODE){ // end of Normal Philips mode; start of PDM mode - #if (SOC_I2S_SUPPORTS_PDM_TX && SOC_I2S_SUPPORTS_PDM_RX) - i2s_mode = (esp_i2s::i2s_mode_t)(i2s_mode | esp_i2s::I2S_MODE_PDM); - #else - log_e("This chip does not support PDM"); - return 0; // ERR - #endif - } // Mode - esp_i2s::i2s_config_t i2s_config = { - .mode = i2s_mode, - .sample_rate = _sampleRate, - .bits_per_sample = (esp_i2s::i2s_bits_per_sample_t)_bitsPerSample, - .channel_format = esp_i2s::I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_COMM_FORMAT_STAND_I2S), - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, - .dma_buf_count = _I2S_DMA_BUFFER_COUNT, - .dma_buf_len = _i2s_dma_buffer_size, - .use_apll = false, - #warning The following values are new and need to be checked - .tx_desc_auto_clear = true, - .fixed_mclk = 0, - .mclk_multiple = esp_i2s::I2S_MCLK_MULTIPLE_128, - .bits_per_chan = esp_i2s::I2S_BITS_PER_CHAN_DEFAULT -#if SOC_I2S_SUPPORTS_TDM - ,.chan_mask = esp_i2s::I2S_CHANNEL_STEREO, - .total_chan = 2, - .left_align = false, - .big_edin = false, - .bit_order_msb = false, - .skip_msk = false -#endif // SOC_I2S_SUPPORTS_TDM - }; - - if(_driveClock == false){ - i2s_config.use_apll = true; - i2s_config.fixed_mclk = 512*_sampleRate; - } - - // Install and start i2s driver - while(ESP_OK != esp_i2s::i2s_driver_install((esp_i2s::i2s_port_t) _deviceIndex, &i2s_config, _I2S_EVENT_QUEUE_LENGTH, &_i2sEventQueue)){ - // increase buffer size - if(2*_i2s_dma_buffer_size <= 1024){ - log_w("WARNING i2s driver install failed.\nTrying to increase I2S DMA buffer size from %d to %d\n", _i2s_dma_buffer_size, 2*_i2s_dma_buffer_size); - setBufferSize(2*_i2s_dma_buffer_size); - }else if(_i2s_dma_buffer_size < 1024){ - log_w("WARNING i2s driver install failed.\nTrying to decrease I2S DMA buffer size from %d to 1024\n", _i2s_dma_buffer_size); - setBufferSize(1024); - }else{ // install failed with max buffer size - log_e("ERROR i2s driver install failed"); - return 0; // ERR - } - } //try installing with increasing size - - if(_mode == I2S_RIGHT_JUSTIFIED_MODE || _mode == I2S_LEFT_JUSTIFIED_MODE || _mode == PDM_MONO_MODE){ // mono/single channel - // Set the clock for MONO. Stereo is not supported yet. - if(ESP_OK != esp_i2s::i2s_set_clk((esp_i2s::i2s_port_t) _deviceIndex, _sampleRate, (esp_i2s::i2s_bits_per_sample_t)_bitsPerSample, esp_i2s::I2S_CHANNEL_MONO)){ - log_e("Setting the I2S Clock has failed!\n"); - return 0; // ERR - } - } // mono channel mode - -#if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_mode == ADC_DAC_MODE){ - esp_i2s::i2s_set_dac_mode(esp_i2s::I2S_DAC_CHANNEL_BOTH_EN); - esp_i2s::adc_unit_t adc_unit; - if(!_gpioToAdcUnit((gpio_num_t)_inSdPin, &adc_unit)){ - log_e("pin to adc unit conversion failed"); - return 0; // ERR - } - esp_i2s::adc_channel_t adc_channel; - if(!_gpioToAdcChannel((gpio_num_t)_inSdPin, &adc_channel)){ - log_e("pin to adc channel conversion failed"); - return 0; // ERR - } - if(ESP_OK != esp_i2s::i2s_set_adc_mode(adc_unit, (esp_i2s::adc1_channel_t)adc_channel)){ - log_e("i2s_set_adc_mode failed"); - return 0; // ERR - } - if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, NULL)){ - log_e("i2s_set_pin failed"); - return 0; // ERR - } - - if(adc_unit == esp_i2s::ADC_UNIT_1){ - esp_i2s::adc1_config_width(esp_i2s::ADC_WIDTH_BIT_12); - esp_i2s::adc1_config_channel_atten((esp_i2s::adc1_channel_t)adc_channel, esp_i2s::ADC_ATTEN_DB_11); - }else if(adc_unit == esp_i2s::ADC_UNIT_2){ - esp_i2s::adc2_config_channel_atten((esp_i2s::adc2_channel_t)adc_channel, esp_i2s::ADC_ATTEN_DB_11); - } - - esp_i2s::i2s_adc_enable((esp_i2s::i2s_port_t) _deviceIndex); - _driverInstalled = true; - }else // End of ADC/DAC mode -#endif // SOC_I2S_SUPPORTS_ADC_DAC - if(_mode == I2S_PHILIPS_MODE || _mode == I2S_RIGHT_JUSTIFIED_MODE || _mode == I2S_LEFT_JUSTIFIED_MODE || _mode == PDM_STEREO_MODE || _mode == PDM_MONO_MODE){ // if I2S mode - _driverInstalled = true; // IDF I2S driver must be installed before calling _applyPinSetting - if(!_applyPinSetting()){ - log_e("could not apply pin setting during driver install"); - _uninstallDriver(); - return 0; // ERR - } - } // if I2S _mode - return 1; // OK -} - -// Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate -int I2SClass::begin(int mode, int sampleRate, int bitsPerSample){ - _take_if_not_holding(); - // master mode (driving clock and frame select pins - output) - int ret = begin(mode, sampleRate, bitsPerSample, true); - _give_if_top_call(); - return ret; -} - -// Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate -int I2SClass::begin(int mode, int bitsPerSample){ - _take_if_not_holding(); - // slave mode (not driving clock and frame select pin - input) - int ret = begin(mode, 96000, bitsPerSample, false); - _give_if_top_call(); - return ret; -} - - -// Core function -int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock){ - _take_if_not_holding(); - if(_initialized){ - log_e("ERROR: Object already initialized! Call I2S.end() to disable"); - _give_if_top_call(); - return 0; // ERR - } - _driveClock = driveClock; - _mode = mode; - _sampleRate = (uint32_t)sampleRate; - _bitsPerSample = bitsPerSample; - - // There is work in progress on this library. - if(_bitsPerSample == 16 && _sampleRate > 16000 && driveClock){ - log_w("This sample rate is not officially supported - audio might be noisy.\nTry using sample rate below or equal to 16000"); - } - if(_bitsPerSample != 16){ - log_w("This bit-per-sample is not officially supported - audio quality might suffer.\nTry using 16bps, with sample rate below or equal 16000"); - } - if(_mode != I2S_PHILIPS_MODE){ - log_w("This mode is not officially supported - audio quality might suffer.\nAt the moment the only supported mode is I2S_PHILIPS_MODE"); - } - - if (_state != I2S_STATE_IDLE && _state != I2S_STATE_DUPLEX) { - log_e("Error: unexpected _state (%d)", _state); - _give_if_top_call(); - return 0; // ERR - } - - switch (mode) { - case I2S_PHILIPS_MODE: - case I2S_RIGHT_JUSTIFIED_MODE: - case I2S_LEFT_JUSTIFIED_MODE: - - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - case ADC_DAC_MODE: - #endif - - case PDM_STEREO_MODE: - case PDM_MONO_MODE: - break; - - default: // invalid mode - log_e("ERROR: unknown mode"); - _give_if_top_call(); - return 0; // ERR - } - - if(!_installDriver()){ - log_e("ERROR: failed to install driver"); - end(); - _give_if_top_call(); - return 0; // ERR - } - - _buffer_byte_size = _i2s_dma_buffer_size * (_bitsPerSample / 8) * _I2S_DMA_BUFFER_COUNT * 2; - _input_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); - _output_ring_buffer = xRingbufferCreate(_buffer_byte_size, RINGBUF_TYPE_BYTEBUF); - if(_input_ring_buffer == NULL || _output_ring_buffer == NULL){ - log_e("ERROR: could not create one or both internal buffers. Requested size = %d\n", _buffer_byte_size); - _give_if_top_call(); - return 0; // ERR - } - - if(!_createCallbackTask()){ - log_e("ERROR: failed to create callback task"); - end(); - _give_if_top_call(); - return 0; // ERR - } - _initialized = true; - _give_if_top_call(); - return 1; // OK -} - -int I2SClass::_applyPinSetting(){ - if(_driverInstalled){ - esp_i2s::i2s_pin_config_t pin_config = { - .mck_io_num = I2S_PIN_NO_CHANGE, - .bck_io_num = _sckPin, - .ws_io_num = _fsPin, - .data_out_num = I2S_PIN_NO_CHANGE, - .data_in_num = I2S_PIN_NO_CHANGE - }; - if (_state == I2S_STATE_DUPLEX){ // duplex - pin_config.data_out_num = _outSdPin; - pin_config.data_in_num = _inSdPin; - }else{ // simplex - if(_state == I2S_STATE_RECEIVER){ - pin_config.data_out_num = I2S_PIN_NO_CHANGE; - pin_config.data_in_num = _sdPin; - }else if(_state == I2S_STATE_TRANSMITTER){ - pin_config.data_out_num = _sdPin; - pin_config.data_in_num = I2S_PIN_NO_CHANGE; - }else{ - pin_config.data_out_num = I2S_PIN_NO_CHANGE; - pin_config.data_in_num = _sdPin; - } - } - if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, &pin_config)){ - log_e("i2s_set_pin failed; attempted settings: SCK=%d; FS=%d; DIN=%d; DOUT=%d", pin_config.bck_io_num, pin_config.ws_io_num, pin_config.data_in_num, pin_config.data_out_num); - return 0; // ERR - }else{ - return 1; // OK - } - } // if(_driverInstalled) - return 1; // OK -} - -void I2SClass::_setSckPin(int sckPin){ - _take_if_not_holding(); - if(sckPin >= 0){ - _sckPin = sckPin; - }else{ - _sckPin = PIN_I2S_SCK; - } - _give_if_top_call(); -} - -int I2SClass::setSckPin(int sckPin){ - _take_if_not_holding(); - _setSckPin(sckPin); - int ret = _applyPinSetting(); - _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -void I2SClass::_setFsPin(int fsPin){ - if(fsPin >= 0){ - _fsPin = fsPin; - }else{ - _fsPin = PIN_I2S_FS; - } -} - -int I2SClass::setFsPin(int fsPin){ - _take_if_not_holding(); - _setFsPin(fsPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -// shared data pin for simplex -void I2SClass::_setDataPin(int sdPin){ - if(sdPin >= 0){ - _sdPin = sdPin; - }else{ - _sdPin = PIN_I2S_SD; - } -} - -// shared data pin for simplex -int I2SClass::setDataPin(int sdPin){ - _take_if_not_holding(); - _setDataPin(sdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -void I2SClass::_setDataInPin(int inSdPin){ - if(inSdPin >= 0){ - _inSdPin = inSdPin; - }else{ - _inSdPin = PIN_I2S_SD_IN; - } -} - -int I2SClass::setDataInPin(int inSdPin){ - _take_if_not_holding(); - _setDataInPin(inSdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -void I2SClass::_setDataOutPin(int outSdPin){ - if(outSdPin >= 0){ - _outSdPin = outSdPin; - }else{ - _outSdPin = PIN_I2S_SD; - } -} - -int I2SClass::setDataOutPin(int outSdPin){ - _take_if_not_holding(); - _setDataOutPin(outSdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -int I2SClass::setAllPins(){ - _take_if_not_holding(); - int ret = setAllPins(PIN_I2S_SCK, PIN_I2S_FS, PIN_I2S_SD, PIN_I2S_SD_OUT, PIN_I2S_SD_IN); - _give_if_top_call(); - return ret; -} - -int I2SClass::setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin){ - _take_if_not_holding(); - _setSckPin(sckPin); - _setFsPin(fsPin); - _setDataPin(sdPin); - _setDataOutPin(outSdPin); - _setDataInPin(inSdPin); - int ret = _applyPinSetting(); - _give_if_top_call(); - return ret; -} - -int I2SClass::setDuplex(){ - _take_if_not_holding(); - _state = I2S_STATE_DUPLEX; - _give_if_top_call(); - return 1; -} - -int I2SClass::setSimplex(){ - _take_if_not_holding(); - _state = I2S_STATE_IDLE; - _give_if_top_call(); - return 1; -} - -int I2SClass::isDuplex(){ - _take_if_not_holding(); - int ret = (int)(_state == I2S_STATE_DUPLEX); - _give_if_top_call(); - return ret; -} - -int I2SClass::getSckPin(){ - _take_if_not_holding(); - int ret = _sckPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getFsPin(){ - _take_if_not_holding(); - int ret = _fsPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getDataPin(){ - _take_if_not_holding(); - int ret = _sdPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getDataInPin(){ - _take_if_not_holding(); - int ret = _inSdPin; - _give_if_top_call(); - return ret; -} - -int I2SClass::getDataOutPin(){ - _take_if_not_holding(); - int ret = _outSdPin; - _give_if_top_call(); - return ret; -} - -void I2SClass::_uninstallDriver(){ - if(_driverInstalled){ - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_mode == ADC_DAC_MODE){ - esp_i2s::i2s_adc_disable((esp_i2s::i2s_port_t) _deviceIndex); - } - #endif - esp_i2s::i2s_driver_uninstall((esp_i2s::i2s_port_t) _deviceIndex); - - if(_state != I2S_STATE_DUPLEX){ - _state = I2S_STATE_IDLE; - } - _driverInstalled = false; - } // if(_driverInstalled) -} - -void I2SClass::end(){ - _take_if_not_holding(); - if(xTaskGetCurrentTaskHandle() != _callbackTaskHandle){ - if(_callbackTaskHandle){ - vTaskDelete(_callbackTaskHandle); - _callbackTaskHandle = NULL; // prevent secondary termination to non-existing task - } - _uninstallDriver(); - _onTransmit = NULL; - _onReceive = NULL; - if(_input_ring_buffer != NULL){ - vRingbufferDelete(_input_ring_buffer); - _input_ring_buffer = NULL; - } - if(_output_ring_buffer != NULL){ - vRingbufferDelete(_output_ring_buffer); - _output_ring_buffer = NULL; - } - _initialized = false; - }else{ - log_w("WARNING: ending I2SClass from callback task not permitted, but attempted!"); - } - _give_if_top_call(); -} - -// Bytes available to read -int I2SClass::available(){ - _take_if_not_holding(); - int ret = 0; - if(_input_ring_buffer != NULL){ - ret = _buffer_byte_size - (int)xRingbufferGetCurFreeSize(_input_ring_buffer); - } - _give_if_top_call(); - return ret; -} - -union i2s_sample_t { - uint8_t b8; - int16_t b16; - int32_t b32; -}; - -int I2SClass::read(){ - _take_if_not_holding(); - i2s_sample_t sample; - sample.b32 = 0; - if(_initialized){ - read(&sample, _bitsPerSample / 8); - - if (_bitsPerSample == 32) { - _give_if_top_call(); - return sample.b32; - } else if (_bitsPerSample == 16) { - _give_if_top_call(); - return sample.b16; - } else if (_bitsPerSample == 8) { - _give_if_top_call(); - return sample.b8; - } else { - _give_if_top_call(); - return 0; // sample value - } - } // if(_initialized) - _give_if_top_call(); - return 0; // sample value -} - -int I2SClass::read(void* buffer, size_t size){ - _take_if_not_holding(); - size_t requested_size = size; - if(_initialized){ - if(!_enableReceiver()){ - _give_if_top_call(); - return 0; // There was an error switching to receiver - } // _enableReceiver succeeded ? - - size_t item_size = 0; - void *tmp_buffer; - if(_input_ring_buffer != NULL){ - if(_peek_buff_valid){ - memcpy(buffer, &_peek_buff, _bitsPerSample/8); - _peek_buff_valid = false; - requested_size -= _bitsPerSample/8; - } - tmp_buffer = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, pdMS_TO_TICKS(1000), requested_size); - if(tmp_buffer != NULL){ - memcpy(buffer, tmp_buffer, item_size); - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - if(_mode == ADC_DAC_MODE){ - for(size_t i = 0; i < item_size / 2; ++i){ - ((uint16_t*)buffer)[i] = ((uint16_t*)buffer)[i] & 0x0FFF; - } - } // ADC/DAC mode - #endif - vRingbufferReturnItem(_input_ring_buffer, tmp_buffer); - _give_if_top_call(); - return item_size; - }else{ - log_w("input buffer is empty - timed out"); - _give_if_top_call(); - return 0; // 0 Bytes read / ERR - } // tmp buffer not NULL ? - } // ring buffer not NULL ? - } // if(_initialized) - _give_if_top_call(); - return 0; // 0 Bytes read / ERR -} - -size_t I2SClass::write(uint8_t data){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - ret = write_blocking((int32_t*)&data, 1); - } - _give_if_top_call(); - return ret; -} - -size_t I2SClass::write(int32_t sample){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - ret = write_blocking(&sample, _bitsPerSample/8); - } - _give_if_top_call(); - return ret; -} - -size_t I2SClass::write(const uint8_t *buffer, size_t size){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - ret = write((const void*)buffer, size); - } - _give_if_top_call(); - return ret; -} - -size_t I2SClass::write(const void *buffer, size_t size){ - _take_if_not_holding(); - size_t ret = 0; - if(_initialized){ - //size_t ret = write_blocking(buffer, size); - ret = write_nonblocking(buffer, size); - } // if(_initialized) - _give_if_top_call(); - return ret; -} - -// blocking version of write -// This version of write will wait indefinitely to write requested samples -// into output buffer -size_t I2SClass::write_blocking(const void *buffer, size_t size){ - _take_if_not_holding(); - if(_initialized){ - if(!_enableTransmitter()){ - _give_if_top_call(); - return 0; // There was an error switching to transmitter - } // _enableTransmitter succeeded ? - - if(_output_ring_buffer != NULL){ - int ret = xRingbufferSend(_output_ring_buffer, buffer, size, portMAX_DELAY); - if(pdTRUE == ret){ - _give_if_top_call(); - return size; - }else{ - log_e("xRingbufferSend() with infinite wait returned with error"); - _give_if_top_call(); - return 0; - } // ring buffer send ok ? - } // ring buffer not NULL ? - } // if(_initialized) - return 0; - log_w("I2S not initialized"); - _give_if_top_call(); - return 0; -} - -// non-blocking version of write -// In case there is not enough space in buffer to write requested size -// this function will try to flush the buffer and write requested data with 0 time-out -size_t I2SClass::write_nonblocking(const void *buffer, size_t size){ - _take_if_not_holding(); - if(_initialized){ - if(_state != I2S_STATE_TRANSMITTER && _state != I2S_STATE_DUPLEX){ - if(!_enableTransmitter()){ - _give_if_top_call(); - return 0; // There was an error switching to transmitter - } - } - if(availableForWrite() < size){ - flush(); - } - if(_output_ring_buffer != NULL){ - if(pdTRUE == xRingbufferSend(_output_ring_buffer, buffer, size, 0)){ - _give_if_top_call(); - return size; - }else{ - log_w("I2S could not write all data into ring buffer!"); - _give_if_top_call(); - return 0; - } - } - } // if(_initialized) - return 0; - _give_if_top_call(); // this should not be needed -} - -/* - Read 1 sample from internal buffer and return it. - Repeated peeks will return the same sample until read is called. -*/ -int I2SClass::peek(){ - _take_if_not_holding(); - int ret = 0; - if(_initialized && _input_ring_buffer != NULL && !_peek_buff_valid){ - size_t item_size = 0; - void *item = NULL; - - item = xRingbufferReceiveUpTo(_input_ring_buffer, &item_size, 0, _bitsPerSample/8); // fetch 1 sample - if (item != NULL && item_size == _bitsPerSample/8){ - _peek_buff = *((int*)item); - vRingbufferReturnItem(_input_ring_buffer, item); - _peek_buff_valid = true; - } - - } // if(_initialized) - if(_peek_buff_valid){ - ret = _peek_buff; - } - _give_if_top_call(); - return ret; -} - -void I2SClass::flush(){ - _take_if_not_holding(); - if(_initialized){ - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; - size_t item_size = 0; - void *item = NULL; - if(_output_ring_buffer != NULL){ - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, 0, single_dma_buf); - if (item != NULL){ - _fix_and_write(item, item_size); - vRingbufferReturnItem(_output_ring_buffer, item); - } - } - } // if(_initialized) - _give_if_top_call(); -} - -// Bytes available to write -int I2SClass::availableForWrite(){ - _take_if_not_holding(); - int ret = 0; - if(_initialized){ - if(_output_ring_buffer != NULL){ - ret = (int)xRingbufferGetCurFreeSize(_output_ring_buffer); - } - } // if(_initialized) - _give_if_top_call(); - return ret; -} - -void I2SClass::onTransmit(void(*function)(void)){ - _take_if_not_holding(); - _onTransmit = function; - _give_if_top_call(); -} - -void I2SClass::onReceive(void(*function)(void)){ - _take_if_not_holding(); - _onReceive = function; - _give_if_top_call(); -} - -int I2SClass::setBufferSize(int bufferSize){ - _take_if_not_holding(); - int ret = 0; - if(bufferSize >= 8 && bufferSize <= 1024){ - _i2s_dma_buffer_size = bufferSize; - }else{ - log_e("setBufferSize: wrong input! Buffer size must be between 8 and 1024. Requested %d", bufferSize); - _give_if_top_call(); - return 0; // ERR - } // check requested buffer size - - if(_initialized){ - _uninstallDriver(); - ret = _installDriver(); - _give_if_top_call(); - return ret; - }else{ // check requested buffer size - _give_if_top_call(); - return 1; // It's ok to change buffer size for uninitialized driver - new size will be used on begin() - } // if(_initialized) - _give_if_top_call(); - return 0; // ERR -} - -int I2SClass::getBufferSize(){ - _take_if_not_holding(); - int ret = _i2s_dma_buffer_size; - _give_if_top_call(); - return ret; -} - -int I2SClass::_enableTransmitter(){ - if(_state != I2S_STATE_DUPLEX && _state != I2S_STATE_TRANSMITTER){ - _state = I2S_STATE_TRANSMITTER; - return _applyPinSetting(); - } - return 1; // Ok -} - -int I2SClass::_enableReceiver(){ - if(_state != I2S_STATE_DUPLEX && _state != I2S_STATE_RECEIVER){ - _state = I2S_STATE_RECEIVER; - return _applyPinSetting(); - } - return 1; // Ok -} - -void I2SClass::_tx_done_routine(uint8_t* prev_item){ - static bool prev_item_valid = false; - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8)*2; // *2 for stereo - it has double number of samples for 2 channels - static size_t item_size = 0; - static size_t prev_item_size = 0; - static void *item = NULL; - static int prev_item_offset = 0; - static size_t bytes_written = 0; - - if(prev_item_valid){ // use item from previous round - _fix_and_write(prev_item+prev_item_offset, prev_item_size, &bytes_written); - if(prev_item_size == bytes_written){ - prev_item_valid = false; - } // write size check - prev_item_offset = bytes_written; - prev_item_size -= bytes_written; - } // prev_item_valid - - if(_output_ring_buffer != NULL && (_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= single_dma_buf)){ // fill up the I2S DMA buffer - bytes_written = 0; - item_size = 0; - if(_buffer_byte_size - xRingbufferGetCurFreeSize(_output_ring_buffer) >= _i2s_dma_buffer_size*(_bitsPerSample/8)){ // don't read from almost empty buffer - item = xRingbufferReceiveUpTo(_output_ring_buffer, &item_size, pdMS_TO_TICKS(0), single_dma_buf); - if (item != NULL){ - _fix_and_write(item, item_size, &bytes_written); - if(item_size != bytes_written){ // save item that was not written correctly for later - memcpy(prev_item, (void*)&((uint8_t*)item)[bytes_written], item_size-bytes_written); - prev_item_size = item_size - bytes_written; - prev_item_offset = 0; - prev_item_valid = true; - } // save item that was not written correctly for later - vRingbufferReturnItem(_output_ring_buffer, item); - } // Check received item - } // don't read from almost empty buffer - } // fill up the I2S DMA buffer - if(_onTransmit){ - _onTransmit(); - } // user callback -} - -void I2SClass::_rx_done_routine(){ - size_t bytes_read = 0; - const size_t single_dma_buf = _i2s_dma_buffer_size*(_bitsPerSample/8); - - if(_input_ring_buffer != NULL){ - uint8_t *_inputBuffer = (uint8_t*)malloc(_i2s_dma_buffer_size*4); - size_t avail = xRingbufferGetCurFreeSize(_input_ring_buffer); - if(avail > 0){ - esp_err_t ret = esp_i2s::i2s_read((esp_i2s::i2s_port_t) _deviceIndex, _inputBuffer, avail <= single_dma_buf ? avail : single_dma_buf, (size_t*) &bytes_read, 0); - if(ret != ESP_OK){ - log_w("i2s_read returned with error %d", ret); - } - _post_read_data_fix(_inputBuffer, &bytes_read); - } - - if(bytes_read > 0){ // when read more than 0, then send to ring buffer - if(pdTRUE != xRingbufferSend(_input_ring_buffer, _inputBuffer, bytes_read, 0)){ - log_w("I2S failed to send item from DMA to internal buffer\n"); - } // xRingbufferSendComplete - } // if(bytes_read > 0) - free(_inputBuffer); - if (_onReceive && avail < _buffer_byte_size){ // when user callback is registered && and there is some data in ring buffer to read - _onReceive(); - } // user callback - } -} - -void I2SClass::_onTransferComplete(){ - uint8_t prev_item[_i2s_dma_buffer_size*4]; - esp_i2s::i2s_event_t i2s_event; - - while(true){ - xQueueReceive(_i2sEventQueue, &i2s_event, portMAX_DELAY); - if(i2s_event.type == esp_i2s::I2S_EVENT_TX_DONE){ - _tx_done_routine(prev_item); - }else if(i2s_event.type == esp_i2s::I2S_EVENT_RX_DONE){ - _rx_done_routine(); - } // RX Done - } // infinite loop -} - -void I2SClass::onDmaTransferComplete(void*){ - I2S._onTransferComplete(); -} - -void I2SClass::_take_if_not_holding(){ - TaskHandle_t mutex_holder = xSemaphoreGetMutexHolder(_i2s_general_mutex); - if(mutex_holder != NULL && mutex_holder == xTaskGetCurrentTaskHandle()){ - ++_nesting_counter; - return; // we are already holding this mutex - no need to take it - } - - // we are not holding the mutex - wait for it and take it - if(xSemaphoreTake(_i2s_general_mutex, portMAX_DELAY) != pdTRUE ){ - log_e("I2S internal mutex take returned with error"); - } - //_give_if_top_call(); // call after this function -} - -void I2SClass::_give_if_top_call(){ - if(_nesting_counter){ - --_nesting_counter; - }else{ - if(xSemaphoreGive(_i2s_general_mutex) != pdTRUE){ - log_e("I2S internal mutex give error"); - } - } -} - - -// Fixes data in-situ received from esp i2s driver. After fixing they reflect what was on the bus. -// input - bytes as received from i2s_read - this serves as input and output buffer -// size - number of bytes (this may be changed during operation) -void I2SClass::_post_read_data_fix(void *input, size_t *size){ - ulong dst_ptr = 0; - switch(_bitsPerSample){ - case 8: - for(int i = 0; i < *size; i+=4){ - ((uint8_t*)input)[dst_ptr++] = ((uint8_t*)input)[i+3]; - ((uint8_t*)input)[dst_ptr++] = ((uint8_t*)input)[i+1]; - } - *size /= 2; - break; - case 16: - uint16_t tmp; - for(int i = 0; i < *size/2; i+=2){ - tmp = ((uint16_t*)input)[i]; - ((uint16_t*)input)[dst_ptr++] = ((uint16_t*)input)[i+1]; - ((uint16_t*)input)[dst_ptr++] = tmp; - } - break; - default: ; // Do nothing - } // switch -} - -// Prepares data and writes them to IDF i2s driver. -// This counters possible bug in ESP IDF I2S driver -// output - bytes to be sent -// size - number of bytes in original buffer -// bytes_written - number of bytes used from original buffer -// actual_bytes_written - number of bytes written by i2s_write after fix -void I2SClass::_fix_and_write(void *output, size_t size, size_t *bytes_written, size_t *actual_bytes_written){ - ulong src_ptr = 0; - uint8_t* buff = NULL; - size_t buff_size = size; - switch(_bitsPerSample){ - case 8: - buff_size = size *2; - buff = (uint8_t*)calloc(buff_size, sizeof(uint8_t)); - if(buff == NULL){ - log_e("callock error"); - if(bytes_written != NULL){ *bytes_written = 0; } - return; - } - for(int i = 0; i < buff_size ; i+=4){ - ((uint8_t*)buff)[i+3] = (uint16_t)((uint8_t*)output)[src_ptr++]; - ((uint8_t*)buff)[i+1] = (uint16_t)((uint8_t*)output)[src_ptr++]; - } - break; - case 16: - buff = (uint8_t*)malloc(buff_size); - if(buff == NULL){ - log_e("malloc error"); - if(bytes_written != NULL){ *bytes_written = 0; } - return; - } - for(int i = 0; i < size/2; i += 2 ){ - ((uint16_t*)buff)[i] = ((uint16_t*)output)[i+1]; // [1] <- [0] - ((uint16_t*)buff)[i+1] = ((uint16_t*)output)[i]; // [0] <- [1] - } - break; - case 24: - buff = (uint8_t*)output; - break; - case 32: - buff = (uint8_t*)output; - break; - default: ; // Do nothing - } // switch - - size_t _bytes_written; - esp_err_t ret = esp_i2s::i2s_write((esp_i2s::i2s_port_t) _deviceIndex, buff, buff_size, &_bytes_written, 0); // fixed - if(ret != ESP_OK){ - log_e("Error: writing data to i2s - function returned with err code %d", ret); - } - if(ret == ESP_OK && buff_size != _bytes_written){ - log_w("Warning: writing data to i2s - written %d B instead of requested %d B", _bytes_written, buff_size); - } - // free if the buffer was actually allocated - if(_bitsPerSample == 8 || _bitsPerSample == 16){ - free(buff); - } - if(bytes_written != NULL){ - *bytes_written = _bitsPerSample == 8 ? _bytes_written/2 : _bytes_written; - } - if(actual_bytes_written != NULL){ - *actual_bytes_written = _bytes_written; - } -} - - -#if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) -int I2SClass::_gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit){ - switch(gpio_num){ -#if CONFIG_IDF_TARGET_ESP32 - // ADC 1 - case GPIO_NUM_36: - case GPIO_NUM_37: - case GPIO_NUM_38: - case GPIO_NUM_39: - case GPIO_NUM_32: - case GPIO_NUM_33: - case GPIO_NUM_34: - case GPIO_NUM_35: - *adc_unit = esp_i2s::ADC_UNIT_1; - return 1; // OK - - // ADC 2 - case GPIO_NUM_0: - log_w("GPIO 0 for ADC should not be used for dev boards due to external auto program circuits."); - case GPIO_NUM_4: - case GPIO_NUM_2: - case GPIO_NUM_15: - case GPIO_NUM_13: - case GPIO_NUM_12: - case GPIO_NUM_14: - case GPIO_NUM_27: - case GPIO_NUM_25: - case GPIO_NUM_26: - *adc_unit = esp_i2s::ADC_UNIT_2; - return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) - case GPIO_NUM_1: - case GPIO_NUM_2: - case GPIO_NUM_3: - case GPIO_NUM_4: - case GPIO_NUM_5: - case GPIO_NUM_6: - case GPIO_NUM_7: - case GPIO_NUM_8: - case GPIO_NUM_9: - case GPIO_NUM_10: - *adc_unit = esp_i2s::ADC_UNIT_1; - return 1; // OK -#endif - -#if CONFIG_IDF_TARGET_ESP32S2 - case GPIO_NUM_11: - case GPIO_NUM_12: - case GPIO_NUM_13: - case GPIO_NUM_14: - case GPIO_NUM_15: - case GPIO_NUM_16: - case GPIO_NUM_17: - case GPIO_NUM_18: - case GPIO_NUM_19: - case GPIO_NUM_20: - *adc_unit = esp_i2s::ADC_UNIT_2; - return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2) - case GPIO_NUM_0: - case GPIO_NUM_1: - case GPIO_NUM_2: - case GPIO_NUM_3: - case GPIO_NUM_4: - *adc_unit = esp_i2s::ADC_UNIT_1; - return 1; // OK - case GPIO_NUM_5: - *adc_unit = esp_i2s::ADC_UNIT_2; - return 1; // OK -#endif - default: - log_e("GPIO %d not usable for ADC!", gpio_num); - log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); - return 0; // ERR - } -} - -int I2SClass::_gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc_channel){ - switch(gpio_num){ -#if CONFIG_IDF_TARGET_ESP32 - // ADC 1 - case GPIO_NUM_36: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_37: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_38: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_39: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_32: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_33: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_34: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_35: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - - // ADC 2 - case GPIO_NUM_0: - log_w("GPIO 0 for ADC should not be used for dev boards due to external auto program circuits."); - *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_4: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_2: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_15: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_13: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_12: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_14: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_27: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - case GPIO_NUM_25: *adc_channel = esp_i2s::ADC_CHANNEL_8; return 1; // OK - case GPIO_NUM_26: *adc_channel = esp_i2s::ADC_CHANNEL_9; return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3) - case GPIO_NUM_1: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_2: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_3: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_4: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_5: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_6: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_7: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_8: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - case GPIO_NUM_9: *adc_channel = esp_i2s::ADC_CHANNEL_8; return 1; // OK - case GPIO_NUM_10: *adc_channel = esp_i2s::ADC_CHANNEL_9; return 1; // OK -#endif - -#if CONFIG_IDF_TARGET_ESP32S2 - case GPIO_NUM_11: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_12: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_13: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_14: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_15: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_16: *adc_channel = esp_i2s::ADC_CHANNEL_5; return 1; // OK - case GPIO_NUM_17: *adc_channel = esp_i2s::ADC_CHANNEL_6; return 1; // OK - case GPIO_NUM_18: *adc_channel = esp_i2s::ADC_CHANNEL_7; return 1; // OK - case GPIO_NUM_19: *adc_channel = esp_i2s::ADC_CHANNEL_8; return 1; // OK - case GPIO_NUM_20: *adc_channel = esp_i2s::ADC_CHANNEL_9; return 1; // OK -#endif - -#if (CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2) - case GPIO_NUM_0: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK - case GPIO_NUM_1: *adc_channel = esp_i2s::ADC_CHANNEL_1; return 1; // OK - case GPIO_NUM_2: *adc_channel = esp_i2s::ADC_CHANNEL_2; return 1; // OK - case GPIO_NUM_3: *adc_channel = esp_i2s::ADC_CHANNEL_3; return 1; // OK - case GPIO_NUM_4: *adc_channel = esp_i2s::ADC_CHANNEL_4; return 1; // OK - case GPIO_NUM_5: *adc_channel = esp_i2s::ADC_CHANNEL_0; return 1; // OK -#endif - default: - log_e("GPIO %d not usable for ADC!", gpio_num); - log_i("Please refer to documentation https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html"); - return 0; // ERR - } -} -#endif // SOC_I2S_SUPPORTS_ADC_DAC - -#if I2S_INTERFACES_COUNT > 0 - I2SClass I2S(I2S_DEVICE, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS); // default - half duplex -#endif - -#if I2S_INTERFACES_COUNT > 1 - // TODO set default pins for second module - //I2SClass I2S1(I2S_DEVICE+1, I2S_CLOCK_GENERATOR, PIN_I2S_SD, PIN_I2S_SCK, PIN_I2S_FS); // default - half duplex -#endif diff --git a/libraries/I2S/src/I2S.h b/libraries/I2S/src/I2S.h deleted file mode 100644 index 623fa8917b4..00000000000 --- a/libraries/I2S/src/I2S.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - Copyright (c) 2016 Arduino LLC. All right reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _I2S_H_INCLUDED -#define _I2S_H_INCLUDED - -#include -#include "freertos/ringbuf.h" - -namespace esp_i2s { - #include "driver/i2s.h" // ESP specific i2s driver -} - -// Default pins -#ifndef PIN_I2S_SCK - #define PIN_I2S_SCK 14 -#endif - -#ifndef PIN_I2S_FS - #if CONFIG_IDF_TARGET_ESP32S2 - #define PIN_I2S_FS 27 - #else - #define PIN_I2S_FS 25 - #endif -#endif - -#ifndef PIN_I2S_SD - #define PIN_I2S_SD 26 -#endif - -#ifndef PIN_I2S_SD_OUT - #define PIN_I2S_SD_OUT 26 -#endif - -#ifndef PIN_I2S_SD_IN - #define PIN_I2S_SD_IN 35 // Pin 35 is only input! -#endif - -typedef enum { - I2S_PHILIPS_MODE, - I2S_RIGHT_JUSTIFIED_MODE, - I2S_LEFT_JUSTIFIED_MODE, - ADC_DAC_MODE, - PDM_STEREO_MODE, - PDM_MONO_MODE -} i2s_mode_t; - -class I2SClass : public Stream -{ -public: - // The device index and pins must map to the "COM" pads in Table 6-1 of the datasheet - I2SClass(uint8_t deviceIndex, uint8_t clockGenerator, uint8_t sdPin, uint8_t sckPin, uint8_t fsPin); - - // Init in MASTER mode: the SCK and FS pins are driven as outputs using the sample rate - int begin(int mode, int sampleRate, int bitsPerSample); - - // Init in SLAVE mode: the SCK and FS pins are inputs, other side controls sample rate - int begin(int mode, int bitsPerSample); - - // change pin setup and mode (default is Half Duplex) - // Can be called only on initialized object (after begin) - int setSckPin(int sckPin); - int setFsPin(int fsPin); - int setDataPin(int sdPin); // shared data pin for simplex - int setDataOutPin(int outSdPin); - int setDataInPin(int inSdPin); - - int setAllPins(); - int setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin); - - int getSckPin(); - int getFsPin(); - int getDataPin(); - int getDataOutPin(); - int getDataInPin(); - - int setDuplex(); - int setSimplex(); - int isDuplex(); - - void end(); - - // from Stream - virtual int available(); - virtual int read(); - virtual int peek(); - virtual void flush(); - - // from Print - virtual size_t write(uint8_t); - virtual size_t write(const uint8_t *buffer, size_t size); - - virtual int availableForWrite(); - - int read(void* buffer, size_t size); - - //size_t write(int); - size_t write(int32_t); - size_t write(const void *buffer, size_t size); - size_t write_blocking(const void *buffer, size_t size); - size_t write_nonblocking(const void *buffer, size_t size); - - void onTransmit(void(*)(void)); - void onReceive(void(*)(void)); - - int setBufferSize(int bufferSize); - int getBufferSize(); -private: - #if (SOC_I2S_SUPPORTS_ADC && SOC_I2S_SUPPORTS_DAC) - int _gpioToAdcUnit(gpio_num_t gpio_num, esp_i2s::adc_unit_t* adc_unit); - int _gpioToAdcChannel(gpio_num_t gpio_num, esp_i2s::adc_channel_t* adc_channel); - #endif - int begin(int mode, int sampleRate, int bitsPerSample, bool driveClock); - - int _enableTransmitter(); - int _enableReceiver(); - void _onTransferComplete(); - - int _createCallbackTask(); - - static void onDmaTransferComplete(void*); - int _installDriver(); - void _uninstallDriver(); - void _setSckPin(int sckPin); - void _setFsPin(int fsPin); - void _setDataPin(int sdPin); - void _setDataOutPin(int outSdPin); - void _setDataInPin(int inSdPin); - int _applyPinSetting(); - -private: - typedef enum { - I2S_STATE_IDLE, - I2S_STATE_TRANSMITTER, - I2S_STATE_RECEIVER, - I2S_STATE_DUPLEX - } i2s_state_t; - - int _deviceIndex; - int _sdPin; - int _inSdPin; - int _outSdPin; - int _sckPin; - int _fsPin; - - i2s_state_t _state; - int _bitsPerSample; - uint32_t _sampleRate; - int _mode; - - uint16_t _buffer_byte_size; - - bool _driverInstalled; // Is IDF I2S driver installed? - bool _initialized; // Is everything initialized (callback task, I2S driver, ring buffers)? - TaskHandle_t _callbackTaskHandle; - QueueHandle_t _i2sEventQueue; - SemaphoreHandle_t _i2s_general_mutex; - RingbufHandle_t _input_ring_buffer; - RingbufHandle_t _output_ring_buffer; - int _i2s_dma_buffer_size; - bool _driveClock; - uint32_t _peek_buff; - bool _peek_buff_valid; - - void _tx_done_routine(uint8_t* prev_item); - void _rx_done_routine(); - - uint16_t _nesting_counter; - void _take_if_not_holding(); - void _give_if_top_call(); - void _post_read_data_fix(void *input, size_t *size); - void _fix_and_write(void *output, size_t size, size_t *bytes_written = NULL, size_t *actual_bytes_written = NULL); - - void (*_onTransmit)(void); - void (*_onReceive)(void); -}; - -extern I2SClass I2S; - -#endif diff --git a/libraries/LittleFS/src/LittleFS.cpp b/libraries/LittleFS/src/LittleFS.cpp index 608e0ba2b25..f23a3cfe611 100644 --- a/libraries/LittleFS/src/LittleFS.cpp +++ b/libraries/LittleFS/src/LittleFS.cpp @@ -82,7 +82,8 @@ bool LittleFSFS::begin(bool formatOnFail, const char * basePath, uint8_t maxOpen .base_path = basePath, .partition_label = partitionLabel_, .format_if_mount_failed = false, - .dont_mount = false + .dont_mount = false, + .grow_on_mount = true }; esp_err_t err = esp_vfs_littlefs_register(&conf);