diff --git a/platformio.ini b/platformio.ini index 8a060ba0fe..985612a888 100644 --- a/platformio.ini +++ b/platformio.ini @@ -108,7 +108,7 @@ ldscript_2m512k = eagle.flash.2m512.ld ldscript_2m1m = eagle.flash.2m1m.ld ldscript_4m1m = eagle.flash.4m1m.ld -default_usermods = audioreactive ;; audioreactive compiles, but crashes on classic esp32 (driver conflict related to legacy ADC) +default_usermods = audioreactive ;; default_usermods = wizlights animartrix ;; for testing default_usermodsV4 = audioreactive ;; audioreactive is compatible with V4 builds @@ -420,7 +420,7 @@ monitor_filters = esp32_exception_decoder ;; NOTE: ESP32-C5 requires pioarduino platform (Tasmota framework doesn't include C5 Arduino libs yet) platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip platform_packages = -custom_usermods = ;; ${common.default_usermods} ;; ToDO: audiorective compiles, but not tested on C5 yet +custom_usermods = ${common.default_usermods} ;; ToDO: audiorective compiles, but not tested on C5 yet build_unflags = ${esp32_idf_V5.build_unflags} -D WLED_ENABLE_DMX_INPUT ;; esp_dmx library doesn't support C5 UART registers yet build_flags = -g @@ -447,7 +447,7 @@ platform_packages = ${esp32_idf_V5.platform_packages} ;;platform_packages = ;; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1 ;; framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip -custom_usermods = ;; ${common.default_usermods} ;; ToDO: audiorective compiles, but not tested on C6 yet +custom_usermods = ${common.default_usermods} build_unflags = ${esp32_idf_V5.build_unflags} -D WLED_ENABLE_DMX_INPUT ;; esp_dmx library doesn't support C6 UART registers yet @@ -475,7 +475,7 @@ lib_ignore = ${esp32_idf_V5.lib_ignore} ;; Using latest tasmota framework - older frameworks only have minimal support for P4 platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip platform_packages = -custom_usermods = ;; ${common.default_usermods} ;; ToDO: audiorective compiles, but not tested on P4 yet +custom_usermods = ${common.default_usermods} ;; ToDO: audiorective compiles, but not tested on P4 yet build_unflags = ${esp32_idf_V5.build_unflags} -D WLED_ENABLE_DMX_INPUT ;; library not compatible with -P4 -D WLED_ENABLE_DMX ;; DMX serial out needs adaptations for P4 compatibility @@ -764,7 +764,7 @@ board = esp32dev platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} build_unflags = ${esp32_idf_V5.build_unflags} -custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds +custom_usermods = ${common.default_usermods} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 lib_deps = ${esp32_idf_V5.lib_deps} @@ -778,7 +778,7 @@ extends = env:esp32dev ;;build_type = debug ;; disabled - leads to stack smashing in ArduinoJSON "Unhandled debug exception: Stack canary watchpoint triggered (loopTask)" monitor_filters = esp32_exception_decoder upload_speed = 921600 -custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds +custom_usermods = ${common.default_usermods} build_unflags = ${esp32_idf_V5.build_unflags} -D WLED_RELEASE_NAME=\"ESP32\" ;; -DCORE_DEBUG_LEVEL=0 @@ -797,7 +797,7 @@ lib_ignore = ${esp32_idf_V5.lib_ignore} board = esp32dev platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} -custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds +custom_usermods = ${common.default_usermods} build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 @@ -813,7 +813,7 @@ board_upload.maximum_size = 8388608 board = esp32dev platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} -custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds +custom_usermods = ${common.default_usermods} build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 @@ -848,7 +848,7 @@ board = esp32-poe platform = ${esp32_idf_V5.platform} platform_packages = ${esp32_idf_V5.platform_packages} upload_speed = 921600 -custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds +custom_usermods = ${common.default_usermods} build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 -D SR_DMTYPE=-1 -D AUDIOPIN=-1 -D I2S_SDPIN=-1 -D I2S_WSPIN=-1 -D I2S_CKPIN=-1 -D MCLK_PIN=-1 ;; force AR to not allocate any PINs at startup @@ -867,7 +867,7 @@ board = ttgo-t7-v14-mini32 board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} -custom_usermods = ${common.default_usermods} ;; warning: audioreactive analog mic support had to be removed in V5 builds +custom_usermods = ${common.default_usermods} build_unflags = ${esp32_idf_V5.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V5.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index c257b4e59c..9fde65493b 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -3,49 +3,16 @@ #ifdef ARDUINO_ARCH_ESP32 -#include -#include +#if ESP_IDF_VERSION_MAJOR >= 5 +#include // new I2S driver - IDF v5.x and v6.x (legacy driver removed in IDF v6.0) +#else +#include // legacy I2S driver - IDF v4.x only +#if defined(CONFIG_IDF_TARGET_ESP32) +#include // legacy ADC driver causes bootloops in V5 +#endif #endif -// AI: below section was generated by an AI -#if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR >= 5) -/* - * Bootloop workaround for classic ESP32 on esp-idf 5.x (WLED V5): - * AR still uses the legacy I2S driver (driver/i2s.h) which pulls in the legacy ADC driver. - * This causes a "abort" on boot as IDF V5 does not allow the legacy ADC driver to be used - * if any other function (i.e. Arduino's analogRead()) pulls in the new driver. - * - * The no-op stubs below satisfy exactly the symbols i2s_legacy.c.obj needs, so the real - * adc_i2s_deprecated.c.obj / adc_legacy.c.obj are never linked. This is safe because on IDF >= 5 - * the analog built-in-ADC mic class (I2SAdcSource) is compiled out anyway (see audio_source.h), - * so these paths are never executed; digital I2S/PDM mics don't use them. - * Delete this block once the usermod is ported to the new I2S driver (driver/i2s_std.h). - */ -#include - -// Stub: would configure the (removed) I2S built-in ADC mode. Never called for digital mics. -extern "C" esp_err_t adc_i2s_mode_init(int adcUnit, int channel) { - (void)adcUnit; (void)channel; - return ESP_ERR_NOT_SUPPORTED; -} - -// Stub: would select the ADC data source for I2S DMA. Never called for digital mics. -extern "C" esp_err_t adc_set_i2s_data_source(int source) { - (void)source; - return ESP_ERR_NOT_SUPPORTED; -} - -// Stub: would acquire ADC1 for DMA (built-in ADC mode only). Report failure if ever called. -extern "C" esp_err_t adc1_dma_mode_acquire(void) { - return ESP_ERR_NOT_SUPPORTED; -} - -// Stub: would release the legacy ADC1 lock. No lock is ever taken, so nothing to do. -extern "C" esp_err_t adc1_lock_release(void) { - return ESP_OK; -} #endif -// AI: end #if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG)) #include @@ -76,7 +43,7 @@ extern "C" esp_err_t adc1_lock_release(void) { */ //#define UM_AUDIOREACTIVE_USE_ESPDSP_FFT // default on S2 and C3 -//#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // use integer FFT if using ESP-IDF DSP library, always used on S2 and C3 (UM_AUDIOREACTIVE_USE_ARDUINO_FFT takes priority) +//#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // use integer FFT if using ESP-IDF DSP library, always used on chips with no FPU (UM_AUDIOREACTIVE_USE_ARDUINO_FFT takes priority) //#define UM_AUDIOREACTIVE_USE_ARDUINO_FFT // default on ESP32 and S3 #if !defined(FFTTASK_PRIORITY) @@ -176,8 +143,8 @@ static uint8_t binNum = 8; // Used to select the bin for FFT based bea #else #include "dsps_wind_flat_top.h" #endif -#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) -#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // always use integer FFT on ESP32-S2 and ESP32-C3 +#if !SOC_CPU_HAS_FPU +#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // always use integer FFT on MCU's with no hardware float unit #endif #endif // UM_AUDIOREACTIVE_USE_ARDUINO_FFT @@ -201,7 +168,6 @@ static FFTsampleType* windowFFT = nullptr; // use audio source class (ESP32 specific) #include "audio_source.h" -constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !) constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples) // globals @@ -915,7 +881,7 @@ class AudioReactive : public Usermod { static const char _dynamics[]; static const char _frequency[]; static const char _inputLvl[]; -#if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) // legacy ADC driver is not available any more in esp-idf V5.x.y +#if defined(AR_ANALOG_MIC_SUPPORTED) // analog mic: legacy ADC-over-I2S on IDF v4, ADC continuous driver on IDF v5/v6 static const char _analogmic[]; #endif static const char _digitalmic[]; @@ -1414,12 +1380,14 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 - // Reset I2S peripheral for good measure +#if ESP_IDF_VERSION_MAJOR < 5 + // Reset I2S peripheral for good measure (legacy driver only - the new driver cleans up after itself) i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed - #if !defined(CONFIG_IDF_TARGET_ESP32C3) && (ESP_IDF_VERSION_MAJOR < 5) + #if !defined(CONFIG_IDF_TARGET_ESP32C3) delay(100); periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3, neither on esp-idf V5 #endif +#endif delay(100); // Give that poor microphone some time to setup. useBandPassFilter = false; // filter cuts lowest and highest frequency bands from FFT result (use on very noisy mic inputs) useMicFilter = true; // filter fixes aliasing to base & highest frequency bands and reduces noise floor (recommended for all mic inputs) @@ -1430,7 +1398,7 @@ class AudioReactive : public Usermod { switch (dmType) { // stub cases for not-yet-supported I2S modes on other ESP32 chips - #if !defined(CONFIG_IDF_TARGET_ESP32) || (ESP_IDF_VERSION_MAJOR > 4) // legacy ADC driver is not available any more in esp-idf V5.x.y + #if !defined(AR_ANALOG_MIC_SUPPORTED) // no analog mic support on this MCU / IDF version case 0: //ADC analog #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3) // PDM is only supported on S3 and classic esp32 @@ -1478,11 +1446,15 @@ class AudioReactive : public Usermod { if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; - #if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) // legacy ADC driver is not available any more in esp-idf V5.x.y - // ADC over I2S is only possible on "classic" ESP32 + #if defined(AR_ANALOG_MIC_SUPPORTED) + // analog microphone input - ADC over I2S on IDF v4 (classic ESP32), ADC continuous driver on IDF v5/v6 case 0: DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); + #if defined(AR_ANALOG_MIC_I2S_ADC) audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); + #else + audioSource = new AdcContinuousSource(SAMPLE_RATE, BLOCK_SIZE); // note: ADC driver uses 5 DMA buffers: INTERNAL_BUF_NUM in adc_continuous.c, those are set to BLOCK_SIZE bytes + #endif delay(100); useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog if (audioSource) audioSource->initialize(audioPin); @@ -2057,7 +2029,7 @@ class AudioReactive : public Usermod { top[FPSTR(_addPalettes)] = addPalettes; #ifdef ARDUINO_ARCH_ESP32 - #if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) // legacy ADC driver is not available any more in esp-idf V5.x.y + #if defined(AR_ANALOG_MIC_SUPPORTED) // analog mic: legacy ADC-over-I2S on IDF v4, ADC continuous driver on IDF v5/v6 JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); amic["pin"] = audioPin; #endif @@ -2123,14 +2095,14 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes); #ifdef ARDUINO_ARCH_ESP32 - #if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) // legacy ADC driver is not available any more in esp-idf V5.x.y + #if defined(AR_ANALOG_MIC_SUPPORTED) // analog mic: legacy ADC-over-I2S on IDF v4, ADC continuous driver on IDF v5/v6 configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); #else audioPin = -1; // MCU does not support analog mic #endif configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType); - #if !defined(CONFIG_IDF_TARGET_ESP32) || (ESP_IDF_VERSION_MAJOR > 4) // legacy ADC driver is not available any more in esp-idf V5.x.y + #if !defined(AR_ANALOG_MIC_SUPPORTED) // no analog mic support on this MCU / IDF version if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog #endif #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3) // PDM is only supported on S3 and classic esp32 @@ -2179,7 +2151,7 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 uiScript.print(F("uxp=ux+':digitalmic:pin[]';")); // uxp = shortcut for AudioReactive:digitalmic:pin[] uiScript.print(F("dd=addDropdown(ux,'digitalmic:type');")); - #if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) // legacy ADC driver is not available any more in esp-idf V5.x.y + #if defined(AR_ANALOG_MIC_SUPPORTED) // analog mic: legacy ADC-over-I2S on IDF v4, ADC continuous driver on IDF v5/v6 uiScript.print(F("addOption(dd,'Generic Analog',0);")); #endif uiScript.print(F("addOption(dd,'Generic I2S',1);")); @@ -2346,7 +2318,7 @@ const char AudioReactive::_config[] PROGMEM = "config"; const char AudioReactive::_dynamics[] PROGMEM = "dynamics"; const char AudioReactive::_frequency[] PROGMEM = "frequency"; const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel"; -#if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) // legacy ADC driver is not available any more in esp-idf V5.x.y +#if defined(AR_ANALOG_MIC_SUPPORTED) // analog mic: legacy ADC-over-I2S on IDF v4, ADC continuous driver on IDF v5/v6 const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; #endif const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index c2e67c2be5..4e9ef088b9 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -1,6 +1,18 @@ #pragma once #ifdef ARDUINO_ARCH_ESP32 #include "wled.h" + +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#include +#if defined(SOC_ADC_SUPPORTED) +#include // analog mic via ADC continuous mode (replaces legacy ADC-over-I2S) +#if defined(CONFIG_IDF_TARGET_ESP32) +#include // needed for SPH0645 timing workaround (classic ESP32 only) +#endif +#endif + +#else #include #if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) #include // legacy ADC driver causes bootloops in V5 @@ -11,20 +23,41 @@ #include // legacy ADC driver is not available any more in esp-idf V5.x.y #include #endif +#endif +#endif + + +#ifndef I2S_PIN_NO_CHANGE +#define I2S_PIN_NO_CHANGE (-1) // common "pin unused" marker (not defined in IDF V5 driver so define it here) +#endif + +// IDF V5 renamed the PDM RX slot config macro (raw PDM format support) - map back to the classic name +#if (ESP_IDF_VERSION_MAJOR >= 5) && !defined(I2S_PDM_RX_SLOT_DEFAULT_CONFIG) && defined(I2S_PDM_RX_SLOT_PCM_FMT_DEFAULT_CONFIG) +#define I2S_PDM_RX_SLOT_DEFAULT_CONFIG I2S_PDM_RX_SLOT_PCM_FMT_DEFAULT_CONFIG +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) // type of i2s_config_t.SampleRate was changed from "int" to "unsigned" in IDF 4.4.x +// also matches i2s_std_clk_config_t.sample_rate_hz of the IDF V5 driver #define SRate_t uint32_t #else #define SRate_t int #endif -//#include -//#include -//#include -//#include +// analog microphone support +#if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) +#define AR_ANALOG_MIC_I2S_ADC // legacy ADC-over-I2S (classic ESP32, IDF v4 only) +#endif +#if (ESP_IDF_VERSION_MAJOR >= 5) && defined(SOC_ADC_SUPPORTED) +#define AR_ANALOG_MIC_ADC_CONTINUOUS // ADC continuous driver (esp_adc) - IDF v5.x and v6.x +#endif +#if defined(AR_ANALOG_MIC_I2S_ADC) || defined(AR_ANALOG_MIC_ADC_CONTINUOUS) +#define AR_ANALOG_MIC_SUPPORTED +#endif // see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents // and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes -#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32C61) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265) +#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C61) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265) // there are two things in these MCUs that could lead to problems with audio processing: // * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x) // * single core, so FFT task might slow down other things like LED updates @@ -52,20 +85,27 @@ //#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible #ifdef I2S_USE_16BIT_SAMPLES -#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT #define I2S_datatype int16_t #define I2S_unsigned_datatype uint16_t -#define I2S_data_size I2S_BITS_PER_CHAN_16BIT #undef I2S_SAMPLE_DOWNSCALE_TO_16BIT #else -#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT -//#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_24BIT #define I2S_datatype int32_t #define I2S_unsigned_datatype uint32_t -#define I2S_data_size I2S_BITS_PER_CHAN_32BIT #define I2S_SAMPLE_DOWNSCALE_TO_16BIT #endif +#if ESP_IDF_VERSION_MAJOR < 5 +// legacy driver constants (IDF v4.x only) +#ifdef I2S_USE_16BIT_SAMPLES +#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT +#define I2S_data_size I2S_BITS_PER_CHAN_16BIT +#else +#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT +//#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_24BIT +#define I2S_data_size I2S_BITS_PER_CHAN_32BIT +#endif +#endif + /* There are several (confusing) options in IDF 4.4.x: * I2S_CHANNEL_FMT_RIGHT_LEFT, I2S_CHANNEL_FMT_ALL_RIGHT and I2S_CHANNEL_FMT_ALL_LEFT stands for stereo mode, which means two channels will transport different data. * I2S_CHANNEL_FMT_ONLY_RIGHT and I2S_CHANNEL_FMT_ONLY_LEFT they are mono mode, both channels will only transport same data. @@ -73,6 +113,8 @@ * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. */ +#if ESP_IDF_VERSION_MAJOR < 5 + #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 5, 0)) // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) @@ -106,6 +148,19 @@ #endif +#else // ESP_IDF_VERSION_MAJOR >= 5 - I2S driver channel selection is done via slot mask (no swapped-channel bug in V5) + +#ifdef I2S_USE_RIGHT_CHANNEL +#define AR_MIC_SLOT_MASK I2S_STD_SLOT_RIGHT +#define I2S_MIC_CHANNEL_TEXT "right channel only." +#else +#define AR_MIC_SLOT_MASK I2S_STD_SLOT_LEFT +#define I2S_MIC_CHANNEL_TEXT "left channel only." +#endif +#define I2S_PDM_MIC_CHANNEL_TEXT I2S_MIC_CHANNEL_TEXT + +#endif // ESP_IDF_VERSION_MAJOR + /* Interface class AudioSource serves as base class for all microphone types @@ -142,7 +197,7 @@ class AudioSource { virtual bool isInitialized(void) {return(_initialized);} /* identify Audiosource type - I2S-ADC or I2S-digital */ - typedef enum{Type_unknown=0, Type_I2SAdc=1, Type_I2SDigital=2} AudioSourceType; + typedef enum{Type_unknown=0, Type_I2SAdc=1, Type_I2SDigital=2} AudioSourceType; // note: Type_I2SAdc also covers the ADC-continuous source (IDF v5/v6), TODO: should be renamed to Type_Analog virtual AudioSourceType getType(void) {return(Type_I2SDigital);} // default is "I2S digital source" - ADC type overrides this method protected: @@ -170,6 +225,14 @@ class I2SSource : public AudioSource { public: I2SSource(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) : AudioSource(sampleRate, blockSize, sampleScale) { +#if ESP_IDF_VERSION_MAJOR >= 5 + #ifdef I2S_USE_16BIT_SAMPLES + _dataBitWidth = I2S_DATA_BIT_WIDTH_16BIT; + #else + _dataBitWidth = I2S_DATA_BIT_WIDTH_32BIT; + #endif +#else + // legacy I2S driver (IDF v4.x) _config = { .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = _sampleRate, @@ -191,6 +254,7 @@ class I2SSource : public AudioSource { .use_apll = false #endif }; +#endif } virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE) { @@ -209,7 +273,126 @@ class I2SSource : public AudioSource { DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pins: sck=%d\n", i2sckPin); return; } + } + + // Reserve the master clock pin if provided + if (mclkPin != I2S_PIN_NO_CHANGE) { + if(!PinManager::allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) { + DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pin: MCLK=%d\n", mclkPin); + return; + } + } + +#if ESP_IDF_VERSION_MAJOR >= 5 + _wsPin = i2swsPin; _sdPin = i2ssdPin; _ckPin = i2sckPin; _mclkPin = mclkPin; + + bool usePDM = false; + if (i2sckPin == I2S_PIN_NO_CHANGE) { + // This is an I2S PDM microphone, these microphones only use a clock and + // data line, to make it simpler to debug, use the WS pin as CLK and SD pin as DATA + // TODO: see the note below for V4 drivers: do the issues for S3 and C3 persist in V5 or is was there a fix / workaround? + // also check which ICs have SOC_I2S_SUPPORTS_PDM_RX defined (C6 does not) +#if defined(SOC_I2S_SUPPORTS_PDM_RX) + usePDM = true; +#else + #warning this MCU does not support PDM microphones + DEBUGSR_PRINTLN(F("AR: PDM microphone requested, but this MCU does not support PDM RX.")); + return; +#endif + } + + // aPLL clock source improves sampling quality (only on MCUs that have one, and only when a MCLK pin is used) + bool wantAPLL = false; + if (mclkPin != I2S_PIN_NO_CHANGE) { + #if !defined(WLED_USE_ETHERNET) // fix for #5391 aPLL resource conflict - aPLL is needed for ethernet boards with internal RMII clock + wantAPLL = true; + #endif + #if !defined(SOC_I2S_SUPPORTS_APLL) + // S3: not supported; S2: supported; C3: not supported + wantAPLL = false; // APLL not supported on this MCU + #endif + #if defined(CONFIG_IDF_TARGET_ESP32) + if (ESP.getChipRevision() == 0) wantAPLL = false; // APLL is broken on ESP32 revision 0 + #endif + } + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); // note: I2S_NUM_AUTO might fail on some targets (AI warned about it, might not be true), V4 uses I2S_NUM_0 unconditionally + chan_cfg.dma_desc_num = 8; // matches legacy dma_buf_count + chan_cfg.dma_frame_num = _blockSize; // matches legacy dma_buf_len +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) + chan_cfg.intr_priority = 2; // matches legacy ESP_INTR_FLAG_LEVEL2 +#endif + esp_err_t err = i2s_new_channel(&chan_cfg, nullptr, &_rx_handle); // RX only channel + if (err != ESP_OK) { + DEBUGSR_PRINTF("AR: Failed to allocate I2S channel: %d\n", err); + return; + } + + if (usePDM) { +#if defined(SOC_I2S_SUPPORTS_PDM_RX) + i2s_pdm_rx_config_t pdm_cfg = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sampleRate), // note: no aPLL for PDM (fix for #5391) + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), // PDM RX always delivers 16bit PCM + .gpio_cfg = { + .clk = (gpio_num_t)i2swsPin, // legacy driver used the WS pin as PDM clock + .din = (gpio_num_t)i2ssdPin, // single data line (din is a union with dins[] in the driver) + .invert_flags = { .clk_inv = false }, + }, + }; + err = i2s_channel_init_pdm_rx_mode(_rx_handle, &pdm_cfg); + _bytesPerSample = 2; +#endif } else { + i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sampleRate); + clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256; // ES8388 is configured for MCLK/LRCK ratio = 256 +#if defined(SOC_I2S_SUPPORTS_APLL) + if (wantAPLL) clk_cfg.clk_src = I2S_CLK_SRC_APLL; +#endif + i2s_std_slot_config_t slot_cfg; + if (_useMSBFormat) slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(_dataBitWidth, I2S_SLOT_MODE_MONO); // MSB format mics (SPH0645) note: the "narrowing conversion" error still compiles fine + else slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(_dataBitWidth, I2S_SLOT_MODE_MONO); // standard I2S mics + slot_cfg.slot_mask = _slotMask; // left / right channel selection + i2s_std_config_t std_cfg = { + .clk_cfg = clk_cfg, + .slot_cfg = slot_cfg, + .gpio_cfg = { + .mclk = (mclkPin == I2S_PIN_NO_CHANGE) ? I2S_GPIO_UNUSED : (gpio_num_t)mclkPin, // "classic" ESP32 supports MCK on GPIO0/GPIO1/GPIO3 only + .bclk = (gpio_num_t)i2sckPin, + .ws = (gpio_num_t)i2swsPin, + .dout = I2S_GPIO_UNUSED, + .din = (gpio_num_t)i2ssdPin, + .invert_flags = { .mclk_inv = false, .bclk_inv = false, .ws_inv = false }, + }, + }; + err = i2s_channel_init_std_mode(_rx_handle, &std_cfg); + _bytesPerSample = (_dataBitWidth == I2S_DATA_BIT_WIDTH_32BIT) ? 4 : 2; + } + if (err != ESP_OK) { + DEBUGSR_PRINTF("AR: Failed to configure I2S channel: %d\n", err); + i2s_del_channel(_rx_handle); + _rx_handle = nullptr; + return; + } + + err = i2s_channel_enable(_rx_handle); + if (err != ESP_OK) { + DEBUGSR_PRINTF("AR: Failed to enable I2S channel: %d\n", err); + i2s_del_channel(_rx_handle); + _rx_handle = nullptr; + return; + } + + DEBUGSR_PRINTF("AR: I2S driver %s aPLL.\n", wantAPLL ? "uses":"without"); + DEBUGSR_PRINTF("AR: %d bits, Sample scaling factor = %6.4f\n", int(_bytesPerSample) * 8, _sampleScale); + if (usePDM) { + DEBUGSR_PRINTLN(F("AR: I2S driver installed in PDM MASTER mode.")); + } else { + DEBUGSR_PRINTLN(F("AR: I2S driver installed in MASTER mode.")); + } + _initialized = true; + +#else // IDF V4 + if (i2sckPin == I2S_PIN_NO_CHANGE) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) #if !defined(SOC_I2S_SUPPORTS_PDM_RX) #warning this MCU does not support PDM microphones @@ -250,15 +433,8 @@ class I2SSource : public AudioSource { #endif #endif - // Reserve the master clock pin if provided _mclkPin = mclkPin; - if (mclkPin != I2S_PIN_NO_CHANGE) { - if(!PinManager::allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) { - DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pin: MCLK=%d\n", mclkPin); - return; - } else - _routeMclk(mclkPin); - } + if (mclkPin != I2S_PIN_NO_CHANGE) _routeMclk(mclkPin); _pinConfig = { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) @@ -302,10 +478,23 @@ class I2SSource : public AudioSource { } #endif _initialized = true; +#endif } virtual void deinitialize() { _initialized = false; +#if ESP_IDF_VERSION_MAJOR >= 5 + if (_rx_handle != nullptr) { + i2s_channel_disable(_rx_handle); + i2s_del_channel(_rx_handle); + _rx_handle = nullptr; + } + // release the pins + if (_wsPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_wsPin, PinOwner::UM_Audioreactive); + if (_sdPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_sdPin, PinOwner::UM_Audioreactive); + if (_ckPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_ckPin, PinOwner::UM_Audioreactive); + if (_mclkPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_mclkPin, PinOwner::UM_Audioreactive); +#else esp_err_t err = i2s_driver_uninstall(I2S_NUM_0); if (err != ESP_OK) { DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); @@ -316,12 +505,50 @@ class I2SSource : public AudioSource { if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_pinConfig.bck_io_num, PinOwner::UM_Audioreactive); // Release the master clock pin if (_mclkPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_mclkPin, PinOwner::UM_Audioreactive); +#endif } virtual void getSamples(FFTsampleType *buffer, uint16_t num_samples) { if (_initialized) { esp_err_t err; size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */ + +#if ESP_IDF_VERSION_MAJOR >= 5 + const size_t bytes_wanted = (size_t)num_samples * _bytesPerSample; + uint8_t rawBuf[bytes_wanted]; // intermediate sample storage on stack + + err = i2s_channel_read(_rx_handle, rawBuf, bytes_wanted, &bytes_read, portMAX_DELAY); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to get samples: %d\n", err); + return; + } + + // For correct operation, we need to read exactly sizeof(samples) bytes from i2s + if (bytes_read != bytes_wanted) { + DEBUGSR_PRINTF("Failed to get enough samples: wanted: %d read: %d\n", bytes_wanted, bytes_read); + return; + } + + // Store samples in sample buffer + for (int i = 0; i < num_samples; i++) { + I2S_datatype newSample; + if (_bytesPerSample == 4) newSample = ((const int32_t *)rawBuf)[i]; + else newSample = (I2S_datatype)((const int16_t *)rawBuf)[i]; + newSample = postProcessSample(newSample); // perform postprocessing (needed for ADC samples) + +#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT) + float currSample = (_bytesPerSample == 4) ? ((float)newSample / 65536.0f) : (float)newSample; // 32bit input -> 16bit, keeping lower 16bits as decimal places + buffer[i] = currSample; + buffer[i] *= _sampleScale; // scale samples +#else + // note on sample scaling: scaling is only used for inputs with master clock and those are better suited for ESP32 or S3 + // execution speed is critical on single core MCUs + int16_t currSample = (_bytesPerSample == 4) ? (int16_t)(newSample >> 16) : (int16_t)newSample; // no sample scaling, just shift down to 16bit + buffer[i] = currSample; +#endif + } + +#else // IDF V4 I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */ err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY); @@ -367,11 +594,13 @@ class I2SSource : public AudioSource { buffer[i] = (int16_t)currSample; #endif } +#endif } } protected: void _routeMclk(int8_t mclkPin) { +#if ESP_IDF_VERSION_MAJOR < 5 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // MCLK routing by writing registers is not needed any more with IDF > 4.4.0 #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0) @@ -391,11 +620,25 @@ class I2SSource : public AudioSource { } #endif #endif +#endif + // note: in IDF V5 I2S driver, MCLK routing is done by i2s_channel_init_std_mode() via gpio_cfg.mclk } +#if ESP_IDF_VERSION_MAJOR >= 5 + i2s_chan_handle_t _rx_handle = nullptr; + i2s_data_bit_width_t _dataBitWidth = I2S_DATA_BIT_WIDTH_32BIT; + i2s_std_slot_mask_t _slotMask = AR_MIC_SLOT_MASK; + uint8_t _bytesPerSample = 4; + bool _useMSBFormat = false; // set by SPH0645 - selects MSB slot format instead of Philips + int8_t _wsPin = I2S_PIN_NO_CHANGE; + int8_t _sdPin = I2S_PIN_NO_CHANGE; + int8_t _ckPin = I2S_PIN_NO_CHANGE; + int8_t _mclkPin = I2S_PIN_NO_CHANGE; +#else i2s_config_t _config; i2s_pin_config_t _pinConfig; int8_t _mclkPin; +#endif }; /* ES7243 Microphone @@ -430,7 +673,11 @@ class ES7243 : public I2SSource { public: ES7243(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) : I2SSource(sampleRate, blockSize, sampleScale) { +#if ESP_IDF_VERSION_MAJOR >= 5 + _slotMask = I2S_STD_SLOT_RIGHT; // right channel via slot mask +#else _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; +#endif }; void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { @@ -546,7 +793,11 @@ class ES8388Source : public I2SSource { public: ES8388Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) : I2SSource(sampleRate, blockSize, sampleScale) { +#if ESP_IDF_VERSION_MAJOR >= 5 + _slotMask = I2S_STD_SLOT_LEFT; // left channel via slot mask +#else _config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; +#endif }; void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { @@ -567,14 +818,214 @@ class ES8388Source : public I2SSource { }; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) -#if !defined(SOC_I2S_SUPPORTS_ADC) && !defined(SOC_I2S_SUPPORTS_ADC_DAC) - #warning this MCU does not support analog sound input +/* Analog input using ADC continuous sampling (IDF V5 and up only) + uses one ADC1 pin sampled by the ADC digital controller with DMA + notes: + - on "classic" ESP32 the ADC uses the I2S0 DMA path + - the conversion frame contains channel + sample, similar to the legacy driver, + so the "rogue sample" filtering from the original driver can be kept (TODO: should it? or was that a workaround for a bug? Or is it to support analogRead() between samples?) +*/ +#if defined(AR_ANALOG_MIC_ADC_CONTINUOUS) +#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2) + #define AR_ADC_DIGI_FORMAT ADC_DIGI_OUTPUT_FORMAT_TYPE1 // ESP32 and S2 use output format TYPE1 + #define AR_ADC_OUT_TYPE type1 +#else + #define AR_ADC_DIGI_FORMAT ADC_DIGI_OUTPUT_FORMAT_TYPE2 // S3, C3, ... use output format TYPE2 + #define AR_ADC_OUT_TYPE type2 #endif + +class AdcContinuousSource : public AudioSource { + public: + AdcContinuousSource(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) : + AudioSource(sampleRate, blockSize, sampleScale) {} + + /* identify Audiosource type - ADC analog (same type as legacy I2S-ADC, so callers treat it identically) */ + AudioSourceType getType(void) {return(Type_I2SAdc);} + + void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + DEBUGSR_PRINTLN(F("AdcContinuousSource:: initialize().")); + _myADCchannel = 0x0F; + if(!PinManager::allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { + DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin); + return; + } + _audioPin = audioPin; + + // check if this is a valid analog input pin (only ADC1 supported as ADC2 is normally used for wifi) + int adc_channel = digitalPinToAnalogChannel(_audioPin); + if (adc_channel >= SOC_ADC_CHANNEL_NUM(0)) { + DEBUGSR_PRINTF("Incompatible GPIO used for analog audio input: %d\n", _audioPin); + return; + } + _myADCchannel = adc_channel; + + const size_t frameSize = (size_t)_blockSize * 4 * SOC_ADC_DIGI_DATA_BYTES_PER_CONV; // one DMA frame = one FFT block (assumes block = 128, FFT frame = 512) TODO: this should respect the actual numbers set in the cpp file + adc_continuous_handle_cfg_t handle_cfg = { + .max_store_buf_size = frameSize, // buffer size the driver writes data to from the DMA buffers (and what we read back from when), since we stop / start the acquisition, one FFT frame is enough, its not continuous anyway + .conv_frame_size = (size_t)_blockSize, // use BLOCK_SIZE buffer size, driver uses 5 DMA buffers with chained descriptors in a loop + .flags = { .flush_pool = false }, // false=drop samples if full, true=flush buffer if full + }; + esp_err_t err = adc_continuous_new_handle(&handle_cfg, &_adcHandle); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to create adc continuous handle: %d\n", err); + return; + } + + adc_digi_pattern_config_t adc_pattern = { + .atten = ADC_ATTEN_DB_12, // same input amplification as the legacy driver + .channel = (uint8_t)_myADCchannel, + .unit = ADC_UNIT_1, + .bit_width = ADC_BITWIDTH_12, // 12bit resolution, like legacy adc1_config_width(ADC_WIDTH_BIT_12) + }; + adc_continuous_config_t adc_cfg = { + .pattern_num = 1, // single channel + .adc_pattern = &adc_pattern, + .sample_freq_hz = (uint32_t)_sampleRate, + .conv_mode = ADC_CONV_SINGLE_UNIT_1, // only ADC1 can be used (ADC2 is used by wifi) + .format = AR_ADC_DIGI_FORMAT, + }; + err = adc_continuous_config(_adcHandle, &adc_cfg); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to configure adc continuous driver: %d\n", err); + adc_continuous_deinit(_adcHandle); + _adcHandle = nullptr; + return; + } + + #if defined(I2S_GRAB_ADC1_COMPLETELY) + // according to docs from espressif, the ADC needs to be started explicitly + // fingers crossed + err = adc_continuous_start(_adcHandle); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to start adc continuous driver: %d\n", err); + adc_continuous_deinit(_adcHandle); + _adcHandle = nullptr; + return; + } + #endif + _initialized = true; + } + + void getSamples(FFTsampleType *buffer, uint16_t num_samples) { + /* Enable ADC. This has to be enabled and disabled directly before and + * after sampling, otherwise Wifi dies (legacy behaviour, kept for WiFi coexistence) + */ + if (_initialized) { + esp_err_t err = ESP_OK; + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + adc_continuous_flush_pool(_adcHandle); // drop any old conversions + err = adc_continuous_start(_adcHandle); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to start adc continuous driver: %d\n", err); + return; + } + #endif + adc_digi_output_data_t rawBuf[num_samples]; // intermediary sample storage (stack, like the legacy version) + uint32_t bytes_got = 0; + const uint32_t bytes_wanted = sizeof(rawBuf); + // adc_continuous_read() returns as soon as *some* data is available - loop until the frame is complete + while (bytes_got < bytes_wanted) { + uint32_t bytes_read = 0; + esp_err_t read_err = adc_continuous_read(_adcHandle, reinterpret_cast(rawBuf) + bytes_got, bytes_wanted - bytes_got, &bytes_read, 50 /*ms timeout*/); + if ((read_err == ESP_ERR_TIMEOUT) || (read_err == ESP_ERR_INVALID_STATE)) { + DEBUGSR_PRINTF("Failed to get enough ADC samples: wanted: %d read: %d\n", bytes_wanted, bytes_got); + break; + } + if (bytes_read == 0) { + DEBUGSR_PRINTLN("ADC read returned 0 bytes, aborting"); + break; + } + bytes_got += bytes_read; + } + + err = adc_continuous_stop(_adcHandle); // stop sampling even when using I2S_GRAB_ADC1_COMPLETELY, letting it run leads a stall for some reason and adc_continuous_read() above takes ~200ms and still returns ESP_OK. + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to stop adc continuous driver: %d\n", err); + return; + } + + // TODO: in order to let other tasks use analogRead() we can not just stop the DMA transfer using adc_continuous_stop(), we need to fully tear down the driver and reinitialize it. + // so currently any ADC reading outside is broken, even if not using I2S_GRAB_ADC1_COMPLETELY + // for best analog mic performance I would suggest to add an "ADC manager" that handles it all, i.e. request continuous samples or single shot and it manages the teardown and restart decently + + #if defined(I2S_GRAB_ADC1_COMPLETELY) + err = adc_continuous_start(_adcHandle); // start sampling in the background + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to start adc continuous driver: %d\n", err); + return; + } + #endif + + if (bytes_got < bytes_wanted) { + return; // incomplete frame - keep old samples + } + + // Store samples in sample buffer + for (int i = 0; i < num_samples; i++) { + int16_t finalSample = _postProcessAdcSample(rawBuf[i]); + +#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT) + float currSample = (float)finalSample; + buffer[i] = currSample; + buffer[i] *= _sampleScale; // scale samples (1.0f for ADC source) +#else + buffer[i] = finalSample; +#endif + } + } + } + + void deinitialize() { + PinManager::deallocatePin(_audioPin, PinOwner::UM_Audioreactive); + _initialized = false; + _myADCchannel = 0x0F; + if (_adcHandle != nullptr) { +#if defined(I2S_GRAB_ADC1_COMPLETELY) + // according to docs from espressif, the ADC needs to be stopped explicitly + // fingers crossed + esp_err_t err = adc_continuous_stop(_adcHandle); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to stop adc continuous driver: %d\n", err); + } #endif + adc_continuous_deinit(_adcHandle); + _adcHandle = nullptr; + } + } -#if defined(CONFIG_IDF_TARGET_ESP32) && (ESP_IDF_VERSION_MAJOR < 5) // legacy ADC driver is not available any more in esp-idf V5.x.y -// ADC over I2S is only availeable in "classic" ESP32 + private: + /* decode one ADC conversion frame, filter rogue samples, apply low-pass filter. + Mimics the scaling of the legacy I2S-ADC driver: 12bit unsigned -> signed 10bit range */ + int16_t _postProcessAdcSample(adc_digi_output_data_t frame) { + uint16_t the_channel = frame.AR_ADC_OUT_TYPE.channel; + uint16_t the_sample = frame.AR_ADC_OUT_TYPE.data; // 12bit unsigned ADC sample + int finalSample = (int(the_sample) - 2048); // center at 0 TODO: the original ESP32 adc driver scales down to 10bits but that makes MAX9814 mic very insensitive, I see no noise using all bits (we got squelch if its an issue) + + if ((the_channel != _myADCchannel) && (_myADCchannel != 0x0F)) { // 0x0F means "don't know what my channel is" + // fix bad sample + finalSample = _lastADCsample; // replace with last good ADC sample + _broken_samples_counter++; + if (_broken_samples_counter > 256) _myADCchannel = 0x0F; // too many bad samples in a row -> disable sample corrections + } else _broken_samples_counter = 0; // good sample - reset counter + + //finalSample = (3 * finalSample + _lastADCsample) >> 2; // apply low-pass filter (2-tap FIR) TODO: this was done in the original driver but my tests show this is probably unnecessary + //finalSample = (finalSample + _lastADCsample) >> 1; // apply stronger low-pass filter (2-tap FIR) + // TODO: IDF V5 ADC sampling supports doing on-the-fly IIR filtering while sampling, not sure how that works though i.e. if its done when reading samples or if it has to be called explicitly with the samples. + + _lastADCsample = finalSample; // update ADC last sample + return (int16_t)finalSample; + } + + adc_continuous_handle_t _adcHandle = nullptr; + int8_t _audioPin = -1; + int8_t _myADCchannel = 0x0F; // current ADC channel for analog input. 0x0F means "undefined" + int _lastADCsample = 0; // last good sample (for filtering and rogue sample replacement) + unsigned int _broken_samples_counter = 0; // number of consecutive broken (and fixed) ADC samples +}; +#endif // AR_ANALOG_MIC_ADC_CONTINUOUS + +#if defined(AR_ANALOG_MIC_I2S_ADC) +// ADC over I2S is only availeable in "classic" ESP32 (legacy driver, IDF v4.x only) /* ADC over I2S Microphone This microphone is an ADC pin sampled via the I2S interval @@ -764,7 +1215,7 @@ class I2SAdcSource : public I2SSource { int8_t _audioPin; int8_t _myADCchannel = 0x0F; // current ADC channel for analog input. 0x0F means "undefined" }; -#endif +#endif // AR_ANALOG_MIC_I2S_ADC /* SPH0645 Microphone This is an I2S microphone with some timing quirks that need @@ -773,19 +1224,30 @@ class I2SAdcSource : public I2SSource { // https://github.com/espressif/esp-idf/issues/7192 SPH0645 i2s microphone issue when migrate from legacy esp-idf version (IDFGH-5453) // a user recommended this: Try to set .communication_format to I2S_COMM_FORMAT_STAND_I2S and call i2s_set_clk() after i2s_set_pin(). +// note: with the IDF V5 I2S driver the MSB slot format is selected via I2S_STD_MSB_SLOT_DEFAULT_CONFIG instead of the I2S_RX_MSB_SHIFT register poke. class SPH0654 : public I2SSource { public: SPH0654(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) : I2SSource(sampleRate, blockSize, sampleScale) - {} + { +#if ESP_IDF_VERSION_MAJOR >= 5 + _useMSBFormat = true; // SPH0645 sends MSB format (no 1-bit shift like Philips) - handled by the slot config +#endif + } void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE) { DEBUGSR_PRINTLN(F("SPH0654:: initialize();")); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin); #if defined(CONFIG_IDF_TARGET_ESP32) // these registers are only existing in "classic" ESP32 + #if ESP_IDF_VERSION_MAJOR < 5 REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9)); - REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT); + REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT); // legacy driver: MSB shift via register poke + #else + // MSB format is already selected via _useMSBFormat (slot config) - only the timing tweak remains. + // TODO: test if this is still needed with the V5 driver and remove it if possible. + REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9)); + #endif #else #warning FIX ME! Please. #endif