From 4bd35b780e54f56cbd123c29ab75f16cbed310b1 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 7 Nov 2022 15:34:42 +0100 Subject: [PATCH] Some improvements for I2S Line-In - I2S DAC devices utilize the complete sample range [-32767 ... 32768], while I2S microphones typically deliver samples in [-2047... 2047]. Down-scaling Line-in samples ensures that filters and AGC operate optimally. - use aPLL clock source for I2S with MCLK - experimental: I2S slave mode (however could not get it to work ...) --- usermods/audioreactive/audio_reactive.h | 8 ++-- usermods/audioreactive/audio_source.h | 49 +++++++++++++++++++------ wled00/wled.h | 2 +- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index a05b493c..9d9c1a21 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -662,7 +662,7 @@ class AudioReactive : public Usermod { Serial.print("micReal:"); Serial.print(micDataReal); Serial.print("\t"); Serial.print("volumeSmth:"); Serial.print(volumeSmth); Serial.print("\t"); //Serial.print("volumeRaw:"); Serial.print(volumeRaw); Serial.print("\t"); - //Serial.print("DC_Level:"); Serial.print(micLev); Serial.print("\t"); + Serial.print("DC_Level:"); Serial.print(micLev); Serial.print("\t"); //Serial.print("sampleAgc:"); Serial.print(sampleAgc); Serial.print("\t"); //Serial.print("sampleAvg:"); Serial.print(sampleAvg); Serial.print("\t"); //Serial.print("sampleReal:"); Serial.print(sampleReal); Serial.print("\t"); @@ -1107,8 +1107,9 @@ class AudioReactive : public Usermod { } // Reset I2S peripheral for good measure - i2s_driver_uninstall(I2S_NUM_0); + 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) + delay(100); periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3 #endif delay(100); // Give that poor microphone some time to setup. @@ -1140,7 +1141,8 @@ class AudioReactive : public Usermod { break; case 4: DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, true, 1.0f/16.0f); + //audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, false, 1.0f/16.0f); // I2S SLAVE mode - does not work, unfortunately delay(100); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 13cc0947..9a05b8f3 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -138,28 +138,33 @@ class AudioSource { virtual I2S_datatype postProcessSample(I2S_datatype sample_in) {return(sample_in);} // default method can be overriden by instances (ADC) that need sample postprocessing // Private constructor, to make sure it is not callable except from derived classes - AudioSource(SRate_t sampleRate, int blockSize) : + AudioSource(SRate_t sampleRate, int blockSize, bool i2sMaster = true, float sampleScale = 1.0f) : _sampleRate(sampleRate), _blockSize(blockSize), - _initialized(false) + _initialized(false), + _i2sMaster(i2sMaster), + _sampleScale(sampleScale) {}; SRate_t _sampleRate; // Microphone sampling rate int _blockSize; // I2S block size bool _initialized; // Gets set to true if initialization is successful + bool _i2sMaster; // when false, ESP32 will be in I2S SLAVE mode (for devices that only operate in MASTER mode). Only workds in newer IDF >= 4.4.x + float _sampleScale; // pre-scaling factor for I2S samples }; /* Basic I2S microphone source All functions are marked virtual, so derived classes can replace them + WARNING: i2sMaster = false is experimental, and most likely will not work */ class I2SSource : public AudioSource { public: - I2SSource(SRate_t sampleRate, int blockSize) : - AudioSource(sampleRate, blockSize) { + I2SSource(SRate_t sampleRate, int blockSize, bool i2sMaster=true, float sampleScale = 1.0f) : + AudioSource(sampleRate, blockSize, i2sMaster, sampleScale) { _config = { - .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), + .mode = i2sMaster ? i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX) : i2s_mode_t(I2S_MODE_SLAVE | I2S_MODE_RX), .sample_rate = _sampleRate, - .bits_per_sample = I2S_SAMPLE_RESOLUTION, + .bits_per_sample = I2S_SAMPLE_RESOLUTION, // slave mode: may help to set this to 96000, as the other side (master) controls sample rates .channel_format = I2S_MIC_CHANNEL, #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S), @@ -168,6 +173,7 @@ class I2SSource : public AudioSource { .dma_buf_count = 8, .dma_buf_len = _blockSize, .use_apll = 0, + //.fixed_mclk = 0, .bits_per_chan = I2S_data_size, #else .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), @@ -203,6 +209,21 @@ class I2SSource : public AudioSource { #endif } + if (mclkPin != I2S_PIN_NO_CHANGE) { + _config.use_apll = true; // experimental - use aPLL clock source to improve sampling quality, and to avoid glitches. + // //_config.fixed_mclk = 512 * _sampleRate; + // //_config.fixed_mclk = 256 * _sampleRate; + } + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + if (ESP.getChipRevision() == 0) _config.use_apll = false; // APLL is broken on ESP32 revision 0 + #endif + + if (_i2sMaster == false) { + DEBUG_PRINTLN(F("AR: Warning - i2S SLAVE mode is experimental!")); + if ((_config.mode & I2S_MODE_MASTER) != 0) + DEBUG_PRINTLN("AR: (oops) I2S SLAVE mode requested but not configured!"); + } + // Reserve the master clock pin if provided _mclkPin = mclkPin; if (mclkPin != I2S_PIN_NO_CHANGE) { @@ -222,13 +243,20 @@ class I2SSource : public AudioSource { esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err); + DEBUGSR_PRINTF("AR: Failed to install i2s driver: %d\n", err); return; } + DEBUGSR_PRINTF("AR: I2S#0 driver %s aPLL; fixed_mclk=%d.\n", _config.use_apll? "uses":"without", _config.fixed_mclk); + DEBUGSR_PRINTF("AR: Sample scaling factor = %6.4f\n", _sampleScale); + if(_config.mode & I2S_MODE_MASTER) + DEBUGSR_PRINTLN(F("AR: I2S#0 driver installed in MASTER mode.")); + else + DEBUGSR_PRINTLN(F("AR: I2S#0 driver installed in SLAVE mode.")); + err = i2s_set_pin(I2S_NUM_0, &_pinConfig); if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to set i2s pin config: %d\n", err); + DEBUGSR_PRINTF("AR: Failed to set i2s pin config: %d\n", err); i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver return; } @@ -236,7 +264,7 @@ class I2SSource : public AudioSource { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) err = i2s_set_clk(I2S_NUM_0, _sampleRate, I2S_SAMPLE_RESOLUTION, I2S_CHANNEL_MONO); // set bit clocks. Also takes care of MCLK routing if needed. if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to configure i2s clocks: %d\n", err); + DEBUGSR_PRINTF("AR: Failed to configure i2s clocks: %d\n", err); i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver return; } @@ -288,8 +316,7 @@ class I2SSource : public AudioSource { currSample = (float) newSamples[i]; // 16bit input -> use as-is #endif buffer[i] = currSample; - //buffer[i] *= 0.6f; // (ICS-43434): compensate for higher sensitivity (reduce by 2db) - + buffer[i] *= _sampleScale; // scale samples } } } diff --git a/wled00/wled.h b/wled00/wled.h index ae24cecb..f06f76ef 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2210281 +#define VERSION 2211071 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG