It compiles!

Cleaned (and possibly broken) AudioSource
Added:
- usermod notification about update
- strip.getMinShowDelay()
- pin manager updates
Changed:
- data exchange
This commit is contained in:
Blaz Kristan 2022-06-11 00:50:29 +02:00
parent dd584e929f
commit 562a206508
8 changed files with 389 additions and 398 deletions

View File

@ -106,7 +106,6 @@ static uint16_t micDataSm; // Smoothed mic data, as it's a
static float micDataReal = 0.0f; // future support - this one has the full 24bit MicIn data - lowest 8bit after decimal point static float micDataReal = 0.0f; // future support - this one has the full 24bit MicIn data - lowest 8bit after decimal point
static byte soundAgc = 0; // default Automagic gain control static byte soundAgc = 0; // default Automagic gain control
static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our multiplier static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our multiplier
static uint16_t noiseFloor = 100; // default squelch value for FFT reactive routines
static double FFT_MajorPeak = 0; static double FFT_MajorPeak = 0;
static double FFT_Magnitude = 0; static double FFT_Magnitude = 0;
@ -127,13 +126,14 @@ static float fftResultMax[16]; // A table used for test
static float fftAvg[16]; static float fftAvg[16];
// Table of linearNoise results to be multiplied by soundSquelch in order to reduce squelch across fftResult bins. // Table of linearNoise results to be multiplied by soundSquelch in order to reduce squelch across fftResult bins.
static uint16_t linearNoise[16] = { 34, 28, 26, 25, 20, 12, 9, 6, 4, 4, 3, 2, 2, 2, 2, 2 }; static uint8_t linearNoise[16] = { 34, 28, 26, 25, 20, 12, 9, 6, 4, 4, 3, 2, 2, 2, 2, 2 };
// Table of multiplication factors so that we can even out the frequency response. // Table of multiplication factors so that we can even out the frequency response.
static float fftResultPink[16] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; static float fftResultPink[16] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f };
// Create FFT object // Create FFT object
static arduinoFFT FFT = arduinoFFT(vReal, vImag, samples, SAMPLE_RATE); static arduinoFFT FFT = arduinoFFT(vReal, vImag, samples, SAMPLE_RATE);
static TaskHandle_t FFT_Task;
float fftAdd(int from, int to) { float fftAdd(int from, int to) {
float result = 0.0f; float result = 0.0f;
@ -156,15 +156,13 @@ void FFTcode(void * parameter) {
// taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work.
// Only run the FFT computing code if we're not in Receive mode // Only run the FFT computing code if we're not in Receive mode
if (audioSyncEnabled & 0x02) if (audioSyncEnabled & 0x02) continue;
continue;
audioSource->getSamples(vReal, samplesFFT); audioSource->getSamples(vReal, samplesFFT);
// old code - Last sample in vReal is our current mic sample // old code - Last sample in vReal is our current mic sample
//micDataSm = (uint16_t)vReal[samples - 1]; // will do a this a bit later //micDataSm = (uint16_t)vReal[samples - 1]; // will do a this a bit later
//micDataSm = ((micData * 3) + micData)/4;
// micDataSm = ((micData * 3) + micData)/4;
const int halfSamplesFFT = samplesFFT / 2; // samplesFFT divided by 2 const int halfSamplesFFT = samplesFFT / 2; // samplesFFT divided by 2
double maxSample1 = 0.0; // max sample from first half of FFT batch double maxSample1 = 0.0; // max sample from first half of FFT batch
@ -356,6 +354,21 @@ class AudioReactive : public Usermod {
#else #else
int8_t i2sckPin = I2S_CKPIN; int8_t i2sckPin = I2S_CKPIN;
#endif #endif
#ifndef ES7243_SDAPIN
int8_t sdaPin = 18;
#else
int8_t sdaPin = ES7243_SDAPIN;
#endif
#ifndef ES7243_SDAPIN
int8_t sclPin = 23;
#else
int8_t sclPin = ES7243_SCLPIN;
#endif
#ifndef MCLK_PIN
int8_t mclkPin = 0;
#else
int8_t mclkPin = MLCK_PIN;
#endif
#define UDP_SYNC_HEADER "00001" #define UDP_SYNC_HEADER "00001"
struct audioSyncPacket { struct audioSyncPacket {
@ -389,8 +402,8 @@ class AudioReactive : public Usermod {
float sampleAdj; // Gain adjusted sample value float sampleAdj; // Gain adjusted sample value
float sampleAgc = 0.0f; // Our AGC sample float sampleAgc = 0.0f; // Our AGC sample
int16_t rawSampleAgc = 0; // Our AGC sample - raw int16_t rawSampleAgc = 0; // Our AGC sample - raw
long timeOfPeak = 0; uint32_t timeOfPeak = 0;
long lastTime = 0; uint32_t lastTime = 0;
float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller
float sampleAvg = 0.0f; // Smoothed Average float sampleAvg = 0.0f; // Smoothed Average
float beat = 0.0f; // beat Detection float beat = 0.0f; // beat Detection
@ -680,36 +693,6 @@ class AudioReactive : public Usermod {
} // getSample() } // getSample()
/*
* A simple averaging multiplier to automatically adjust sound sensitivity.
*/
/*
* A simple, but hairy, averaging multiplier to automatically adjust sound sensitivity.
* not sure if not sure "sample" or "sampleAvg" is the correct input signal for AGC
*/
void agcAvg() {
float lastMultAgc = multAgc;
float tmpAgc;
if(fabs(sampleAvg) < 2.0f) {
tmpAgc = sampleAvg; // signal below squelch -> deliver silence
multAgc = multAgc * 0.95f; // slightly decrease gain multiplier
} else {
multAgc = (sampleAvg < 1) ? targetAgc : targetAgc / sampleAvg; // Make the multiplier so that sampleAvg * multiplier = setpoint
}
if (multAgc < 0.5f) multAgc = 0.5f; // signal higher than 2*setpoint -> don't reduce it further
multAgc = (lastMultAgc*127.0f +multAgc) / 128.0f; //apply some filtering to gain multiplier -> smoother transitions
tmpAgc = (float)sample * multAgc; // apply gain to signal
if (tmpAgc <= (soundSquelch*1.2f)) tmpAgc = sample; // check against squelch threshold - increased by 20% to avoid artefacts (ripples)
if (tmpAgc > 255) tmpAgc = 255;
sampleAgc = tmpAgc; // ONLY update sampleAgc ONCE because it's used elsewhere asynchronously!!!!
userVar0 = sampleAvg * 4;
if (userVar0 > 255) userVar0 = 255;
} // agcAvg()
void transmitAudioData() { void transmitAudioData() {
if (!udpSyncConnected) return; if (!udpSyncConnected) return;
@ -752,22 +735,25 @@ class AudioReactive : public Usermod {
// usermod exchangeable data // usermod exchangeable data
// we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers
um_data = new um_data_t; um_data = new um_data_t;
um_data->ub8_size = 2; um_data->u_size = 8;
um_data->ub8_data = new (*uint8_t)[um_data->ub8_size]; um_data->u_type = new um_types_t[um_data->u_size];
um_data->ub8_data[0] = &maxVol; um_data->u_data = new void*[um_data->u_size];
um_data->ub8_data[1] = fftResult; um_data->u_data[0] = &maxVol;
um_data->ui32_size = 1; um_data->u_type[0] = UMT_BYTE;
um_data->ui32_data = new (*int32_t)[um_data->ui32_size]; um_data->u_data[1] = fftResult;
um_data->ui32_data[0] = &sample; um_data->u_type[1] = UMT_BYTE_ARR;
um_data->uf4_size = 3; um_data->u_data[2] = &sample;
um_data->uf4_data = new (*float)[um_data->uf4_size]; um_data->u_type[2] = UMT_INT16;
um_data->uf4_data[0] = fftAvg; um_data->u_data[3] = fftAvg;
um_data->uf4_data[1] = fftCalc; um_data->u_type[3] = UMT_FLOAT_ARR;
um_data->uf4_data[2] = fftBin; um_data->u_data[4] = fftCalc;
um_data->uf8_size = 2; um_data->u_type[4] = UMT_FLOAT_ARR;
um_data->uf8_data = new (*double)[um_data->uf8_size]; um_data->u_data[5] = fftBin;
um_data->uf8_data[0] = &FFT_MajorPeak; um_data->u_type[5] = UMT_FLOAT_ARR;
um_data->uf8_data[1] = &FFT_Magnitude; um_data->u_data[6] = &FFT_MajorPeak;
um_data->u_type[6] = UMT_DOUBLE;
um_data->u_data[7] = &FFT_Magnitude;
um_data->u_type[7] = UMT_DOUBLE;
//... //...
// these are values used by effects in soundreactive fork // these are values used by effects in soundreactive fork
//uint8_t *fftResult = um_data->; //uint8_t *fftResult = um_data->;
@ -800,22 +786,32 @@ class AudioReactive : public Usermod {
case 1: case 1:
DEBUGSR_PRINTLN("AS: Generic I2S Microphone."); DEBUGSR_PRINTLN("AS: Generic I2S Microphone.");
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF);
delay(100);
audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin);
break; break;
case 2: case 2:
DEBUGSR_PRINTLN("AS: ES7243 Microphone."); DEBUGSR_PRINTLN("AS: ES7243 Microphone.");
audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF);
delay(100);
audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin);
break; break;
case 3: case 3:
DEBUGSR_PRINTLN("AS: SPH0645 Microphone"); DEBUGSR_PRINTLN("AS: SPH0645 Microphone");
audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF);
delay(100);
audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin);
break; break;
case 4: case 4:
DEBUGSR_PRINTLN("AS: Generic I2S Microphone with Master Clock"); DEBUGSR_PRINTLN("AS: Generic I2S Microphone with Master Clock");
audioSource = new I2SSourceWithMasterClock(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new I2SSourceWithMasterClock(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF);
delay(100);
audioSource->initialize(mclkPin, i2swsPin, i2ssdPin, i2sckPin);
break; break;
case 5: case 5:
DEBUGSR_PRINTLN("AS: I2S PDM Microphone"); DEBUGSR_PRINTLN("AS: I2S PDM Microphone");
audioSource = new I2SPdmSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new I2SPdmSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF);
delay(100);
audioSource->initialize(i2swsPin, i2ssdPin);
break; break;
case 0: case 0:
default: default:
@ -823,18 +819,17 @@ class AudioReactive : public Usermod {
// we don't do the down-shift by 16bit any more // we don't do the down-shift by 16bit any more
//audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, -4, 0x0FFF); // request upscaling to 16bit - still produces too much noise //audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, -4, 0x0FFF); // request upscaling to 16bit - still produces too much noise
audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0x0FFF); // keep at 12bit - less noise audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0x0FFF); // keep at 12bit - less noise
delay(100);
audioSource->initialize(audioPin);
break; break;
} }
delay(100);
audioSource->initialize();
delay(250); delay(250);
//pinMode(LED_BUILTIN, OUTPUT);
//sampling_period_us = round(1000000*(1.0/SAMPLE_RATE)); //sampling_period_us = round(1000000*(1.0/SAMPLE_RATE));
onUpdateBegin(false); // create FFT task
/*
// Define the FFT Task and lock it to core 0 // Define the FFT Task and lock it to core 0
xTaskCreatePinnedToCore( xTaskCreatePinnedToCore(
FFTcode, // Function to implement the task FFTcode, // Function to implement the task
@ -844,6 +839,7 @@ class AudioReactive : public Usermod {
1, // Priority of the task 1, // Priority of the task
&FFT_Task, // Task handle &FFT_Task, // Task handle
0); // Core where the task should run 0); // Core where the task should run
*/
} }
@ -1008,11 +1004,27 @@ class AudioReactive : public Usermod {
bool getUMData(um_data_t **data) { bool getUMData(um_data_t **data) {
if (!data) return false; // no pointer provided by caller -> exit if (!data) return false; // no pointer provided by caller -> exit
*data = &um_data; *data = um_data;
return true; return true;
} }
void onUpdateBegin(bool init) {
if (init) vTaskDelete(FFT_Task); // update is about to begin, remove task to prevent crash
else { // update has failed or create task requested
// Define the FFT Task and lock it to core 0
xTaskCreatePinnedToCore(
FFTcode, // Function to implement the task
"FFT", // Name of the task
5000, // Stack size in words
NULL, // Task input parameter
1, // Priority of the task
&FFT_Task, // Task handle
0); // Core where the task should run
}
}
/* /*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
@ -1098,7 +1110,7 @@ class AudioReactive : public Usermod {
JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject top = root.createNestedObject(FPSTR(_name));
JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); JsonObject amic = top.createNestedObject(FPSTR(_analogmic));
top["pin"] = audioPin; amic["pin"] = audioPin;
JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic));
dmic[F("type")] = dmType; dmic[F("type")] = dmType;

View File

@ -20,29 +20,6 @@
#define I2S_SAMPLE_DOWNSCALE_TO_16BIT #define I2S_SAMPLE_DOWNSCALE_TO_16BIT
#endif #endif
#ifndef MCLK_PIN
int mclkPin = 0;
#else
int mclkPin = MLCK_PIN;
#endif
#ifndef ES7243_ADDR
int addr_ES7243 = 0x13;
#else
int addr_ES7243 = ES7243_ADDR;
#endif
#ifndef ES7243_SDAPIN
int pin_ES7243_SDA = 18;
#else
int pin_ES7243_SDA = ES7243_SDAPIN;
#endif
#ifndef ES7243_SDAPIN
int pin_ES7243_SCL = 23;
#else
int pin_ES7243_SCL = ES7243_SCLPIN;
#endif
/* Interface class /* Interface class
AudioSource serves as base class for all microphone types AudioSource serves as base class for all microphone types
@ -50,7 +27,7 @@
which simplifies the caller code which simplifies the caller code
*/ */
class AudioSource { class AudioSource {
public: public:
/* All public methods are virtual, so they can be overridden /* All public methods are virtual, so they can be overridden
Everything but the destructor is also removed, to make sure each mic Everything but the destructor is also removed, to make sure each mic
Implementation provides its version of this function Implementation provides its version of this function
@ -61,7 +38,7 @@ public:
This function needs to take care of anything that needs to be done This function needs to take care of anything that needs to be done
before samples can be obtained from the microphone. before samples can be obtained from the microphone.
*/ */
virtual void initialize() = 0; virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {};
/* Deinitialize /* Deinitialize
Release all resources and deactivate any functionality that is used Release all resources and deactivate any functionality that is used
@ -76,26 +53,34 @@ public:
virtual void getSamples(double *buffer, uint16_t num_samples) = 0; virtual void getSamples(double *buffer, uint16_t num_samples) = 0;
/* Get an up-to-date sample without DC offset */ /* Get an up-to-date sample without DC offset */
virtual int getSampleWithoutDCOffset() = 0; virtual int getSampleWithoutDCOffset() { return _sampleNoDCOffset; };
protected: protected:
// Private constructor, to make sure it is not callable except from derived classes // Private constructor, to make sure it is not callable except from derived classes
AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : _sampleRate(sampleRate), _blockSize(blockSize), _sampleNoDCOffset(0), _dcOffset(0.0f), _shift(lshift), _mask(mask), _initialized(false) {}; AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
_sampleRate(sampleRate),
_blockSize(blockSize),
_sampleNoDCOffset(0),
_dcOffset(0.0f),
_shift(lshift),
_mask(mask),
_initialized(false)
{};
int _sampleRate; /* Microphone sampling rate */ int _sampleRate; // Microphone sampling rate
int _blockSize; /* I2S block size */ int _blockSize; // I2S block size
volatile int _sampleNoDCOffset; /* Up-to-date sample without DCOffset */ volatile int _sampleNoDCOffset; // Up-to-date sample without DCOffset
float _dcOffset; /* Rolling average DC offset */ float _dcOffset; // Rolling average DC offset
int16_t _shift; /* Shift obtained samples to the right (positive) or left(negative) by this amount */ int16_t _shift; // Shift obtained samples to the right (positive) or left(negative) by this amount
uint32_t _mask; /* Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed*/ uint32_t _mask; // Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed
bool _initialized; /* Gets set to true if initialization is successful */ bool _initialized; // Gets set to true if initialization is successful
}; };
/* Basic I2S microphone source /* Basic I2S microphone source
All functions are marked virtual, so derived classes can replace them All functions are marked virtual, so derived classes can replace them
*/ */
class I2SSource : public AudioSource { class I2SSource : public AudioSource {
public: public:
I2SSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : I2SSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
AudioSource(sampleRate, blockSize, lshift, mask) { AudioSource(sampleRate, blockSize, lshift, mask) {
_config = { _config = {
@ -108,6 +93,20 @@ public:
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = _blockSize .dma_buf_len = _blockSize
}; };
}
void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE) {
if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) {
if (!pinManager.allocatePin(i2swsPin, true, PinOwner::DigitalMic) ||
!pinManager.allocatePin(i2ssdPin, true, PinOwner::DigitalMic)) {
return;
}
}
// i2ssckPin needs special treatment, since it might be unused on PDM mics
if (i2sckPin != I2S_PIN_NO_CHANGE) {
if (!pinManager.allocatePin(i2sckPin, true, PinOwner::DigitalMic)) return;
}
_pinConfig = { _pinConfig = {
.bck_io_num = i2sckPin, .bck_io_num = i2sckPin,
@ -115,23 +114,6 @@ public:
.data_out_num = I2S_PIN_NO_CHANGE, .data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = i2ssdPin .data_in_num = i2ssdPin
}; };
};
virtual void initialize() {
if (!pinManager.allocatePin(i2swsPin, true, PinOwner::DigitalMic) ||
!pinManager.allocatePin(i2ssdPin, true, PinOwner::DigitalMic)) {
return;
}
// i2ssckPin needs special treatment, since it might be unused on PDM mics
if (i2sckPin != -1) {
if (!pinManager.allocatePin(i2sckPin, true, PinOwner::DigitalMic))
return;
}
esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
if (err != ESP_OK) { if (err != ESP_OK) {
@ -148,23 +130,20 @@ public:
_initialized = true; _initialized = true;
} }
virtual void deinitialize() { void deinitialize() {
_initialized = false; _initialized = false;
esp_err_t err = i2s_driver_uninstall(I2S_NUM_0); esp_err_t err = i2s_driver_uninstall(I2S_NUM_0);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err);
return; return;
} }
pinManager.deallocatePin(i2swsPin, PinOwner::DigitalMic); if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.ws_io_num, PinOwner::DigitalMic);
pinManager.deallocatePin(i2ssdPin, PinOwner::DigitalMic); if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.data_in_num, PinOwner::DigitalMic);
// i2ssckPin needs special treatment, since it might be unused on PDM mics if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.bck_io_num, PinOwner::DigitalMic);
if (i2sckPin != -1) {
pinManager.deallocatePin(i2sckPin, PinOwner::DigitalMic);
}
} }
virtual void getSamples(double *buffer, uint16_t num_samples) { void getSamples(double *buffer, uint16_t num_samples) {
if(_initialized) { if (_initialized) {
esp_err_t err; esp_err_t err;
size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */ size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */
I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */ I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */
@ -173,13 +152,13 @@ public:
_dcOffset = 0.0f; _dcOffset = 0.0f;
err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY); err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY);
if ((err != ESP_OK)){ if (err != ESP_OK) {
DEBUGSR_PRINTF("Failed to get samples: %d\n", err); DEBUGSR_PRINTF("Failed to get samples: %d\n", err);
return; return;
} }
// For correct operation, we need to read exactly sizeof(samples) bytes from i2s // For correct operation, we need to read exactly sizeof(samples) bytes from i2s
if(bytes_read != sizeof(newSamples)) { if (bytes_read != sizeof(newSamples)) {
DEBUGSR_PRINTF("Failed to get enough samples: wanted: %d read: %d\n", sizeof(newSamples), bytes_read); DEBUGSR_PRINTF("Failed to get enough samples: wanted: %d read: %d\n", sizeof(newSamples), bytes_read);
return; return;
} }
@ -213,11 +192,7 @@ public:
} }
} }
virtual int getSampleWithoutDCOffset() { protected:
return _sampleNoDCOffset;
}
protected:
i2s_config_t _config; i2s_config_t _config;
i2s_pin_config_t _pinConfig; i2s_pin_config_t _pinConfig;
}; };
@ -227,28 +202,29 @@ protected:
routing via the provided API, so we have to do it by hand routing via the provided API, so we have to do it by hand
*/ */
class I2SSourceWithMasterClock : public I2SSource { class I2SSourceWithMasterClock : public I2SSource {
public: public:
I2SSourceWithMasterClock(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : I2SSourceWithMasterClock(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
I2SSource(sampleRate, blockSize, lshift, mask) { I2SSource(sampleRate, blockSize, lshift, mask) {
}; };
virtual void initialize() { virtual void initialize(int8_t mclkPin, int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE) {
// Reserve the master clock pin // Reserve the master clock pin
if(!pinManager.allocatePin(mclkPin, true, PinOwner::DigitalMic)) { if(!pinManager.allocatePin(mclkPin, true, PinOwner::DigitalMic)) {
return; return;
} }
_routeMclk(); _mclkPin = mclkPin;
I2SSource::initialize(); _routeMclk(mclkPin);
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin);
} }
virtual void deinitialize() { virtual void deinitialize() {
// Release the master clock pin // Release the master clock pin
pinManager.deallocatePin(mclkPin, PinOwner::DigitalMic); pinManager.deallocatePin(_mclkPin, PinOwner::DigitalMic);
I2SSource::deinitialize(); I2SSource::deinitialize();
} }
protected:
void _routeMclk() { protected:
void _routeMclk(int8_t mclkPin) {
/* Enable the mclk routing depending on the selected mclk pin /* Enable the mclk routing depending on the selected mclk pin
Only I2S_NUM_0 is supported Only I2S_NUM_0 is supported
*/ */
@ -263,22 +239,28 @@ protected:
WRITE_PERI_REG(PIN_CTRL, 0xFF00); WRITE_PERI_REG(PIN_CTRL, 0xFF00);
} }
} }
private:
int8_t _mclkPin;
}; };
/* ES7243 Microphone /* ES7243 Microphone
This is an I2S microphone that requires ininitialization over This is an I2S microphone that requires ininitialization over
I2C before I2S data can be received I2C before I2S data can be received
*/ */
class ES7243 : public I2SSourceWithMasterClock { class ES7243 : public I2SSource {
private:
private:
// I2C initialization functions for ES7243 // I2C initialization functions for ES7243
void _es7243I2cBegin() { void _es7243I2cBegin() {
Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U); Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
} }
void _es7243I2cWrite(uint8_t reg, uint8_t val) { void _es7243I2cWrite(uint8_t reg, uint8_t val) {
Wire.beginTransmission(addr_ES7243); #ifndef ES7243_ADDR
Wire.beginTransmission(0x13);
#else
Wire.beginTransmission(ES7243_ADDR);
#endif
Wire.write((uint8_t)reg); Wire.write((uint8_t)reg);
Wire.write((uint8_t)val); Wire.write((uint8_t)val);
Wire.endTransmission(); Wire.endTransmission();
@ -295,29 +277,36 @@ private:
} }
public: public:
ES7243(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : ES7243(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
I2SSourceWithMasterClock(sampleRate, blockSize, lshift, mask) { I2SSource(sampleRate, blockSize, lshift, mask) {
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
}; };
void initialize() {
void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE) {
// Reserve SDA and SCL pins of the I2C interface // Reserve SDA and SCL pins of the I2C interface
if (!pinManager.allocatePin(pin_ES7243_SDA, true, PinOwner::DigitalMic) || if (!pinManager.allocatePin(sdaPin, true, PinOwner::DigitalMic) ||
!pinManager.allocatePin(pin_ES7243_SCL, true, PinOwner::DigitalMic)) { !pinManager.allocatePin(sclPin, true, PinOwner::DigitalMic)) {
return; return;
} }
pin_ES7243_SDA = sdaPin;
pin_ES7243_SCL = sclPin;
// First route mclk, then configure ADC over I2C, then configure I2S // First route mclk, then configure ADC over I2C, then configure I2S
_es7243InitAdc(); _es7243InitAdc();
I2SSourceWithMasterClock::initialize(); I2SSource::initialize();
} }
void deinitialize() { void deinitialize() {
// Release SDA and SCL pins of the I2C interface // Release SDA and SCL pins of the I2C interface
pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::DigitalMic); pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::DigitalMic);
pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::DigitalMic); pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::DigitalMic);
I2SSourceWithMasterClock::deinitialize(); I2SSource::deinitialize();
} }
private:
int8_t pin_ES7243_SDA;
int8_t pin_ES7243_SCL;
}; };
/* ADC over I2S Microphone /* ADC over I2S Microphone
@ -326,9 +315,9 @@ public:
without the need of manual timing of the samples without the need of manual timing of the samples
*/ */
class I2SAdcSource : public I2SSource { class I2SAdcSource : public I2SSource {
public: public:
I2SAdcSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : I2SAdcSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
I2SSource(sampleRate, blockSize, lshift, mask){ I2SSource(sampleRate, blockSize, lshift, mask) {
_config = { _config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = _sampleRate, .sample_rate = _sampleRate,
@ -341,15 +330,16 @@ public:
}; };
} }
void initialize() { void initialize(int8_t audioPin) {
if(!pinManager.allocatePin(audioPin, false, PinOwner::AnalogMic)) { if(!pinManager.allocatePin(audioPin, false, PinOwner::AnalogMic)) {
return; return;
} }
_audioPin = audioPin;
// Determine Analog channel. Only Channels on ADC1 are supported // Determine Analog channel. Only Channels on ADC1 are supported
int8_t channel = digitalPinToAnalogChannel(audioPin); int8_t channel = digitalPinToAnalogChannel(_audioPin);
if (channel > 9) { if (channel > 9) {
DEBUGSR_PRINTF("Incompatible GPIO used for audio in: %d\n", audioPin); DEBUGSR_PRINTF("Incompatible GPIO used for audio in: %d\n", _audioPin);
return; return;
} else { } else {
adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel)); adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel));
@ -367,8 +357,8 @@ public:
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUGSR_PRINTF("Failed to set i2s adc mode: %d\n", err); DEBUGSR_PRINTF("Failed to set i2s adc mode: %d\n", err);
return; return;
} }
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
// according to docs from espressif, the ADC needs to be started explicitly // according to docs from espressif, the ADC needs to be started explicitly
// fingers crossed // fingers crossed
@ -383,9 +373,8 @@ public:
} }
void getSamples(double *buffer, uint16_t num_samples) { void getSamples(double *buffer, uint16_t num_samples) {
/* Enable ADC. This has to be enabled and disabled directly before and /* Enable ADC. This has to be enabled and disabled directly before and
after sampling, otherwise Wifi dies * after sampling, otherwise Wifi dies
*/ */
if (_initialized) { if (_initialized) {
#if !defined(ARDUINO_ARCH_ESP32) #if !defined(ARDUINO_ARCH_ESP32)
@ -412,7 +401,7 @@ public:
} }
void deinitialize() { void deinitialize() {
pinManager.deallocatePin(audioPin, PinOwner::AnalogMic); pinManager.deallocatePin(_audioPin, PinOwner::AnalogMic);
_initialized = false; _initialized = false;
esp_err_t err; esp_err_t err;
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
@ -430,6 +419,9 @@ public:
return; return;
} }
} }
private:
int8_t _audioPin;
}; };
/* SPH0645 Microphone /* SPH0645 Microphone
@ -437,13 +429,12 @@ public:
special consideration. special consideration.
*/ */
class SPH0654 : public I2SSource { class SPH0654 : public I2SSource {
public:
public:
SPH0654(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : SPH0654(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
I2SSource(sampleRate, blockSize, lshift, mask){} I2SSource(sampleRate, blockSize, lshift, mask){}
void initialize() { void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin) {
I2SSource::initialize(); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin);
REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9)); 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);
} }
@ -454,20 +445,14 @@ public:
data line, to make it simpler to debug, use the WS pin as CLK and SD data line, to make it simpler to debug, use the WS pin as CLK and SD
pin as DATA pin as DATA
*/ */
class I2SPdmSource : public I2SSource { class I2SPdmSource : public I2SSource {
public:
public:
I2SPdmSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : I2SPdmSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) :
I2SSource(sampleRate, blockSize, lshift, mask) { I2SSource(sampleRate, blockSize, lshift, mask) {
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm _config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm
}
_pinConfig = { void initialize(uint8_t i2swsPin, uint8_t i2ssdPin) {
.bck_io_num = I2S_PIN_NO_CHANGE, // bck is unused in PDM mics I2SSource::initialize(i2swsPin, i2ssdPin, I2S_PIN_NO_CHANGE);
.ws_io_num = i2swsPin, // clk pin for PDM mic
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = i2ssdPin
};
} }
}; };

View File

@ -5569,7 +5569,7 @@ uint16_t WS2812FX::mode_2DAkemi(void) {
um_data_t *um_data; um_data_t *um_data;
if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
fftResult = um_data->ub8_data[1]; fftResult = (uint8_t*)um_data->u_data[1];
base = fftResult[0]/255.0f; base = fftResult[0]/255.0f;
} }

View File

@ -752,6 +752,8 @@ class WS2812FX {
getLengthPhysical(void), getLengthPhysical(void),
getFps(); getFps();
inline uint16_t getMinShowDelay() { return MIN_SHOW_DELAY; }
uint32_t uint32_t
now, now,
timebase, timebase,

View File

@ -216,54 +216,38 @@ int getSignalQuality(int rssi);
void WiFiEvent(WiFiEvent_t event); void WiFiEvent(WiFiEvent_t event);
//um_manager.cpp //um_manager.cpp
typedef enum UM_Data_Types {
UMT_BYTE = 0,
UMT_UINT16,
UMT_INT16,
UMT_UINT32,
UMT_INT32,
UMT_FLOAT,
UMT_DOUBLE,
UMT_BYTE_ARR,
UMT_UINT16_ARR,
UMT_INT16_ARR,
UMT_UINT32_ARR,
UMT_INT32_ARR,
UMT_FLOAT_ARR,
UMT_DOUBLE_ARR
} um_types_t;
typedef struct UM_Exchange_Data { typedef struct UM_Exchange_Data {
// should just use: size_t arr_size, void **arr_ptr, byte *ptr_type // should just use: size_t arr_size, void **arr_ptr, byte *ptr_type
size_t ub8_size; // size of ub8_data size_t u_size; // size of u_data array
uint8_t **ub8_data; // array of pointers to bytes (pointer can point to an array of bytes, depends on the usermod) um_types_t *u_type; // array of data types
size_t uw16_size; // size of uw16_data void **u_data; // array of pointers to data
uint16_t **uw16_data; // array of pointers to unsigned words
size_t uw32_size; // size of uw32_data
uint32_t **uw32_data; // array of pointers to unsigned long words
size_t ui32_size; // size of uw32_data
int32_t **ui32_data; // array of pointers to long words
size_t uf4_size; // size of ubf4_data
float **uf4_data; // array of pointers to floats
size_t uf8_size; // size of ubf4_data
double **uf8_data; // array of pointers to doubles
/*
uint8_t ub1, ub2, ub3, ub4; // 4 byte values
uint16_t ui1, ui2, *aui1, *aui2, *aui3; // 2 word values and 3 pointers to word arrays/values
int16_t ui3, ui4, *aui4, *aui5, *aui6; // 2 signed word values and 3 pointers to signed word arrays/values
uint32_t ul1, ul2; // 2 long word values
float uf1, uf2, uf3, *auf1, *auf2; // 3 float values and 2 pointers to float arrays/values
*/
UM_Exchange_Data() { UM_Exchange_Data() {
ub8_size = 0; u_size = 0;
uw16_size = 0; u_type = nullptr;
uw32_size = 0; u_data = nullptr;
ui32_size = 0;
uf4_size = 0;
uf8_size = 0;
/*
ub1 = ub2 = ub3 = ub4 = 0;
ui1 = ui2 = ui3 = ui4 = 0;
ul1 = ul2 = 0;
uf1 = uf2 = uf3 = 0.0f;
aui1 = aui2 = aui3 = nullptr;
aui4 = aui5 = aui6 = nullptr;
auf1 = auf2 = nullptr;
*/
} }
~UM_Exchange_Data() { ~UM_Exchange_Data() {
if (ub8_size && ub8_data ) delete[] ub8_data; if (u_type) delete[] u_type;
if (uw16_size && uw16_data) delete[] uw16_data; if (u_data) delete[] u_data;
if (uw32_size && uw32_data) delete[] uw32_data;
if (ui32_size && ui32_data) delete[] ui32_data;
if (uf4_size && uf4_data ) delete[] uf4_data;
if (uf8_size && uf8_data ) delete[] uf8_data;
} }
} um_data_t; } um_data_t;
const unsigned int um_data_size = sizeof(um_data_t); // about 64 bytes const unsigned int um_data_size = sizeof(um_data_t); // 12 bytes
class Usermod { class Usermod {
protected: protected:
@ -284,6 +268,7 @@ class Usermod {
virtual bool readFromConfig(JsonObject& obj) { return true; } // Note as of 2021-06 readFromConfig() now needs to return a bool, see usermod_v2_example.h virtual bool readFromConfig(JsonObject& obj) { return true; } // Note as of 2021-06 readFromConfig() now needs to return a bool, see usermod_v2_example.h
virtual void onMqttConnect(bool sessionPresent) {} virtual void onMqttConnect(bool sessionPresent) {}
virtual bool onMqttMessage(char* topic, char* payload) { return false; } virtual bool onMqttMessage(char* topic, char* payload) { return false; }
virtual void onUpdateBegin(bool) {}
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
}; };
@ -306,6 +291,7 @@ class UsermodManager {
bool readFromConfig(JsonObject& obj); bool readFromConfig(JsonObject& obj);
void onMqttConnect(bool sessionPresent); void onMqttConnect(bool sessionPresent);
bool onMqttMessage(char* topic, char* payload); bool onMqttMessage(char* topic, char* payload);
void onUpdateBegin(bool);
bool add(Usermod* um); bool add(Usermod* um);
Usermod* lookup(uint16_t mod_id); Usermod* lookup(uint16_t mod_id);
byte getModCount(); byte getModCount();

View File

@ -34,6 +34,8 @@ enum struct PinOwner : uint8_t {
DebugOut = 0x89, // 'Dbg' == debug output always IO1 DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2 DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32) HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
AnalogMic = 0x8C, // WLEDSR
DigitalMic = 0x8D, // WLEDSR
// Use UserMod IDs from const.h here // Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01 UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h" UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"
@ -53,7 +55,8 @@ enum struct PinOwner : uint8_t {
// #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h // #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h
// #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager // #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager
UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h" UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h"
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA // 0x17 // Usermod "quinled-an-penta.h" UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h"
UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE // 0x1E // Usermod: "audio_reactive.h"
}; };
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

View File

@ -40,6 +40,7 @@ bool UsermodManager::onMqttMessage(char* topic, char* payload) {
for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true; for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true;
return false; return false;
} }
void UsermodManager::onUpdateBegin(bool init) { for (byte i = 0; i < numMods; i++) ums[i]->onUpdateBegin(init); } // notify usermods that update is to begin
/* /*
* Enables usermods to lookup another Usermod. * Enables usermods to lookup another Usermod.

View File

@ -284,6 +284,7 @@ void initServer()
if(!index){ if(!index){
DEBUG_PRINTLN(F("OTA Update Start")); DEBUG_PRINTLN(F("OTA Update Start"));
WLED::instance().disableWatchdog(); WLED::instance().disableWatchdog();
usermods.onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
lastEditTime = millis(); // make sure PIN does not lock during update lastEditTime = millis(); // make sure PIN does not lock during update
#ifdef ESP8266 #ifdef ESP8266
Update.runAsync(true); Update.runAsync(true);
@ -297,6 +298,7 @@ void initServer()
} else { } else {
DEBUG_PRINTLN(F("Update Failed")); DEBUG_PRINTLN(F("Update Failed"));
WLED::instance().enableWatchdog(); WLED::instance().enableWatchdog();
usermods.onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
} }
} }
}); });