kopia lustrzana https://github.com/Aircoookie/WLED
Adding analog mic support to AR for C3, S2, S3
still needs some cleanup and testing but basically works.pull/4761/head
rodzic
e6716fe834
commit
220f2565e2
|
@ -267,7 +267,6 @@ void FFTcode(void * parameter)
|
|||
#endif
|
||||
|
||||
xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay
|
||||
|
||||
// band pass filter - can reduce noise floor by a factor of 50
|
||||
// downside: frequencies below 100Hz will be ignored
|
||||
if (useBandPassFilter) runMicFilter(samplesFFT, vReal);
|
||||
|
@ -393,9 +392,8 @@ void FFTcode(void * parameter)
|
|||
// run peak detection
|
||||
autoResetPeak();
|
||||
detectSamplePeak();
|
||||
|
||||
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||
if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC
|
||||
#if !defined(I2S_GRAB_ADC1_COMPLETELY) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_Adc)) // the "delay trick" does not help for analog ADC
|
||||
#endif
|
||||
vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers
|
||||
|
||||
|
@ -672,9 +670,7 @@ class AudioReactive : public Usermod {
|
|||
static const char _dynamics[];
|
||||
static const char _frequency[];
|
||||
static const char _inputLvl[];
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
static const char _analogmic[];
|
||||
#endif
|
||||
static const char _digitalmic[];
|
||||
static const char _addPalettes[];
|
||||
static const char UDP_SYNC_HEADER[];
|
||||
|
@ -1177,12 +1173,9 @@ class AudioReactive : public Usermod {
|
|||
#endif
|
||||
|
||||
switch (dmType) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// stub cases for not-yet-supported I2S modes on other ESP32 chips
|
||||
case 0: //ADC analog
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
// stub case for not-yet-supported I2S mode
|
||||
case 5: //PDM Microphone
|
||||
#endif
|
||||
#endif
|
||||
case 1:
|
||||
DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
|
||||
|
@ -1223,17 +1216,24 @@ class AudioReactive : public Usermod {
|
|||
delay(100);
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ADC over I2S is only possible on "classic" ESP32
|
||||
case 0:
|
||||
default:
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ADC over I2S is only possible on "classic" ESP32
|
||||
DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only)."));
|
||||
audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE);
|
||||
delay(100);
|
||||
useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog
|
||||
if (audioSource) audioSource->initialize(audioPin);
|
||||
break;
|
||||
#else
|
||||
// use ADC DMA on ESP32S2, ESP32C3, ESP32S3
|
||||
DEBUGSR_PRINTLN(F("AR: Analog Microphone"));
|
||||
audioSource = new DMAadcSource(SAMPLE_RATE, samplesFFT);
|
||||
delay(10); // might help with proper initialization
|
||||
useBandPassFilter = true; // PDM bandpass filter seems to help for bad quality analog
|
||||
if (audioSource) audioSource->initialize(audioPin);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
delay(250); // give microphone enough time to initialise
|
||||
|
@ -1611,7 +1611,7 @@ class AudioReactive : public Usermod {
|
|||
// Analog or I2S digital input
|
||||
if (audioSource && (audioSource->isInitialized())) {
|
||||
// audio source successfully configured
|
||||
if (audioSource->getType() == AudioSource::Type_I2SAdc) {
|
||||
if (audioSource->getType() == AudioSource::Type_Adc) {
|
||||
infoArr.add(F("ADC analog"));
|
||||
} else {
|
||||
infoArr.add(F("I2S digital"));
|
||||
|
@ -1787,10 +1787,8 @@ class AudioReactive : public Usermod {
|
|||
top[FPSTR(_addPalettes)] = addPalettes;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
JsonObject amic = top.createNestedObject(FPSTR(_analogmic));
|
||||
amic["pin"] = audioPin;
|
||||
#endif
|
||||
|
||||
JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic));
|
||||
dmic["type"] = dmType;
|
||||
|
@ -1846,18 +1844,10 @@ class AudioReactive : public Usermod {
|
|||
configComplete &= getJsonValue(top[FPSTR(_addPalettes)], addPalettes);
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
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_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM
|
||||
#endif
|
||||
#endif
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin);
|
||||
|
@ -1893,9 +1883,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_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
uiScript.print(F("addOption(dd,'Generic Analog',0);"));
|
||||
#endif
|
||||
uiScript.print(F("addOption(dd,'Generic I2S',1);"));
|
||||
uiScript.print(F("addOption(dd,'ES7243',2);"));
|
||||
uiScript.print(F("addOption(dd,'SPH0654',3);"));
|
||||
|
@ -2059,9 +2047,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(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
|
||||
#endif
|
||||
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic";
|
||||
const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes";
|
||||
const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure
|
||||
|
|
|
@ -140,7 +140,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_Adc=1, Type_I2SDigital=2} AudioSourceType;
|
||||
virtual AudioSourceType getType(void) {return(Type_I2SDigital);} // default is "I2S digital source" - ADC type overrides this method
|
||||
|
||||
protected:
|
||||
|
@ -546,11 +546,11 @@ 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
|
||||
#endif
|
||||
#endif
|
||||
//#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
|
||||
//#endif
|
||||
//#endif
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ADC over I2S is only availeable in "classic" ESP32
|
||||
|
@ -583,8 +583,8 @@ class I2SAdcSource : public I2SSource {
|
|||
};
|
||||
}
|
||||
|
||||
/* identify Audiosource type - I2S-ADC*/
|
||||
AudioSourceType getType(void) {return(Type_I2SAdc);}
|
||||
/* identify Audiosource type - ADC*/
|
||||
AudioSourceType getType(void) {return(Type_Adc);}
|
||||
|
||||
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("I2SAdcSource:: initialize()."));
|
||||
|
@ -743,6 +743,180 @@ class I2SAdcSource : public I2SSource {
|
|||
int8_t _audioPin;
|
||||
int8_t _myADCchannel = 0x0F; // current ADC channel for analog input. 0x0F means "undefined"
|
||||
};
|
||||
#else
|
||||
|
||||
/* ADC sampling with DMA
|
||||
This microphone is an ADC pin sampled via ADC1 in continuous mode
|
||||
This allows to sample in the background with high sample rates and minimal CPU load
|
||||
note: only ADC1 channels can be used (ADC2 is used for WiFi)
|
||||
ESP32 is not implemented as it supports I2S for ADC sampling (see above)
|
||||
*/
|
||||
|
||||
#include "driver/adc.h"
|
||||
#include "hal/adc_types.h"
|
||||
#define ADC_TIMEOUT 30 // Timout for one full frame of samples in ms (TODO: use (FFT_MIN_CYCLE + 5) but need to move the ifdefs before the include in the cpp file)
|
||||
#define ADC_RESULT_BYTE SOC_ADC_DIGI_RESULT_BYTES //for C3 this is 4 (32bit, first 12bits is ADC result, see adc_digi_output_data_t)
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
#define MAX_ADC1_CHANNEL 4 // C3 has 5 channels (0-4)
|
||||
#else
|
||||
#define MAX_ADC1_CHANNEL 9 // ESP32, S2, S3 have 10 channels (0-9)
|
||||
#endif
|
||||
|
||||
class DMAadcSource : public AudioSource {
|
||||
public:
|
||||
DMAadcSource(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||
AudioSource(sampleRate, blockSize, sampleScale) {
|
||||
// ADC continuous mode configuration
|
||||
adc_dma_config = {
|
||||
.max_store_buf_size = (unsigned)blockSize * ADC_RESULT_BYTE, // internal storage of DMA driver (in bytes, one sample is 4 bytes on C3&S3, 2bytes on S2 note: using 2x buffer size would reduce overflows but can add latency
|
||||
.conv_num_each_intr = (unsigned)blockSize * ADC_RESULT_BYTE, // number of bytes per interrupt (or per frame, one sample contains 12bit of sample data)
|
||||
.adc1_chan_mask = 0, // ADC1 channel mask (set to correct channel in initialize())
|
||||
.adc2_chan_mask = 0, // dont use adc2 (used for wifi)
|
||||
};
|
||||
|
||||
adcpattern = {
|
||||
.atten = ADC_ATTEN_DB_11, // approx. 0-2.5V input range
|
||||
.channel = 0, // channel mask (set to correct channel in initialize())
|
||||
.unit = 0, // use ADC1
|
||||
.bit_width = SOC_ADC_DIGI_MAX_BITWIDTH, // set to 12bit
|
||||
};
|
||||
|
||||
dig_cfg = {
|
||||
.conv_limit_en = 0, // disable limit (does not work right if enabled)
|
||||
.conv_limit_num = 255, // set to max just in case
|
||||
.pattern_num = 1, // single channel sampling
|
||||
.adc_pattern = &adcpattern, // Pattern configuration
|
||||
.sample_freq_hz = sampleRate, // sample frequency in Hz
|
||||
.conv_mode = ADC_CONV_SINGLE_UNIT_1, // use ADC1 only
|
||||
.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2, // C3, S2 and S3 use this type, ESP32 uses TYPE1
|
||||
};
|
||||
}
|
||||
|
||||
/* identify Audiosource type - ADC*/
|
||||
AudioSourceType getType(void) {return(Type_Adc);}
|
||||
|
||||
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("DMAadcSource:: 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;
|
||||
|
||||
// Determine Analog channel. Only Channels on ADC1 are supported
|
||||
int8_t channel = digitalPinToAnalogChannel(_audioPin);
|
||||
if (channel > MAX_ADC1_CHANNEL) {
|
||||
DEBUGSR_PRINTF("Incompatible GPIO used for analog audio input: %d\n", _audioPin);
|
||||
return;
|
||||
} else {
|
||||
_myADCchannel = channel;
|
||||
}
|
||||
|
||||
adc_dma_config.adc1_chan_mask = (1 << channel); // update channel mask in DMA config
|
||||
adcpattern.channel = channel; // update channel in pattern config
|
||||
|
||||
if (init_adc_continuous() != ESP_OK)
|
||||
return;
|
||||
|
||||
adc_digi_start(); //start sampling
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void getSamples(float *buffer, uint16_t num_samples) {
|
||||
|
||||
int32_t framesize = num_samples * ADC_RESULT_BYTE; // size of one sample frame in bytes
|
||||
uint8_t result[framesize]; // create a read buffer
|
||||
uint32_t ret_num;
|
||||
uint32_t totalbytes = 0;
|
||||
uint32_t j = 0;
|
||||
esp_err_t err;
|
||||
if (_initialized) {
|
||||
do {
|
||||
err = adc_digi_read_bytes(result, framesize, &ret_num, ADC_TIMEOUT); // read samples
|
||||
if ((err == ESP_OK || err == ESP_ERR_INVALID_STATE) && ret_num > 0) { // in invalid sate (internal buffer overrun), still read the last valid sample, then reset the ADC DMA afterwards (better than not having samples at all)
|
||||
totalbytes += ret_num; // After an error, DMA buffer can be misaligned, returning partial frames. Found no other solution to re-align or flush the buffers, seems to be yet another IDF4 bug
|
||||
|
||||
// TODO: anything different if all channels of ADC1 are initialized in DMA init? currently not setting extra channels to zero like in the example.
|
||||
|
||||
if (totalbytes > framesize) { // got too many bytes to fit sample buffer
|
||||
ret_num -= totalbytes - framesize; // discard extra samples
|
||||
}
|
||||
for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE) {
|
||||
adc_digi_output_data_t *p = reinterpret_cast<adc_digi_output_data_t*>(&result[i]);
|
||||
buffer[j++] = float(((p->val & 0x0FFF))); // get the 12bit sample data and convert to float note: works on all format types
|
||||
// for integer math: when scaling up to 16bit: compared to I2S mic the scaling seems about the same when not shifting at all, so need to divide by 16 after FFT if scaling up to 16bit
|
||||
//Serial.print(buffer[j-1]); Serial.print(",");
|
||||
}
|
||||
//Serial.println("*");
|
||||
//if (j < NUMSAMPLES) Serial.println("***SPLIT SAMPLE***"); // Even with split samples, the data is consistend (no discontinuities in a sine input signal input)
|
||||
} else { // other read error, usually ESP_ERR_TIMEOUT (if DMA has stopped for some reason)
|
||||
reset_DMA_ADC();
|
||||
DEBUGSR_PRINTF("!!!!!!!! ADC ERROR !!!!!!!!!!\n");
|
||||
return; // something went very wrong, just exit
|
||||
}
|
||||
} while (totalbytes < framesize);
|
||||
}
|
||||
|
||||
// remove DC TODO: should really do this in int on C3... -> can use integer math if defined, see other PR
|
||||
int32_t sum = 0;
|
||||
for (int i = 0; i < num_samples; i++) sum += buffer[i];
|
||||
int32_t mean = sum / num_samples;
|
||||
for (int i = 0; i < num_samples; i++) buffer[i] -= mean; //uses static mean, as it should not change too much over time, deducted above
|
||||
|
||||
if (err == ESP_ERR_INVALID_STATE) { // error reading data, errors are: ESP_ERR_INVALID_STATE (=buffer overrun) or ESP_ERR_TIMEOUT, in both cases its best to reset the DMA ADC
|
||||
DEBUGSR_PRINTF("ADC BFR OVERFLOW, RESETTING ADC\n");
|
||||
Serial.println("BFR OVERFLOW");
|
||||
reset_DMA_ADC();
|
||||
}
|
||||
//Serial.print("bytes:");
|
||||
//Serial.println(totalbytes);
|
||||
}
|
||||
|
||||
void deinitialize() {
|
||||
PinManager::deallocatePin(_audioPin, PinOwner::UM_Audioreactive);
|
||||
_initialized = false;
|
||||
_myADCchannel = 0x0F;
|
||||
esp_err_t err;
|
||||
adc_digi_stop();
|
||||
delay(50); // just in case, give it some time
|
||||
err = adc_digi_deinitialize();
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to uninstall adc driver: %d\n", err);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
adc_digi_init_config_t adc_dma_config;
|
||||
adc_digi_pattern_config_t adcpattern;
|
||||
adc_digi_configuration_t dig_cfg;
|
||||
int8_t _audioPin;
|
||||
int8_t _myADCchannel = 0x0F; // current ADC channel for analog input. 0x0F means "undefined"
|
||||
|
||||
// Initialize ADC continuous mode with stored settings
|
||||
esp_err_t init_adc_continuous() {
|
||||
esp_err_t err = adc_digi_initialize(&adc_dma_config);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to init ADC DMA: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = adc_digi_controller_configure(&dig_cfg);
|
||||
if (err != ESP_OK) {
|
||||
DEBUGSR_PRINTF("Failed to init ADC sampling: %d\n", err);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void reset_DMA_ADC(void) {
|
||||
adc_digi_stop();
|
||||
adc_digi_deinitialize();
|
||||
//delay(1); // TODO: need any delay? seems to work fine without it and this code can be invoked at any time, so do not waste time here
|
||||
init_adc_continuous();
|
||||
adc_digi_start(); //start sampling
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* SPH0645 Microphone
|
||||
|
@ -771,3 +945,4 @@ class SPH0654 : public I2SSource {
|
|||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue