From 774cfb8cef40171a60312411dd0814fab1e8fb9e Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Mon, 19 Feb 2024 21:52:36 -0500 Subject: [PATCH 01/15] adding arduino examples to test interrupt timing --- .../GpioInterruptLogger.ino | 99 +++++++++++++++++++ .../InterruptBasedGpioToggle.ino | 17 ++++ 2 files changed, 116 insertions(+) create mode 100644 testing/GpioInterruptLogger/GpioInterruptLogger.ino create mode 100644 testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino diff --git a/testing/GpioInterruptLogger/GpioInterruptLogger.ino b/testing/GpioInterruptLogger/GpioInterruptLogger.ino new file mode 100644 index 00000000..1c08d19f --- /dev/null +++ b/testing/GpioInterruptLogger/GpioInterruptLogger.ino @@ -0,0 +1,99 @@ +// COde to read the timestamp of 1KHz wave +//#define DEBUG_SERIAL +#ifdef ARDUINO_FEATHER_ESP32 +#define PIN_IN 5 +#define PIN_OUT 18 +#elif defined ADAFRUIT_FEATHER_M0 +#define PIN_IN 24 +#define PIN_OUT 23 +#endif +#include +#include + +DoubleBufferFloat us_timeBuffer(50); +DoubleBufferFloat ms_timeBuffer(50); +#ifdef ARDUINO_FEATHER_ESP32 +void IRAM_ATTR isr() { + //counter++; + // push to Double Buffer + us_timeBuffer.push_back(micros()); + ms_timeBuffer.push_back(millis()); + digitalWrite(PIN_OUT, !digitalRead(PIN_OUT)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN +} +#elif defined ADAFRUIT_FEATHER_M0 +void isr() { + //counter++; + // push to Double Buffer + us_timeBuffer.push_back(micros()); + ms_timeBuffer.push_back(millis()); + digitalWrite(PIN_OUT, !digitalRead(PIN_OUT)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN +} + +#endif + +void setup() { + Serial.begin(2000000); + pinMode(PIN_IN, INPUT); + pinMode(PIN_OUT, OUTPUT); + #ifdef ARDUINO_FEATHER_ESP32 + attachInterrupt(PIN_IN, isr, RISING); + #elif defined ADAFRUIT_FEATHER_M0 + attachInterrupt( digitalPinToInterrupt( PIN_IN ), isr, RISING ); + #endif +} + +void loop() { + uint32_t timeNow = millis(); + float *us_timeData; float *ms_timeData; + uint32_t tu, tm; // dummy variables. + size_t dataAvailableuS, dataAvailablemS; + while(1) + { + if(millis() - timeNow > 200) + { + #ifdef DEBUG_SERIAL + Serial.println("timeNow > millis()"); + #endif + timeNow = millis(); + #ifdef DEBUG_SERIAL + Serial.println("swapping buffer"); + #endif + us_timeBuffer.swap(); // swap buffers before reading + ms_timeBuffer.swap(); // swap buffers before reading + #ifdef DEBUG_SERIAL + Serial.println("swapped buffer"); + #endif + // get the data + #ifdef DEBUG_SERIAL + Serial.println("getting data"); + #endif + dataAvailableuS = us_timeBuffer.getData(&us_timeData, &tu, false); + dataAvailablemS = ms_timeBuffer.getData(&ms_timeData, &tm, false); + #ifdef DEBUG_SERIAL + Serial.println("got data"); + #endif + if(dataAvailableuS == dataAvailablemS) + { + #ifdef DEBUG_SERIAL + Serial.print("dataAvailableuS"); Serial.println(dataAvailableuS); + #endif + for (uint8_t i = 0; i < dataAvailableuS; i++) + { + String str = String(ms_timeData[i]) + ":" + String(us_timeData[i]) ; + Serial.println(str); + } + } + else + { + Serial.print("dataAvailableuS: "); Serial.println(dataAvailableuS); + Serial.print("dataAvailablemS: "); Serial.println(dataAvailablemS); + Serial.println("mismatch in buffer lengths"); + //while(1); + } + } + else + { + // do ntohing + } + } +} \ No newline at end of file diff --git a/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino b/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino new file mode 100644 index 00000000..968d634c --- /dev/null +++ b/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino @@ -0,0 +1,17 @@ +// Code to create a sq waveform to test timing accuracy +#define OUT_PIN 5 +hw_timer_t *My_timer = NULL; +void IRAM_ATTR onTimer(){ +digitalWrite(OUT_PIN, !digitalRead(OUT_PIN)); +} +void setup() { +pinMode(OUT_PIN, OUTPUT); +My_timer = timerBegin(0, 80, true); +timerAttachInterrupt(My_timer, &onTimer, true); +// We want to create a wave with a time period of 6666uS (150Hz) +// Choose timer to be 1/2 of required time period +timerAlarmWrite(My_timer, 3333, true); +timerAlarmEnable(My_timer); //Just Enable +} +void loop() { +} \ No newline at end of file From 74830c9d8932fd25682992d0942db851ee19d67f Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Fri, 1 Mar 2024 19:46:40 -0500 Subject: [PATCH 02/15] added provision for 2 interrupts --- .../GpioInterruptLogger.ino | 126 +++++++++++------- 1 file changed, 75 insertions(+), 51 deletions(-) diff --git a/testing/GpioInterruptLogger/GpioInterruptLogger.ino b/testing/GpioInterruptLogger/GpioInterruptLogger.ino index 1c08d19f..823eacc9 100644 --- a/testing/GpioInterruptLogger/GpioInterruptLogger.ino +++ b/testing/GpioInterruptLogger/GpioInterruptLogger.ino @@ -1,55 +1,101 @@ // COde to read the timestamp of 1KHz wave //#define DEBUG_SERIAL #ifdef ARDUINO_FEATHER_ESP32 -#define PIN_IN 5 -#define PIN_OUT 18 +#define PIN_IN_1 5 +#define PIN_OUT_1 18 #elif defined ADAFRUIT_FEATHER_M0 -#define PIN_IN 24 -#define PIN_OUT 23 +#define PIN_IN_1 24 +#define PIN_OUT_1 23 +#define PIN_IN_2 22 +#define PIN_OUT_2 5 #endif #include #include -DoubleBufferFloat us_timeBuffer(50); -DoubleBufferFloat ms_timeBuffer(50); +DoubleBufferFloat us_timeBuffer_IN1(50); +DoubleBufferFloat ms_timeBuffer_IN1(50); +DoubleBufferFloat us_timeBuffer_IN2(50); +DoubleBufferFloat ms_timeBuffer_IN2(50); #ifdef ARDUINO_FEATHER_ESP32 -void IRAM_ATTR isr() { +void IRAM_ATTR isr_pin1() { //counter++; // push to Double Buffer - us_timeBuffer.push_back(micros()); - ms_timeBuffer.push_back(millis()); - digitalWrite(PIN_OUT, !digitalRead(PIN_OUT)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN + us_timeBuffer_IN1.push_back(micros()); + ms_timeBuffer_IN1.push_back(millis()); + digitalWrite(PIN_OUT_1, !digitalRead(PIN_OUT_1)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN_1 } #elif defined ADAFRUIT_FEATHER_M0 -void isr() { +void isr_pin1() { //counter++; // push to Double Buffer - us_timeBuffer.push_back(micros()); - ms_timeBuffer.push_back(millis()); - digitalWrite(PIN_OUT, !digitalRead(PIN_OUT)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN + us_timeBuffer_IN1.push_back(micros()); + ms_timeBuffer_IN1.push_back(millis()); + digitalWrite(PIN_OUT_1, !digitalRead(PIN_OUT_1)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN_1 } +void isr_pin2() { + //counter++; + // push to Double Buffer + us_timeBuffer_IN2.push_back(micros()); + ms_timeBuffer_IN2.push_back(millis()); + digitalWrite(PIN_OUT_2, !digitalRead(PIN_OUT_2)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN_2 +} #endif + +void getData(DoubleBufferFloat *buf_us, DoubleBufferFloat *buf_ms, String pin) +{ + float *us_timeData; float *ms_timeData; + uint32_t tu, tm; // dummy variables. + size_t dataAvailableuS, dataAvailablemS; + // get the data + #ifdef DEBUG_SERIAL + Serial.println("getting data"); + #endif + dataAvailableuS = buf_us->getData(&us_timeData, &tu, false); + dataAvailablemiS = buf_ms->getData(&ms_timeData, &tm, false); + #ifdef DEBUG_SERIAL + Serial.println("got data"); + #endif + if(dataAvailableuS == dataAvailablemS) + { + #ifdef DEBUG_SERIAL + Serial.print("dataAvailableuS"); Serial.println(dataAvailableuS); + #endif + for (uint8_t i = 0; i < dataAvailableuS; i++) + { + String str = pin + ":" + String(ms_timeData[i]) + ":" + String(us_timeData[i]) ; + Serial.println(str); + } + } + else + { + Serial.print("dataAvailableuS: "); Serial.println(dataAvailableuS); + Serial.print("dataAvailablemS: "); Serial.println(dataAvailablemS); + Serial.println("mismatch in buffer lengths"); + //while(1); + } +} void setup() { Serial.begin(2000000); - pinMode(PIN_IN, INPUT); - pinMode(PIN_OUT, OUTPUT); + pinMode(PIN_IN_1, INPUT); + pinMode(PIN_OUT_1, OUTPUT); + pinMode(PIN_IN_2, INPUT); + pinMode(PIN_OUT_2, OUTPUT); #ifdef ARDUINO_FEATHER_ESP32 - attachInterrupt(PIN_IN, isr, RISING); + attachInterrupt(PIN_IN_1, isr_pin1, RISING); #elif defined ADAFRUIT_FEATHER_M0 - attachInterrupt( digitalPinToInterrupt( PIN_IN ), isr, RISING ); + attachInterrupt( digitalPinToInterrupt( PIN_IN_1 ), isr_pin1, RISING ); + attachInterrupt( digitalPinToInterrupt( PIN_IN_2 ), isr_pin2, RISING ); #endif } void loop() { uint32_t timeNow = millis(); - float *us_timeData; float *ms_timeData; - uint32_t tu, tm; // dummy variables. - size_t dataAvailableuS, dataAvailablemS; + while(1) { - if(millis() - timeNow > 200) + if(millis() - timeNow > 100) { #ifdef DEBUG_SERIAL Serial.println("timeNow > millis()"); @@ -58,38 +104,16 @@ void loop() { #ifdef DEBUG_SERIAL Serial.println("swapping buffer"); #endif - us_timeBuffer.swap(); // swap buffers before reading - ms_timeBuffer.swap(); // swap buffers before reading + us_timeBuffer_IN1.swap(); // swap buffers before reading + ms_timeBuffer_IN1.swap(); // swap buffers before reading + us_timeBuffer_IN2.swap(); // swap buffers before reading + ms_timeBuffer_IN2.swap(); // swap buffers before reading #ifdef DEBUG_SERIAL Serial.println("swapped buffer"); #endif - // get the data - #ifdef DEBUG_SERIAL - Serial.println("getting data"); - #endif - dataAvailableuS = us_timeBuffer.getData(&us_timeData, &tu, false); - dataAvailablemS = ms_timeBuffer.getData(&ms_timeData, &tm, false); - #ifdef DEBUG_SERIAL - Serial.println("got data"); - #endif - if(dataAvailableuS == dataAvailablemS) - { - #ifdef DEBUG_SERIAL - Serial.print("dataAvailableuS"); Serial.println(dataAvailableuS); - #endif - for (uint8_t i = 0; i < dataAvailableuS; i++) - { - String str = String(ms_timeData[i]) + ":" + String(us_timeData[i]) ; - Serial.println(str); - } - } - else - { - Serial.print("dataAvailableuS: "); Serial.println(dataAvailableuS); - Serial.print("dataAvailablemS: "); Serial.println(dataAvailablemS); - Serial.println("mismatch in buffer lengths"); - //while(1); - } + getData(&us_timeBuffer_IN1, &ms_timeBuffer_IN1, String("PIN_1")); + getData(&us_timeBuffer_IN2, &ms_timeBuffer_IN2, String("PIN_2")); + } else { From 1f683d846f4fc036aeefea8dc771817e99c40d1b Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Fri, 1 Mar 2024 19:47:28 -0500 Subject: [PATCH 03/15] init tweak to make it work on 2 cores --- EmotiBit.cpp | 60 ++++++++++++++++++++++++++++++++++------------------ EmotiBit.h | 9 +++++--- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 8b2ed0ee..b0566ed6 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -5,7 +5,7 @@ EmotiBit* myEmotiBit = nullptr; void(*onInterruptCallback)(void); - +SemaphoreHandle_t _xMutex; #ifdef ARDUINO_FEATHER_ESP32 TaskHandle_t EmotiBitDataAcquisition; hw_timer_t * timer = NULL; @@ -1099,17 +1099,17 @@ uint8_t EmotiBit::setup(String firmwareVariant) startTimer(BASE_SAMPLING_FREQ); #elif defined ARDUINO_FEATHER_ESP32 // setup timer - timer = timerBegin(0, 80, true); // timer ticks with APB timer, which runs at 80MHz. This setting makes the timer tick every 1uS + //timer = timerBegin(0, 80, true); // timer ticks with APB timer, which runs at 80MHz. This setting makes the timer tick every 1uS // Attach onTimer function to our timer. - timerAttachInterrupt(timer, &onTimer, true); + //timerAttachInterrupt(timer, &onTimer, true); // Set alarm to call onTimer function (value in microseconds). // Repeat the alarm (third parameter) - timerAlarmWrite(timer, 1000000 / BASE_SAMPLING_FREQ, true); + //timerAlarmWrite(timer, 1000000 / BASE_SAMPLING_FREQ, true); // Start an alarm - timerAlarmEnable(timer); + //timerAlarmEnable(timer); attachToCore(&ReadSensors, this); #endif @@ -1597,6 +1597,7 @@ void EmotiBit::updateButtonPress() uint8_t EmotiBit::update() { + if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_1, HIGH); if (testingMode == TestingMode::FACTORY_TEST) { processFactoryTestMessages(); @@ -1671,7 +1672,7 @@ uint8_t EmotiBit::update() if (millis() - dataSendTimer > DATA_SEND_INTERVAL) { dataSendTimer = millis(); - + if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, HIGH); if (_sendTestData) @@ -1698,6 +1699,7 @@ uint8_t EmotiBit::update() bufferOverflowTest(); } } + if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, LOW); } // Hibernate after writing data @@ -1729,7 +1731,7 @@ uint8_t EmotiBit::update() sleep(); } } - + if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_1, LOW); // ToDo: implement logic to determine return val return 0; } @@ -1842,7 +1844,7 @@ int8_t EmotiBit::updateThermopileData() { #ifdef DEBUG_THERM_UPDATE Serial.println("updateThermopileData"); #endif - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_1, HIGH); + // Thermopile int8_t status = 0; uint32_t timestamp; @@ -1871,12 +1873,11 @@ int8_t EmotiBit::updateThermopileData() { #endif if (thermStatus == MLX90632::status::SENSOR_SUCCESS) { - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, HIGH); + timestamp = millis(); status = status | therm0AMB.push_back(AMB, &(timestamp)); status = status | therm0Sto.push_back(Sto, &(timestamp)); thermopile.startRawSensorValues(thermStatus); - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, LOW); } else { @@ -1897,7 +1898,7 @@ int8_t EmotiBit::updateThermopileData() { } _thermReadFinishedTime = micros(); - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_1, LOW); + return status; } @@ -2234,8 +2235,11 @@ bool EmotiBit::processThermopileData() // Swap buffers with minimal delay to avoid size mismatch unsigned long int swapStart = micros(); + //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY therm0AMB.swap(); therm0Sto.swap(); + //xSemaphoreGive( _xMutex ); + unsigned long int swapEnd = micros(); //Serial.println("swap: " + String(swapEnd - swapStart)); @@ -2287,7 +2291,9 @@ bool EmotiBit::processThermopileData() // if dummy data was stored if (dataAMB[i] == -2 && dataSto[i] == -2) { + //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->swap(); + //xSemaphoreGive( _xMutex ); return true; //return dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->getData(data, timestamp); } @@ -2351,7 +2357,9 @@ bool EmotiBit::processThermopileData() therm0Sto.getOverflowCount(DoubleBufferFloat::BufferSelector::OUT) ) ); + //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->swap(); + //xSemaphoreGive( _xMutex ); // ToDo: implement logic to determine return val return true; } @@ -3330,7 +3338,9 @@ void EmotiBit::processData() } else { + //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY dataDoubleBuffers[t]->swap(); + //xSemaphoreGive( _xMutex ); //Serial.print(String(t) + ","); } } @@ -4420,9 +4430,9 @@ void attachToCore(void(*readFunction)(void*), EmotiBit*e) "EmotiBitDataAcquisition", /* name of task. */ 10000, /* Stack size of task */ NULL, /* parameter of the task */ - configMAX_PRIORITIES - 1, /* priority of the task */ + 5, /* priority of the task */ &EmotiBitDataAcquisition, /* Task handle to keep track of created task */ - 1); /* pin task to core 0 */ + 0); /* pin task to core 0 */ delay(500); } @@ -4445,18 +4455,26 @@ void ReadSensors() void ReadSensors(void *pvParameters) { Serial.print("The data acquisition is executing on core: "); Serial.println(xPortGetCoreID()); + uint32_t timeLast = micros(); while (1) // the function assigned to the second core should never return { - if (myEmotiBit != nullptr) + if(micros() - timeLast > 6666) { - myEmotiBit->readSensors(); - - } - else - { - Serial.println("EmotiBit is nullptr"); + timeLast = micros(); + if (myEmotiBit != nullptr) + { + //xSemaphoreTake( myEmotiBit->_xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + myEmotiBit->readSensors(); + //xSemaphoreGive( myEmotiBit->_xMutex ); + } + else + { + Serial.println("EmotiBit is nullptr"); + } + //vTaskSuspend(NULL); + vTaskDelay(pdMS_TO_TICKS(1)); } - vTaskSuspend(NULL); + } } diff --git a/EmotiBit.h b/EmotiBit.h index b3052220..709f6c05 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -50,14 +50,14 @@ class EmotiBit { - String firmware_version = "1.9.0"; + String firmware_version = "1.9.0.feat-2Core"; TestingMode testingMode = TestingMode::NONE; - const bool DIGITAL_WRITE_DEBUG = false; + const bool DIGITAL_WRITE_DEBUG = true; #if defined (ARDUINO_FEATHER_ESP32) - const uint8_t DEBUG_OUT_PIN_0 = 26; + const uint8_t DEBUG_OUT_PIN_0 = 21; const uint8_t DEBUG_OUT_PIN_1 = 33; const uint8_t DEBUG_OUT_PIN_2 = 15; #elif defined (ADAFRUIT_FEATHER_M0) @@ -425,6 +425,7 @@ class EmotiBit { DataType _serialData = DataType::length; volatile bool buttonPressed = false; bool startBufferOverflowTest = false; + void setupFailed(const String failureMode, int buttonPin = -1); bool setupSdCard(); @@ -673,6 +674,7 @@ class EmotiBit { const uint8_t SCOPE_TEST_PIN = A0; bool scopeTestPinOn = false; + }; @@ -684,6 +686,7 @@ void ReadSensors(); void onTimer(); void attachToCore(void(*readFunction)(void*), EmotiBit*e = nullptr); void ReadSensors(void* pvParameters); + #endif From 39e432ca5ffdcff7bc839ede14d83ff921c6a278 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Sat, 2 Mar 2024 00:47:24 -0500 Subject: [PATCH 04/15] working on 2 cores! moved acquisition on core 1 and process-update to core 0. Now WiFi does not interrupt acquisition task --- EmotiBit.cpp | 40 ++++++++++++++++++- EmotiBit.h | 6 ++- .../EmotiBit_stock_firmware.ino | 5 ++- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index b0566ed6..d717a5a0 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -1112,6 +1112,7 @@ uint8_t EmotiBit::setup(String firmwareVariant) //timerAlarmEnable(timer); attachToCore(&ReadSensors, this); + attachProcessToCore(&Process); #endif } @@ -4421,6 +4422,21 @@ void onTimer() { #ifdef ARDUINO_FEATHER_ESP32 +void attachProcessToCore(void(*readFunction)(void*)) +{ + //attachEmotiBit(e); + // assigning readSensors to second core + xTaskCreatePinnedToCore( + *readFunction, /* Task function. */ + "EmotiBitDataProcess", /* name of task. */ + 32000, /* Stack size of task */ + NULL, /* parameter of the task */ + 1, /* priority of the task */ + NULL, /* Task handle to keep track of created task */ + 0); /* pin task to core 0 */ + delay(500); +} + void attachToCore(void(*readFunction)(void*), EmotiBit*e) { attachEmotiBit(e); @@ -4430,9 +4446,9 @@ void attachToCore(void(*readFunction)(void*), EmotiBit*e) "EmotiBitDataAcquisition", /* name of task. */ 10000, /* Stack size of task */ NULL, /* parameter of the task */ - 5, /* priority of the task */ + configMAX_PRIORITIES - 1, /* priority of the task */ &EmotiBitDataAcquisition, /* Task handle to keep track of created task */ - 0); /* pin task to core 0 */ + 1); /* pin task to core 0 */ delay(500); } @@ -4452,6 +4468,26 @@ void ReadSensors() } } #elif defined ARDUINO_FEATHER_ESP32 +void Process(void *pvParameter) +{ + Serial.print("The data process is executing on core: "); Serial.println(xPortGetCoreID()); + while(1) + { + if (myEmotiBit != nullptr) + { + //xSemaphoreTake( myEmotiBit->_xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + myEmotiBit->update(); + //xSemaphoreGive( myEmotiBit->_xMutex ); + } + else + { + Serial.println("EmotiBit is nullptr"); + } + //vTaskSuspend(NULL); + vTaskDelay(pdMS_TO_TICKS(1)); + } +} + void ReadSensors(void *pvParameters) { Serial.print("The data acquisition is executing on core: "); Serial.println(xPortGetCoreID()); diff --git a/EmotiBit.h b/EmotiBit.h index 709f6c05..5c36d85a 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -50,7 +50,7 @@ class EmotiBit { - String firmware_version = "1.9.0.feat-2Core"; + String firmware_version = "1.9.0.feat-2Core.0"; @@ -425,6 +425,7 @@ class EmotiBit { DataType _serialData = DataType::length; volatile bool buttonPressed = false; bool startBufferOverflowTest = false; + bool _freeToSleep = false; void setupFailed(const String failureMode, int buttonPin = -1); @@ -686,7 +687,8 @@ void ReadSensors(); void onTimer(); void attachToCore(void(*readFunction)(void*), EmotiBit*e = nullptr); void ReadSensors(void* pvParameters); - +void Process(void* pvParameters); +void attachProcessToCore(void(*readFunction)(void*)); #endif diff --git a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino index c9fbebe0..f2b7cdca 100644 --- a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino +++ b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino @@ -52,7 +52,9 @@ void setup() void loop() { //Serial.println("emotibit.update()"); - emotibit.update(); + //emotibit.update(); + vTaskDelete(NULL); + /* size_t dataAvailable = emotibit.readData(EmotiBit::DataType::PPG_GREEN, &data[0], dataSize); if (dataAvailable > 0) @@ -70,4 +72,5 @@ void loop() } } } + */ } \ No newline at end of file From 74564baea018d5ffeb37a3bebf61d489c8196f87 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Sat, 2 Mar 2024 01:10:36 -0500 Subject: [PATCH 05/15] sleep added with wifi off --- EmotiBit.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index d717a5a0..52526d58 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -1672,6 +1672,7 @@ uint8_t EmotiBit::update() static uint32_t dataSendTimer = millis(); if (millis() - dataSendTimer > DATA_SEND_INTERVAL) { + _freeToSleep = false; dataSendTimer = millis(); if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, HIGH); @@ -1701,6 +1702,7 @@ uint8_t EmotiBit::update() } } if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, LOW); + _freeToSleep = true; } // Hibernate after writing data @@ -4508,7 +4510,16 @@ void ReadSensors(void *pvParameters) Serial.println("EmotiBit is nullptr"); } //vTaskSuspend(NULL); - vTaskDelay(pdMS_TO_TICKS(1)); + if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) + { + esp_sleep_enable_timer_wakeup(6000); // every 6.6mS to maintain 150Hz sampling + esp_light_sleep_start(); // start light sleep + } + else + { + vTaskDelay(pdMS_TO_TICKS(1)); + } + //vTaskDelay(pdMS_TO_TICKS(1)); } } From 6a49e33e25eb7be2284d1ffee501e9f8a2339a8b Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Sat, 2 Mar 2024 22:29:18 -0500 Subject: [PATCH 06/15] remapped DIO toggle section. experimenting with sleep time --- EmotiBit.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 52526d58..b60612d2 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -2956,7 +2956,7 @@ void EmotiBit::readSensors() #ifdef DEBUG_GET_DATA Serial.println("readSensors()"); #endif // DEBUG - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_0, HIGH); + static unsigned long readSensorsBegin = micros(); if (_debugMode) @@ -3236,7 +3236,7 @@ void EmotiBit::readSensors() } if (acquireData.debug) pushData(EmotiBit::DataType::DEBUG, micros() - readSensorsBegin); // Add readSensors processing duration to debugBuffer - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_0, LOW); + } void EmotiBit::processHeartRate() @@ -4502,7 +4502,9 @@ void ReadSensors(void *pvParameters) if (myEmotiBit != nullptr) { //xSemaphoreTake( myEmotiBit->_xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); myEmotiBit->readSensors(); + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); //xSemaphoreGive( myEmotiBit->_xMutex ); } else @@ -4512,8 +4514,10 @@ void ReadSensors(void *pvParameters) //vTaskSuspend(NULL); if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) { - esp_sleep_enable_timer_wakeup(6000); // every 6.6mS to maintain 150Hz sampling + esp_sleep_enable_timer_wakeup(4500); // every 6.6mS to maintain 150Hz sampling + //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); esp_light_sleep_start(); // start light sleep + //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); } else { From 134c6515ebfe04621448682f876d42e69d42bbc7 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Fri, 8 Mar 2024 07:06:20 -0500 Subject: [PATCH 07/15] moved both acquisition and update tasks to core 1 for testing --- EmotiBit.cpp | 42 ++++++++++++------- EmotiBit.h | 4 +- .../EmotiBit_stock_firmware.ino | 9 ++-- EmotiBit_stock_firmware/platformio.ini | 2 +- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index b60612d2..26fd63ab 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -1112,7 +1112,7 @@ uint8_t EmotiBit::setup(String firmwareVariant) //timerAlarmEnable(timer); attachToCore(&ReadSensors, this); - attachProcessToCore(&Process); + //attachProcessToCore(&Process); #endif } @@ -1598,7 +1598,7 @@ void EmotiBit::updateButtonPress() uint8_t EmotiBit::update() { - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_1, HIGH); + if (testingMode == TestingMode::FACTORY_TEST) { processFactoryTestMessages(); @@ -1667,15 +1667,15 @@ uint8_t EmotiBit::update() // NOTE:: An older firmware, v1.2.86 needs to be used to write EDA correction values to V3 (OTP) and below. // Newer FW version can read both NVMs (EEPROM and OTP), but only have write ability for EEPROM - + // Handle data buffer reading and sending static uint32_t dataSendTimer = millis(); if (millis() - dataSendTimer > DATA_SEND_INTERVAL) { + _freeToSleep = false; dataSendTimer = millis(); - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, HIGH); - + if (_sendTestData) { @@ -1701,7 +1701,7 @@ uint8_t EmotiBit::update() bufferOverflowTest(); } } - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_2, LOW); + _freeToSleep = true; } @@ -1734,7 +1734,7 @@ uint8_t EmotiBit::update() sleep(); } } - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_1, LOW); + // ToDo: implement logic to determine return val return 0; } @@ -4433,9 +4433,9 @@ void attachProcessToCore(void(*readFunction)(void*)) "EmotiBitDataProcess", /* name of task. */ 32000, /* Stack size of task */ NULL, /* parameter of the task */ - 1, /* priority of the task */ + tskIDLE_PRIORITY, /* priority of the task */ NULL, /* Task handle to keep track of created task */ - 0); /* pin task to core 0 */ + 1); /* pin task to core 0 */ delay(500); } @@ -4494,6 +4494,7 @@ void ReadSensors(void *pvParameters) { Serial.print("The data acquisition is executing on core: "); Serial.println(xPortGetCoreID()); uint32_t timeLast = micros(); + uint32_t timeLastProcess_ms = millis(); while (1) // the function assigned to the second core should never return { if(micros() - timeLast > 6666) @@ -4512,16 +4513,27 @@ void ReadSensors(void *pvParameters) Serial.println("EmotiBit is nullptr"); } //vTaskSuspend(NULL); - if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) + if(millis() - timeLastProcess_ms > 50) { - esp_sleep_enable_timer_wakeup(4500); // every 6.6mS to maintain 150Hz sampling - //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); - esp_light_sleep_start(); // start light sleep - //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); + // every 50mS, run the process task + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, HIGH); + timeLastProcess_ms = millis(); + vTaskDelay(pdMS_TO_TICKS(1));// give update some time + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, LOW); } else { - vTaskDelay(pdMS_TO_TICKS(1)); + if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) + { + esp_sleep_enable_timer_wakeup(4500); // every 6.6mS to maintain 150Hz sampling + //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); + esp_light_sleep_start(); // start light sleep + //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); + } + else + { + vTaskDelay(pdMS_TO_TICKS(1)); + } } //vTaskDelay(pdMS_TO_TICKS(1)); } diff --git a/EmotiBit.h b/EmotiBit.h index 5c36d85a..7b0e0282 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -49,8 +49,8 @@ class EmotiBit { }; - - String firmware_version = "1.9.0.feat-2Core.0"; + // 1.9.0.feat-2Core.1 - update and acquisition on core 1. WiFi on core 0 + String firmware_version = "1.9.0.feat-2Core.1"; diff --git a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino index f2b7cdca..4da8053e 100644 --- a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino +++ b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino @@ -52,10 +52,9 @@ void setup() void loop() { //Serial.println("emotibit.update()"); - //emotibit.update(); - vTaskDelete(NULL); - /* - + emotibit.update(); + //vTaskDelete(NULL); + size_t dataAvailable = emotibit.readData(EmotiBit::DataType::PPG_GREEN, &data[0], dataSize); if (dataAvailable > 0) { @@ -72,5 +71,5 @@ void loop() } } } - */ + } \ No newline at end of file diff --git a/EmotiBit_stock_firmware/platformio.ini b/EmotiBit_stock_firmware/platformio.ini index d8e9cbd7..1cfeb420 100644 --- a/EmotiBit_stock_firmware/platformio.ini +++ b/EmotiBit_stock_firmware/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] extra_configs = - ../board_feather_m0.ini + ;../board_feather_m0.ini ../board_feather_esp32.ini src_dir = ./ lib_dir = ../../ From 765f5b03daf2e399e6684215d8459511a97c303c Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Fri, 8 Mar 2024 17:05:47 -0500 Subject: [PATCH 08/15] acquisition on core 1, update on core 0 with wifi, emotibit timing test. Emotibit timing checks out in analysis (gsheet) --- EmotiBit.cpp | 51 +++++++++++-------- EmotiBit.h | 2 +- .../EmotiBit_stock_firmware.ino | 3 +- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 26fd63ab..72c3d19f 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -1112,7 +1112,7 @@ uint8_t EmotiBit::setup(String firmwareVariant) //timerAlarmEnable(timer); attachToCore(&ReadSensors, this); - //attachProcessToCore(&Process); + attachProcessToCore(&Process); #endif } @@ -1672,7 +1672,7 @@ uint8_t EmotiBit::update() static uint32_t dataSendTimer = millis(); if (millis() - dataSendTimer > DATA_SEND_INTERVAL) { - + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, HIGH); _freeToSleep = false; dataSendTimer = millis(); @@ -1703,6 +1703,7 @@ uint8_t EmotiBit::update() } _freeToSleep = true; + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, LOW); } // Hibernate after writing data @@ -4433,9 +4434,9 @@ void attachProcessToCore(void(*readFunction)(void*)) "EmotiBitDataProcess", /* name of task. */ 32000, /* Stack size of task */ NULL, /* parameter of the task */ - tskIDLE_PRIORITY, /* priority of the task */ + 1, /* priority of the task */ NULL, /* Task handle to keep track of created task */ - 1); /* pin task to core 0 */ + 0); /* pin task to core 0 */ delay(500); } @@ -4494,7 +4495,7 @@ void ReadSensors(void *pvParameters) { Serial.print("The data acquisition is executing on core: "); Serial.println(xPortGetCoreID()); uint32_t timeLast = micros(); - uint32_t timeLastProcess_ms = millis(); + //uint32_t timeLastProcess_ms = millis(); // if update runs on the same core as acquisition while (1) // the function assigned to the second core should never return { if(micros() - timeLast > 6666) @@ -4513,28 +4514,38 @@ void ReadSensors(void *pvParameters) Serial.println("EmotiBit is nullptr"); } //vTaskSuspend(NULL); - if(millis() - timeLastProcess_ms > 50) - { - // every 50mS, run the process task - if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, HIGH); - timeLastProcess_ms = millis(); - vTaskDelay(pdMS_TO_TICKS(1));// give update some time - if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, LOW); - } - else - { + // if(millis() - timeLastProcess_ms > 50) + // { + // // every 50mS, run the process task + + // timeLastProcess_ms = millis(); + // vTaskDelay(pdMS_TO_TICKS(1));// give update some time + + // } + // else + // { if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) { - esp_sleep_enable_timer_wakeup(4500); // every 6.6mS to maintain 150Hz sampling - //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); - esp_light_sleep_start(); // start light sleep - //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); + int sleepTime; + uint32_t wakeupTimebuffer = 600; // takes cpu 600uS to wake up + if((timeLast + 6666) - micros() > wakeupTimebuffer) // more than a 1mS away from next acquisition pulse + { + sleepTime = (int)(timeLast + 6666) - (int)micros() - (int)wakeupTimebuffer; + //Serial.print("sleeping for(uS): "); Serial.println(sleepTime); + } + if(sleepTime > wakeupTimebuffer) + { + esp_sleep_enable_timer_wakeup(sleepTime); // every 6.6mS to maintain 150Hz sampling + //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); + esp_light_sleep_start(); // start light sleep + //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); + } } else { vTaskDelay(pdMS_TO_TICKS(1)); } - } + //} //vTaskDelay(pdMS_TO_TICKS(1)); } diff --git a/EmotiBit.h b/EmotiBit.h index 7b0e0282..be4ba422 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -50,7 +50,7 @@ class EmotiBit { // 1.9.0.feat-2Core.1 - update and acquisition on core 1. WiFi on core 0 - String firmware_version = "1.9.0.feat-2Core.1"; + String firmware_version = "1.9.0.feat-2Core.2"; diff --git a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino index 4da8053e..333e40e7 100644 --- a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino +++ b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino @@ -52,9 +52,10 @@ void setup() void loop() { //Serial.println("emotibit.update()"); - emotibit.update(); + //emotibit.update(); //vTaskDelete(NULL); + // gets run when acquisitoin gives a delay size_t dataAvailable = emotibit.readData(EmotiBit::DataType::PPG_GREEN, &data[0], dataSize); if (dataAvailable > 0) { From 50598f34e8a127268f2ad33ba25b076093a15268 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Fri, 8 Mar 2024 18:00:16 -0500 Subject: [PATCH 09/15] Added semaphores --- EmotiBit.cpp | 42 ++++++++++++------- .../EmotiBit_stock_firmware.ino | 2 +- EmotiBit_stock_firmware/platformio.ini | 2 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 72c3d19f..7d0cc6d8 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -5,10 +5,10 @@ EmotiBit* myEmotiBit = nullptr; void(*onInterruptCallback)(void); -SemaphoreHandle_t _xMutex; #ifdef ARDUINO_FEATHER_ESP32 TaskHandle_t EmotiBitDataAcquisition; hw_timer_t * timer = NULL; +SemaphoreHandle_t _xMutex; #endif EmotiBit::EmotiBit() @@ -1110,7 +1110,7 @@ uint8_t EmotiBit::setup(String firmwareVariant) // Start an alarm //timerAlarmEnable(timer); - + _xMutex = xSemaphoreCreateMutex(); attachToCore(&ReadSensors, this); attachProcessToCore(&Process); #endif @@ -2239,10 +2239,14 @@ bool EmotiBit::processThermopileData() // Swap buffers with minimal delay to avoid size mismatch unsigned long int swapStart = micros(); - //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif therm0AMB.swap(); therm0Sto.swap(); - //xSemaphoreGive( _xMutex ); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif unsigned long int swapEnd = micros(); //Serial.println("swap: " + String(swapEnd - swapStart)); @@ -2295,9 +2299,13 @@ bool EmotiBit::processThermopileData() // if dummy data was stored if (dataAMB[i] == -2 && dataSto[i] == -2) { - //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->swap(); - //xSemaphoreGive( _xMutex ); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif return true; //return dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->getData(data, timestamp); } @@ -2361,9 +2369,13 @@ bool EmotiBit::processThermopileData() therm0Sto.getOverflowCount(DoubleBufferFloat::BufferSelector::OUT) ) ); - //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->swap(); - //xSemaphoreGive( _xMutex ); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif // ToDo: implement logic to determine return val return true; } @@ -3342,9 +3354,13 @@ void EmotiBit::processData() } else { - //xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif dataDoubleBuffers[t]->swap(); - //xSemaphoreGive( _xMutex ); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif //Serial.print(String(t) + ","); } } @@ -4478,9 +4494,7 @@ void Process(void *pvParameter) { if (myEmotiBit != nullptr) { - //xSemaphoreTake( myEmotiBit->_xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY myEmotiBit->update(); - //xSemaphoreGive( myEmotiBit->_xMutex ); } else { @@ -4503,11 +4517,11 @@ void ReadSensors(void *pvParameters) timeLast = micros(); if (myEmotiBit != nullptr) { - //xSemaphoreTake( myEmotiBit->_xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); myEmotiBit->readSensors(); if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); - //xSemaphoreGive( myEmotiBit->_xMutex ); + xSemaphoreGive( _xMutex ); } else { diff --git a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino index 333e40e7..30ec7f6f 100644 --- a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino +++ b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino @@ -53,9 +53,9 @@ void loop() { //Serial.println("emotibit.update()"); //emotibit.update(); - //vTaskDelete(NULL); // gets run when acquisitoin gives a delay + // ToDo: assess time slice given to this task once ported to 2-core topology size_t dataAvailable = emotibit.readData(EmotiBit::DataType::PPG_GREEN, &data[0], dataSize); if (dataAvailable > 0) { diff --git a/EmotiBit_stock_firmware/platformio.ini b/EmotiBit_stock_firmware/platformio.ini index 1cfeb420..d8e9cbd7 100644 --- a/EmotiBit_stock_firmware/platformio.ini +++ b/EmotiBit_stock_firmware/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] extra_configs = - ;../board_feather_m0.ini + ../board_feather_m0.ini ../board_feather_esp32.ini src_dir = ./ lib_dir = ../../ From 3b222b613af1a1cea3cf25d5a3c26422e7e8a1b6 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Fri, 8 Mar 2024 19:06:34 -0500 Subject: [PATCH 10/15] some cleanup --- EmotiBit.cpp | 33 +++++++++++++++++++-------------- EmotiBit.h | 6 +++--- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 7d0cc6d8..efb8e7ba 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -1098,6 +1098,7 @@ uint8_t EmotiBit::setup(String firmwareVariant) //Serial.println("Starting interrupts"); startTimer(BASE_SAMPLING_FREQ); #elif defined ARDUINO_FEATHER_ESP32 + // ToDo: remove timer setup once 2 core functionality is verified. // setup timer //timer = timerBegin(0, 80, true); // timer ticks with APB timer, which runs at 80MHz. This setting makes the timer tick every 1uS @@ -1112,7 +1113,7 @@ uint8_t EmotiBit::setup(String firmwareVariant) //timerAlarmEnable(timer); _xMutex = xSemaphoreCreateMutex(); attachToCore(&ReadSensors, this); - attachProcessToCore(&Process); + attachUpdateToCore(&Update); #endif } @@ -4434,6 +4435,7 @@ void attachToInterruptTC3(void(*readFunction)(void), EmotiBit* e) } #elif defined ARDUINO_FEATHER_ESP32 +// ToDo: remove this once 2 core functionality is verified. void onTimer() { vTaskResume(EmotiBitDataAcquisition); } @@ -4441,7 +4443,7 @@ void onTimer() { #ifdef ARDUINO_FEATHER_ESP32 -void attachProcessToCore(void(*readFunction)(void*)) +void attachUpdateToCore(void(*readFunction)(void*)) { //attachEmotiBit(e); // assigning readSensors to second core @@ -4467,7 +4469,7 @@ void attachToCore(void(*readFunction)(void*), EmotiBit*e) NULL, /* parameter of the task */ configMAX_PRIORITIES - 1, /* priority of the task */ &EmotiBitDataAcquisition, /* Task handle to keep track of created task */ - 1); /* pin task to core 0 */ + 1); /* pin task to core 1 */ delay(500); } @@ -4487,9 +4489,9 @@ void ReadSensors() } } #elif defined ARDUINO_FEATHER_ESP32 -void Process(void *pvParameter) +void Update(void *pvParameter) { - Serial.print("The data process is executing on core: "); Serial.println(xPortGetCoreID()); + Serial.print("Update is executing on core: "); Serial.println(xPortGetCoreID()); while(1) { if (myEmotiBit != nullptr) @@ -4501,18 +4503,19 @@ void Process(void *pvParameter) Serial.println("EmotiBit is nullptr"); } //vTaskSuspend(NULL); - vTaskDelay(pdMS_TO_TICKS(1)); + vTaskDelay(pdMS_TO_TICKS(1)); // Delay added to give time to IDLE task } } void ReadSensors(void *pvParameters) { Serial.print("The data acquisition is executing on core: "); Serial.println(xPortGetCoreID()); + int BASE_TIME_PERIOD = (int)((float) 1000000 / (float) BASE_SAMPLING_FREQ); uint32_t timeLast = micros(); //uint32_t timeLastProcess_ms = millis(); // if update runs on the same core as acquisition while (1) // the function assigned to the second core should never return { - if(micros() - timeLast > 6666) + if(micros() - timeLast > BASE_TIME_PERIOD) { timeLast = micros(); if (myEmotiBit != nullptr) @@ -4541,23 +4544,25 @@ void ReadSensors(void *pvParameters) if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) { int sleepTime; - uint32_t wakeupTimebuffer = 600; // takes cpu 600uS to wake up - if((timeLast + 6666) - micros() > wakeupTimebuffer) // more than a 1mS away from next acquisition pulse + // ToDo: get more accurate buffer time + uint32_t wakeupTimebuffer = 600; // Empirically derived estimate. Time taken by CPU to wake up + // ToDo: move this into a calculateSleepTime() function + if((timeLast + BASE_TIME_PERIOD) - micros() > wakeupTimebuffer) // calculate time to sleep { - sleepTime = (int)(timeLast + 6666) - (int)micros() - (int)wakeupTimebuffer; - //Serial.print("sleeping for(uS): "); Serial.println(sleepTime); + sleepTime = (int)(timeLast + BASE_TIME_PERIOD) - (int)micros() - (int)wakeupTimebuffer; } + if(sleepTime > wakeupTimebuffer) { + // only sleep if we can wake up in time esp_sleep_enable_timer_wakeup(sleepTime); // every 6.6mS to maintain 150Hz sampling - //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, HIGH); esp_light_sleep_start(); // start light sleep - //if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_0, LOW); } } else { - vTaskDelay(pdMS_TO_TICKS(1)); + // ToDo: Schedule time give to idle task. Check min time before watchdog timer is activated. + vTaskDelay(pdMS_TO_TICKS(1)); // if not sleeping, give time to IDLE task } //} //vTaskDelay(pdMS_TO_TICKS(1)); diff --git a/EmotiBit.h b/EmotiBit.h index be4ba422..c992ee60 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -50,7 +50,7 @@ class EmotiBit { // 1.9.0.feat-2Core.1 - update and acquisition on core 1. WiFi on core 0 - String firmware_version = "1.9.0.feat-2Core.2"; + String firmware_version = "1.9.0.feat-2Core.3"; @@ -687,8 +687,8 @@ void ReadSensors(); void onTimer(); void attachToCore(void(*readFunction)(void*), EmotiBit*e = nullptr); void ReadSensors(void* pvParameters); -void Process(void* pvParameters); -void attachProcessToCore(void(*readFunction)(void*)); +void Update(void* pvParameters); +void attachUpdateToCore(void(*readFunction)(void*)); #endif From 460156848dbf5287ccae982e0495f28b270ecbfb Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Tue, 12 Mar 2024 11:00:53 -0400 Subject: [PATCH 11/15] fixed compile issue --- testing/GpioInterruptLogger/GpioInterruptLogger.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/GpioInterruptLogger/GpioInterruptLogger.ino b/testing/GpioInterruptLogger/GpioInterruptLogger.ino index 823eacc9..ff7e6f00 100644 --- a/testing/GpioInterruptLogger/GpioInterruptLogger.ino +++ b/testing/GpioInterruptLogger/GpioInterruptLogger.ino @@ -53,7 +53,7 @@ void getData(DoubleBufferFloat *buf_us, DoubleBufferFloat *buf_ms, String pin) Serial.println("getting data"); #endif dataAvailableuS = buf_us->getData(&us_timeData, &tu, false); - dataAvailablemiS = buf_ms->getData(&ms_timeData, &tm, false); + dataAvailablemS = buf_ms->getData(&ms_timeData, &tm, false); #ifdef DEBUG_SERIAL Serial.println("got data"); #endif From 036c310a30e8be58887c09a6fac6f8c5d4569cb6 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Wed, 20 Mar 2024 21:48:29 -0400 Subject: [PATCH 12/15] adding test codes that help in debugging --- .../GpioInterruptLogger.ino | 25 +++++++++++++++++-- .../InterruptBasedGpioToggle.ino | 20 +++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/testing/GpioInterruptLogger/GpioInterruptLogger.ino b/testing/GpioInterruptLogger/GpioInterruptLogger.ino index ff7e6f00..9044e679 100644 --- a/testing/GpioInterruptLogger/GpioInterruptLogger.ino +++ b/testing/GpioInterruptLogger/GpioInterruptLogger.ino @@ -1,4 +1,23 @@ -// COde to read the timestamp of 1KHz wave +/** + * @file GpioInterruptLogger + * + * @section description Description + * This example can be used to generate timestamps for external events. + * A timestamp in mS and uS is generated everytime an interrupt is triggered. + * + * @section circuit Circuit + * - This is written for Feather M0. Tested on Adafruit Feather M0. + * - Refer adafruit's documentation on pin details: https://learn.adafruit.com/adafruit-feather-m0-wifi-atwinc1500/pinouts + * - Use PIN_IN_1 and PIN_IN_2 as inputs to which external interrupt signals can be attached. + * - PIN_OUT_1 and PIN_OUT_2 toggle at half the frequeny input pins, respectively. (Useful if probing with an Digital Oscilloscope) + * + * @section author Author + * - Created by Nitin Nair for EmotiBit. + * + */ + + +// ToDO: fix for Feather ESP32. Looks like the double buffer implementation is crashing for ESP32. //#define DEBUG_SERIAL #ifdef ARDUINO_FEATHER_ESP32 #define PIN_IN_1 5 @@ -23,6 +42,8 @@ void IRAM_ATTR isr_pin1() { us_timeBuffer_IN1.push_back(micros()); ms_timeBuffer_IN1.push_back(millis()); digitalWrite(PIN_OUT_1, !digitalRead(PIN_OUT_1)); // toggle a pin everytime we get a rising edge. Half the freq of PIN_IN_1 + + // ToDo: implement logic for pin 2 } #elif defined ADAFRUIT_FEATHER_M0 void isr_pin1() { @@ -64,6 +85,7 @@ void getData(DoubleBufferFloat *buf_us, DoubleBufferFloat *buf_ms, String pin) #endif for (uint8_t i = 0; i < dataAvailableuS; i++) { + // ToDo: improve serial output pattern. String str = pin + ":" + String(ms_timeData[i]) + ":" + String(us_timeData[i]) ; Serial.println(str); } @@ -73,7 +95,6 @@ void getData(DoubleBufferFloat *buf_us, DoubleBufferFloat *buf_ms, String pin) Serial.print("dataAvailableuS: "); Serial.println(dataAvailableuS); Serial.print("dataAvailablemS: "); Serial.println(dataAvailablemS); Serial.println("mismatch in buffer lengths"); - //while(1); } } void setup() { diff --git a/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino b/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino index 968d634c..7d32d972 100644 --- a/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino +++ b/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino @@ -1,4 +1,20 @@ -// Code to create a sq waveform to test timing accuracy +/** + * @file InterruptBasedGpioToggle + * + * @section description Description + * This example toggles a GPIO using hw timer. + * Creates a sq waveform to test timing accuracy. + * This example was created to test the GpioInterruptLogger example. + * + * @section circuit Circuit + * - This is written for ESP32. Tested on Adafruit Feather ESP32 Huzzah. + * - Pin 5 generates the timer output. Refer Adafruit Pinout for pin location and details: https://learn.adafruit.com/assets/111179 + * + * @section author Author + * - Created by Nitin Nair for EmotiBit. + * + */ + #define OUT_PIN 5 hw_timer_t *My_timer = NULL; void IRAM_ATTR onTimer(){ @@ -8,7 +24,7 @@ void setup() { pinMode(OUT_PIN, OUTPUT); My_timer = timerBegin(0, 80, true); timerAttachInterrupt(My_timer, &onTimer, true); -// We want to create a wave with a time period of 6666uS (150Hz) +// We want to create a wave with a time period of 6666uS (150Hz, default data acuisition task frequency) // Choose timer to be 1/2 of required time period timerAlarmWrite(My_timer, 3333, true); timerAlarmEnable(My_timer); //Just Enable From 45435a48cd6ac1bd417aa37fe1e2dfb36f9f13f4 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Wed, 20 Mar 2024 22:47:21 -0400 Subject: [PATCH 13/15] Added minor changes to comments. deleted old code --- EmotiBit.cpp | 93 +++++++------------ EmotiBit.h | 5 +- .../EmotiBit_stock_firmware.ino | 10 +- 3 files changed, 39 insertions(+), 69 deletions(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index efb8e7ba..4d50c5ee 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -8,7 +8,7 @@ void(*onInterruptCallback)(void); #ifdef ARDUINO_FEATHER_ESP32 TaskHandle_t EmotiBitDataAcquisition; hw_timer_t * timer = NULL; -SemaphoreHandle_t _xMutex; +SemaphoreHandle_t _xMutex; ///< Semaphore to prevent concurrent access of double buffer between acquisition and update tasks #endif EmotiBit::EmotiBit() @@ -1098,21 +1098,8 @@ uint8_t EmotiBit::setup(String firmwareVariant) //Serial.println("Starting interrupts"); startTimer(BASE_SAMPLING_FREQ); #elif defined ARDUINO_FEATHER_ESP32 - // ToDo: remove timer setup once 2 core functionality is verified. - // setup timer - //timer = timerBegin(0, 80, true); // timer ticks with APB timer, which runs at 80MHz. This setting makes the timer tick every 1uS - - // Attach onTimer function to our timer. - //timerAttachInterrupt(timer, &onTimer, true); - - // Set alarm to call onTimer function (value in microseconds). - // Repeat the alarm (third parameter) - //timerAlarmWrite(timer, 1000000 / BASE_SAMPLING_FREQ, true); - - // Start an alarm - //timerAlarmEnable(timer); _xMutex = xSemaphoreCreateMutex(); - attachToCore(&ReadSensors, this); + attachReadSensorsToCore(&ReadSensors, this); attachUpdateToCore(&Update); #endif } @@ -1674,7 +1661,7 @@ uint8_t EmotiBit::update() if (millis() - dataSendTimer > DATA_SEND_INTERVAL) { if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, HIGH); - _freeToSleep = false; + _freeToSleep = false; // Sending data involves writing to storage. Dont sleep cores when writing to storage. dataSendTimer = millis(); @@ -4435,18 +4422,11 @@ void attachToInterruptTC3(void(*readFunction)(void), EmotiBit* e) } #elif defined ARDUINO_FEATHER_ESP32 -// ToDo: remove this once 2 core functionality is verified. -void onTimer() { - vTaskResume(EmotiBitDataAcquisition); -} -#endif - -#ifdef ARDUINO_FEATHER_ESP32 void attachUpdateToCore(void(*readFunction)(void*)) { - //attachEmotiBit(e); - // assigning readSensors to second core + // Creating a new task to run emotibit.update() + // Task runs on core 0, along with WiFi tasks. xTaskCreatePinnedToCore( *readFunction, /* Task function. */ "EmotiBitDataProcess", /* name of task. */ @@ -4458,10 +4438,11 @@ void attachUpdateToCore(void(*readFunction)(void*)) delay(500); } -void attachToCore(void(*readFunction)(void*), EmotiBit*e) +void attachReadSensorsToCore(void(*readFunction)(void*), EmotiBit*e) { attachEmotiBit(e); - // assigning readSensors to second core + // Creating a new task to run data acquisition + // Task runs on core 1, along with main loop. xTaskCreatePinnedToCore( *readFunction, /* Task function. */ "EmotiBitDataAcquisition", /* name of task. */ @@ -4502,7 +4483,6 @@ void Update(void *pvParameter) { Serial.println("EmotiBit is nullptr"); } - //vTaskSuspend(NULL); vTaskDelay(pdMS_TO_TICKS(1)); // Delay added to give time to IDLE task } } @@ -4512,8 +4492,7 @@ void ReadSensors(void *pvParameters) Serial.print("The data acquisition is executing on core: "); Serial.println(xPortGetCoreID()); int BASE_TIME_PERIOD = (int)((float) 1000000 / (float) BASE_SAMPLING_FREQ); uint32_t timeLast = micros(); - //uint32_t timeLastProcess_ms = millis(); // if update runs on the same core as acquisition - while (1) // the function assigned to the second core should never return + while (1) // the function should never return, otherwise causes an exception in RTOS { if(micros() - timeLast > BASE_TIME_PERIOD) { @@ -4530,42 +4509,32 @@ void ReadSensors(void *pvParameters) { Serial.println("EmotiBit is nullptr"); } - //vTaskSuspend(NULL); - // if(millis() - timeLastProcess_ms > 50) - // { - // // every 50mS, run the process task - - // timeLastProcess_ms = millis(); - // vTaskDelay(pdMS_TO_TICKS(1));// give update some time - - // } - // else - // { - if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) + + if(myEmotiBit->_freeToSleep && myEmotiBit->_emotiBitWiFi._wifiOff) + { + int sleepTime; + // ToDo: get more accurate buffer time + uint32_t wakeupTimebuffer = 600; // in uS. Empirically derived estimate. Time taken by CPU to wake up + // ToDo: move this into a calculateSleepTime() function + if((timeLast + BASE_TIME_PERIOD) - micros() > wakeupTimebuffer) { - int sleepTime; - // ToDo: get more accurate buffer time - uint32_t wakeupTimebuffer = 600; // Empirically derived estimate. Time taken by CPU to wake up - // ToDo: move this into a calculateSleepTime() function - if((timeLast + BASE_TIME_PERIOD) - micros() > wakeupTimebuffer) // calculate time to sleep - { - sleepTime = (int)(timeLast + BASE_TIME_PERIOD) - (int)micros() - (int)wakeupTimebuffer; - } + // calculate time to sleep + sleepTime = (int)(timeLast + BASE_TIME_PERIOD) - (int)micros() - (int)wakeupTimebuffer; + } - if(sleepTime > wakeupTimebuffer) - { - // only sleep if we can wake up in time - esp_sleep_enable_timer_wakeup(sleepTime); // every 6.6mS to maintain 150Hz sampling - esp_light_sleep_start(); // start light sleep - } - } - else + if(sleepTime > wakeupTimebuffer) { - // ToDo: Schedule time give to idle task. Check min time before watchdog timer is activated. - vTaskDelay(pdMS_TO_TICKS(1)); // if not sleeping, give time to IDLE task + // only sleep if we can wake up in time + esp_sleep_enable_timer_wakeup(sleepTime); // every 6.6mS to maintain 150Hz base sampling + esp_light_sleep_start(); // start light sleep } - //} - //vTaskDelay(pdMS_TO_TICKS(1)); + } + else + { + // ToDo: Schedule time give to idle task. + vTaskDelay(pdMS_TO_TICKS(1)); // if not sleeping, give time-slice to IDLE task + } + } } diff --git a/EmotiBit.h b/EmotiBit.h index c992ee60..3d4ee23e 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -50,7 +50,7 @@ class EmotiBit { // 1.9.0.feat-2Core.1 - update and acquisition on core 1. WiFi on core 0 - String firmware_version = "1.9.0.feat-2Core.3"; + String firmware_version = "1.9.0.feat-2Core.4"; @@ -684,8 +684,7 @@ void attachEmotiBit(EmotiBit*e = nullptr); void attachToInterruptTC3(void(*readFunction)(void), EmotiBit*e = nullptr); void ReadSensors(); #elif defined ARDUINO_FEATHER_ESP32 -void onTimer(); -void attachToCore(void(*readFunction)(void*), EmotiBit*e = nullptr); +void attachReadSensorsToCore(void(*readFunction)(void*), EmotiBit*e = nullptr); void ReadSensors(void* pvParameters); void Update(void* pvParameters); void attachUpdateToCore(void(*readFunction)(void*)); diff --git a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino index 30ec7f6f..09dbe51b 100644 --- a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino +++ b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino @@ -51,11 +51,13 @@ void setup() void loop() { - //Serial.println("emotibit.update()"); - //emotibit.update(); + // Main loop is attached to a different task spawned by default. In our current scheme, + // main loop runs run when acquisitoin task pasuses by calling vTaskDelay() - // gets run when acquisitoin gives a delay - // ToDo: assess time slice given to this task once ported to 2-core topology + // The update function has been moved to its separate task. + //emotibit.update(); + + // ToDo: assess time slice given to this task once ported to 2-core 2-task architecture size_t dataAvailable = emotibit.readData(EmotiBit::DataType::PPG_GREEN, &data[0], dataSize); if (dataAvailable > 0) { From 41688f3dfaaa8423c79c6b4f90aaacd44fa2e1b5 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Wed, 20 Mar 2024 22:51:26 -0400 Subject: [PATCH 14/15] removed unused code --- EmotiBit.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 4d50c5ee..5bdacb78 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -7,7 +7,6 @@ EmotiBit* myEmotiBit = nullptr; void(*onInterruptCallback)(void); #ifdef ARDUINO_FEATHER_ESP32 TaskHandle_t EmotiBitDataAcquisition; -hw_timer_t * timer = NULL; SemaphoreHandle_t _xMutex; ///< Semaphore to prevent concurrent access of double buffer between acquisition and update tasks #endif From edb773829b20a27db24699b61187308924741793 Mon Sep 17 00:00:00 2001 From: Nitin Nair Date: Wed, 20 Mar 2024 22:53:09 -0400 Subject: [PATCH 15/15] minor changes to comments. --- EmotiBit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EmotiBit.h b/EmotiBit.h index 3d4ee23e..ace6832b 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -49,7 +49,7 @@ class EmotiBit { }; - // 1.9.0.feat-2Core.1 - update and acquisition on core 1. WiFi on core 0 + String firmware_version = "1.9.0.feat-2Core.4";