diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 8b2ed0ee..5bdacb78 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -5,10 +5,9 @@ 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 EmotiBit::EmotiBit() @@ -1098,20 +1097,9 @@ uint8_t EmotiBit::setup(String firmwareVariant) //Serial.println("Starting interrupts"); 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 - - // 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); - - attachToCore(&ReadSensors, this); + _xMutex = xSemaphoreCreateMutex(); + attachReadSensorsToCore(&ReadSensors, this); + attachUpdateToCore(&Update); #endif } @@ -1597,6 +1585,7 @@ void EmotiBit::updateButtonPress() uint8_t EmotiBit::update() { + if (testingMode == TestingMode::FACTORY_TEST) { processFactoryTestMessages(); @@ -1665,14 +1654,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) { + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, HIGH); + _freeToSleep = false; // Sending data involves writing to storage. Dont sleep cores when writing to storage. dataSendTimer = millis(); - - + if (_sendTestData) { @@ -1698,6 +1688,9 @@ uint8_t EmotiBit::update() bufferOverflowTest(); } } + + _freeToSleep = true; + if (myEmotiBit->DIGITAL_WRITE_DEBUG) digitalWrite(myEmotiBit->DEBUG_OUT_PIN_1, LOW); } // Hibernate after writing data @@ -1842,7 +1835,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 +1864,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 +1889,7 @@ int8_t EmotiBit::updateThermopileData() { } _thermReadFinishedTime = micros(); - if (DIGITAL_WRITE_DEBUG) digitalWrite(DEBUG_OUT_PIN_1, LOW); + return status; } @@ -2234,8 +2226,15 @@ bool EmotiBit::processThermopileData() // Swap buffers with minimal delay to avoid size mismatch unsigned long int swapStart = micros(); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif therm0AMB.swap(); therm0Sto.swap(); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif + unsigned long int swapEnd = micros(); //Serial.println("swap: " + String(swapEnd - swapStart)); @@ -2287,7 +2286,13 @@ bool EmotiBit::processThermopileData() // if dummy data was stored if (dataAMB[i] == -2 && dataSto[i] == -2) { + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->swap(); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif return true; //return dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->getData(data, timestamp); } @@ -2351,7 +2356,13 @@ bool EmotiBit::processThermopileData() therm0Sto.getOverflowCount(DoubleBufferFloat::BufferSelector::OUT) ) ); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif dataDoubleBuffers[(uint8_t)EmotiBit::DataType::THERMOPILE]->swap(); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif // ToDo: implement logic to determine return val return true; } @@ -2945,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) @@ -3225,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() @@ -3330,7 +3341,13 @@ void EmotiBit::processData() } else { + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreTake( _xMutex, portMAX_DELAY ); // ToDo: look into implications of portAX_DELAY + #endif dataDoubleBuffers[t]->swap(); + #ifdef ARDUINO_FEATHER_ESP32 + xSemaphoreGive( _xMutex ); + #endif //Serial.print(String(t) + ","); } } @@ -4404,17 +4421,27 @@ void attachToInterruptTC3(void(*readFunction)(void), EmotiBit* e) } #elif defined ARDUINO_FEATHER_ESP32 -void onTimer() { - vTaskResume(EmotiBitDataAcquisition); -} -#endif -#ifdef ARDUINO_FEATHER_ESP32 +void attachUpdateToCore(void(*readFunction)(void*)) +{ + // 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. */ + 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) +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. */ @@ -4422,7 +4449,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); } @@ -4442,21 +4469,73 @@ void ReadSensors() } } #elif defined ARDUINO_FEATHER_ESP32 -void ReadSensors(void *pvParameters) +void Update(void *pvParameter) { - Serial.print("The data acquisition is executing on core: "); Serial.println(xPortGetCoreID()); - while (1) // the function assigned to the second core should never return + Serial.print("Update is executing on core: "); Serial.println(xPortGetCoreID()); + while(1) { if (myEmotiBit != nullptr) { - myEmotiBit->readSensors(); - + myEmotiBit->update(); } else { Serial.println("EmotiBit is nullptr"); } - vTaskSuspend(NULL); + 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(); + while (1) // the function should never return, otherwise causes an exception in RTOS + { + if(micros() - timeLast > BASE_TIME_PERIOD) + { + timeLast = micros(); + if (myEmotiBit != nullptr) + { + 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( _xMutex ); + } + else + { + Serial.println("EmotiBit is nullptr"); + } + + 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) + { + // 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 base sampling + esp_light_sleep_start(); // start light sleep + } + } + 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 b3052220..ace6832b 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.4"; 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,8 @@ class EmotiBit { DataType _serialData = DataType::length; volatile bool buttonPressed = false; bool startBufferOverflowTest = false; + bool _freeToSleep = false; + void setupFailed(const String failureMode, int buttonPin = -1); bool setupSdCard(); @@ -673,6 +675,7 @@ class EmotiBit { const uint8_t SCOPE_TEST_PIN = A0; bool scopeTestPinOn = false; + }; @@ -681,9 +684,10 @@ 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*)); #endif diff --git a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino index c9fbebe0..09dbe51b 100644 --- a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino +++ b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino @@ -51,9 +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() + + // 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) { @@ -70,4 +74,5 @@ void loop() } } } + } \ No newline at end of file diff --git a/testing/GpioInterruptLogger/GpioInterruptLogger.ino b/testing/GpioInterruptLogger/GpioInterruptLogger.ino new file mode 100644 index 00000000..9044e679 --- /dev/null +++ b/testing/GpioInterruptLogger/GpioInterruptLogger.ino @@ -0,0 +1,144 @@ +/** + * @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 +#define PIN_OUT_1 18 +#elif defined ADAFRUIT_FEATHER_M0 +#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_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_pin1() { + //counter++; + // push to Double Buffer + 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() { + //counter++; + // push to Double Buffer + 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); + dataAvailablemS = 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++) + { + // ToDo: improve serial output pattern. + 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"); + } +} +void setup() { + Serial.begin(2000000); + 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_1, isr_pin1, RISING); + #elif defined ADAFRUIT_FEATHER_M0 + attachInterrupt( digitalPinToInterrupt( PIN_IN_1 ), isr_pin1, RISING ); + attachInterrupt( digitalPinToInterrupt( PIN_IN_2 ), isr_pin2, RISING ); + #endif +} + +void loop() { + uint32_t timeNow = millis(); + + while(1) + { + if(millis() - timeNow > 100) + { + #ifdef DEBUG_SERIAL + Serial.println("timeNow > millis()"); + #endif + timeNow = millis(); + #ifdef DEBUG_SERIAL + Serial.println("swapping buffer"); + #endif + 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 + getData(&us_timeBuffer_IN1, &ms_timeBuffer_IN1, String("PIN_1")); + getData(&us_timeBuffer_IN2, &ms_timeBuffer_IN2, String("PIN_2")); + + } + 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..7d32d972 --- /dev/null +++ b/testing/InterruptBasedGpioToggle/InterruptBasedGpioToggle.ino @@ -0,0 +1,33 @@ +/** + * @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(){ +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, 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 +} +void loop() { +} \ No newline at end of file