Accel data logger @ 1000 Hz

Hello, currently working on a ESP32 Adafruit feather running FreeRTOS in an effort to log accel. data 1000 Hz. LIS3DH accel hooked up using I2C, SD card hooked up using SPI (Feather hat RTC+SD card). I’ve used the vtaskdelay successfully to get data at 100 to about 400 Hz when things start going bad. Ideally Task “GetData” gets data from the LIS3DH via I2C 5000Hz. Then writes to queue and TAsk “SDWrite” takes data via pointers to SD Card. I have a 3rd Task “SDflush” to run a file.flush() on the system to make sure its written. I can always get data but as it gets faster it writes the same values up to 16 times in a row and the fastest it achieves is 2000 usec (about 500 Hz).
Things I’ve tried:

  • remove vtaskdelay(): data timing jumps around and average Hz actually gets lower
  • keep an eye on Queue: Queue never seems to even get close to filling up, SDwrite task works pretty fast
  • log.flush(): adjust time, doesn’t seem to have an effect
  • removed size of data structure: didn’t seem to have any effect on lag

I think I may not be getting data fast enough, but data rate is set to 5000Hz and I2C should definitely be fast enough?

Here is a data file read out with vTaskDelay(1):
439540,0.00000,0.57475,9.31093
441540,0.00000,0.45980,9.31093
449541,-0.11495,0.57475,8.96608
449541,-0.11495,0.57475,8.96608
449541,-0.11495,0.57475,8.96608
449541,-0.11495,0.57475,8.96608
451536,-0.11495,0.57475,9.19598
453541,-0.11495,0.34485,9.31093
455541,-0.11495,0.22990,9.42588
457541,-0.22990,0.34485,9.31093
459541,-0.22990,0.34485,9.19598
461541,-0.22990,0.45980,9.42588
463541,-0.45980,0.34485,9.31093
465541,-0.11495,0.45980,9.54083
467541,-0.11495,0.57475,9.31093
469541,-0.11495,0.80465,9.42588
471541,0.11495,0.45980,9.42588
473541,-0.11495,0.34485,9.42588
481540,0.00000,0.34485,9.42588
481540,0.00000,0.34485,9.42588
481540,0.00000,0.34485,9.42588
481540,0.00000,0.34485,9.42588

You can see “hiccups” of same data logged.

// Data logger:  For measuring acceleration from LIS3DH
// Hardware:  Adafruit ESP32, Adalogger feather+RTC, 1x LIS3DH accels (Currently, 2x in future) 
// Created:  May 12 2020
// Updated:
// Uses SPI for SD, I2C for Accels, hoping for 1000 Hz. sampling 
// files are saves text files, csv = NNNNNNNN.TXT
// See ReadME and photos for additional hook up info

//Use ESP32 duo core
const int TaskCore1  = 1;
const int TaskCore0 = 0;

//Libraries
#include <Wire.h>
#include <SPI.h>
#include <SdFat.h>
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
#include <stdio.h>
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
//#include "freertos/Arduino_FreeRTOS.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
//#include "freertos/stream_buffer.h"
//------------------------------------------------------------------------------
// SD file definitions
const uint8_t sdChipSelect = 33;
SdFat sd;
SdFile file;
File logfile;
//------------------------------------------------------------------------------
// Queue definitions

// data type for Queue item
struct Data_t {
  uint32_t usec; 
  float valueX;
  float valueY;
  float valueZ;
} xData_t;

//Declare Queue data type for FreeRTOS
QueueHandle_t DataQueue = NULL;

//TickType_t pdMS_TO_TICKS(uint32_t millis);
//TickType_t intervalTicks = pdMS_TO_TICKS(1);
// interval between points in units of 1000 usec
const uint16_t intervalTicks = 1;

//------------------------------------------------------------------------------
// Accel Lis3dh definitions, I2C
// Sensor I2C 
Adafruit_LIS3DH lis = Adafruit_LIS3DH();

// Used for hardware & software SPI
// hardware SPI 1 LIS3DH->Feather:  Power to Vin, Gnd to Gnd, SCL->SCK, SDA->MOSI, SDO->MOSO, CS->CS 14/15
//#define LIS3DH_CS 14  //ESP32: 14/A6 , Cortex m0: 5, Use for upper accel, hbar, seatpost, etc.
//#define LIS3DH_CS2 15  //ESP32: 15/A8, Cortex m0: 9, Use for lower accel, axles, etc. 
// Sensor 1 
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS);
// Sensor 2 
//Adafruit_LIS3DH lis2 = Adafruit_LIS3DH(LIS3DH_CS2);

//------------------------------------------------------------------------------
// define two tasks for Sensor Data and SD Write
void TaskGetData( void *pvParameters );
void TaskSDWrite( void *pvParameters );
void TaskSDFlush( void *pvParameters );
//------------------------------------------------------------------------------

// Start the scheduler so the created tasks start executing. Need this for ESP32???
void vTaskStartScheduler();
   
// the setup function runs once when you press reset or power the board
void setup() {

  // initialize serial communication at 115200 bits per second:
  //Serial.begin(115200);

  //Outputs, Pins, Buttons, Etc. 
  pinMode(13, OUTPUT);  //set Built in LED to show writing on SD Card

  //ACCEL Setup and RUN
  if (! lis.begin(0x18)) {   // change this to 0x19 for alternative i2c address
  Serial.println("Couldnt start");
  while (1) yield();
  }
  Serial.println("LIS3DH found!");
  // Set accel range  
  lis.setRange(LIS3DH_RANGE_16_G);   // 2, 4, 8 or 16 G!
  //lis2.setRange(LIS3DH_RANGE_16_G);
  // Set DataRate
  lis.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); //OPTIONS:  LIS3DH_DATARATE_400_HZ, LIS3DH_DATARATE_LOWPOWER_1K6HZ, LIS3DH_DATARATE_LOWPOWER_5KHZ
  //lis2.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); 

  //Setup I2C speed
  //Wire.setClock(400000);

// SD CARD SETUP ====================================================================
// see if the card is present and can be initialized:  (Use highest SD clock possible, but lower if has error, 15 Mhz works, possible to go to to 25 Mhz if sample rate is low enough
if (!sd.begin(sdChipSelect, SD_SCK_MHZ(25))) {
  Serial.println("Card init. failed!");
  //error(2);
}

// Create filename scheme ====================================================================
  char filename[15];
  //  Setup filename to be appropriate for what you are testing
  strcpy(filename, "/DATA00.TXT");
  for (uint8_t i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! sd.exists(filename)) {
      break;
    }
  }

// Create file and prepare it ============================================================
  logfile = sd.open(filename, O_CREAT | O_WRITE ); // | O_TRUNC);
  if( ! logfile ) {
    Serial.print("Couldnt create "); 
    Serial.println(filename);
    //error(3);
  }
  Serial.print("Writing to "); 
  Serial.println(filename);

  pinMode(13, OUTPUT);
  Serial.println("Ready!");


  //Queue Setup
  DataQueue = xQueueCreate(5000, sizeof( &xData_t ));
  if(DataQueue == NULL){
     Serial.println("Error Creating the Queue");
   }
  
// Setup up Tasks and where to run ============================================================  
// Now set up tasks to run independently.
  xTaskCreatePinnedToCore(
    TaskGetData
    ,  "Get Data from Accel to Queue"   // A name just for humans
    ,  2048  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  3  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL 
    ,  TaskCore0);

  xTaskCreatePinnedToCore(
    TaskSDWrite
    ,  "Get Data from Queue"
    ,  2048 // Stack size
    ,  NULL
    ,  2  // Priority
    ,  NULL 
    ,  TaskCore1);

  xTaskCreatePinnedToCore(
    TaskSDFlush
    ,  "Write Data to Card"
    ,  1024 // Stack size
    ,  NULL
    ,  1  // Priority
    ,  NULL 
    ,  TaskCore1);
}

void loop()
{
  // Empty. Things are done in Tasks.
/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/
}

void TaskGetData(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  
  struct Data_t *pxPointerToxData_t;

  for (;;) // A Task shall never return or exit.
  {
    pxPointerToxData_t = &xData_t; 

    if(xQueueSend( DataQueue, (void *) &pxPointerToxData_t, 10 ) != pdPASS )  //portMAX_DELAY
      {
        Serial.println("xQueueSend is not working"); 
      }
      
    sensors_event_t event;
    lis.getEvent(&event);
    pxPointerToxData_t->usec = micros();
    pxPointerToxData_t->valueX = event.acceleration.x;
    pxPointerToxData_t->valueY = event.acceleration.y;
    pxPointerToxData_t->valueZ = event.acceleration.z;
    Serial.print(pxPointerToxData_t->usec); 
    Serial.print(',');
    Serial.print(pxPointerToxData_t->valueX,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->valueY,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->valueZ,5);
    Serial.println();
    
    //digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    //vTaskDelay(100);  // one tick delay (15ms) in between reads for stability
    //digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    //vTaskDelay(100);  // one tick delay (15ms) in between reads for stability
 
    vTaskDelay(intervalTicks);  // one tick delay (1000 uSec/1 mSec) in between reads for 1000 Hz reading 
    
  
  }
}
//------------------------------------------------------------------------------
void TaskSDWrite(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  struct Data_t xData_RCV, *pxData_RCV;
  
  for (;;)
  {

    if(DataQueue != NULL ) 
    {
      if( xQueueReceive( DataQueue, &( pxData_RCV ), 10 ) != pdPASS )   //portMAX_DELAY
      {
        Serial.println("xQueueRecieve is not working");
      }
        
      // print interval between points
      /*if (last) {
        logfile.print(p->usec - last);
      } else {
        logfile.write("NA");
      }
      last = p->usec;*/
      //for (uint8_t i = 0; i < 10; i++) { 
        logfile.print(pxData_RCV->usec);
        logfile.print(',');
        logfile.print(pxData_RCV->valueX,5);
        logfile.print(',');
        logfile.print(pxData_RCV->valueY,5);
        logfile.print(',');
        logfile.print(pxData_RCV->valueZ,5);
        logfile.println(); 
        /*Serial.print(pxData_RCV->usec); 
        Serial.print(',');
        Serial.print(pxData_RCV->valueX,5);
        Serial.print(',');
        Serial.print(pxData_RCV->valueY,5);
        Serial.print(',');
        Serial.print(pxData_RCV->valueZ,5);
        Serial.println();*/ 
      //}

        uint16_t FreeSpace = uxQueueSpacesAvailable( DataQueue ); 
        Serial.println(FreeSpace); 
      //logfile.flush();
      //uint8_t i = 0; 
    }
   }
}


//------------------------------------------------------------------------------
void TaskSDFlush(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

  for (;;)
  {
    logfile.flush();
    vTaskDelay( 1000 );
    Serial.println("Flushed file"); 
    
  }
}

You keep sending the same pointer to the queue. If two items end up in the queue because your log task doesn’t keep up, they will by definition be identical (same pointer in both queue slots).

Also, all of the serial.println statements are probably slowing you down. Have you considered how long it takes to serial.print based on your serial baud rate?

Forgot to mention - Trying to get even semi accurate 1000Hz using a vTaskDelay(1) with a 1000Hz tick is going to be hard/impossible. You need to rethink your time keeping.

Interrupt/Timer to fire each data collection or a higher tick rate (say 5000Hz) with vTaskDelayUntil(last execution + 5)

Pilot thanks so much for the feedback! I’ve made some adjustments and hopeful improvements, but i’ll have to understand your interrupt/Timer better. I left the Serial.prints in there just for debugging but turn them off during a normal run, because yes, they do slow things down. The code below is getting me results down to 2000 uSec (about 500 Hz) without it repeating values but i’m still seeing jumps of 10000 uSec here and there. I think if i could take advantage of ESP32 mutliple SPIs would be better now that i’m using SPI for everything (instead of I2C).

Typical result (notice jump around 262000 uSec):
249854,-1.1495,-0.1149
251853,-1.3794,-0.1149
253853,-1.0345,-0.1149
255853,-1.2644,-0.1149
257853,-1.3794,-0.2299
259853,-1.2644,-0.2299
261853,-1.2644,-0.3448
270196,-1.2644,-0.3448
272853,-1.2644,-0.1149
274853,-1.1495,-0.2299
276853,-1.1495,-0.1149
278853,-0.8046,0.0000
280854,-1.2644,-0.2299

[Code] // Data logger: For measuring acceleration from LIS3DH
// Hardware: Adafruit ESP32, Adalogger feather+RTC, 1x LIS3DH accels (Currently, 2x in future)
// Created: May 12 2020
// Updated:
// Uses SPI for SD, I2C for Accels, hoping for 1000 Hz. sampling
// files are saves text files, csv = NNNNNNNN.TXT
// See ReadME and photos for additional hook up info

//Use ESP32 duo core
const int TaskCore1 = 1;
const int TaskCore0 = 0;

//Libraries
#include <Wire.h>
#include <SPI.h>
#include “SdFat.h”
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
#include “stdio.h”
#include “esp_system.h” //This inclusion configures the peripherals in the ESP system.
//#include “freertos/Arduino_FreeRTOS.h”
#include “freertos/FreeRTOS.h”
#include “freertos/task.h”
#include “freertos/timers.h”
#include “freertos/event_groups.h”
#include “freertos/queue.h”
//#include “freertos/stream_buffer.h”
//------------------------------------------------------------------------------
// SD file definitions
const uint8_t sdChipSelect = 33;
SdFat sd;
SdFile file;
File logfile;
//------------------------------------------------------------------------------
// Queue definitions

// data type for Queue item
struct Data_t {
uint32_t usec;
//float value1X;
float value1Y;
//float value1Z;
//float value2X;
float value2Y;
//float value2Z;
} xData_t;

//Declare Queue data type for FreeRTOS
QueueHandle_t DataQueue = NULL;

// interval between points in units of 1000 usec
const uint16_t intervalTicks = 2;

//------------------------------------------------------------------------------
// Accel Lis3dh definitions, SPI or I2C
// Used for software SPI
//#define LIS3DH_CLK 32 //SCL
//#define LIS3DH_MISO 15 //SDO
//#define LIS3DH_MOSI 27 //SDA
// Used for hardware & software SPI
#define LIS3DH_CS 14 //ESP32: 14/A6 , Cortex m0: 5, Use for upper accel (Sensor 1!!!) = hbar, seatpost, etc.
#define LIS3DH_CS2 15 //ESP32: 15/A8, Cortex m0: 9, Use for lower accel (Sensor 2!!!) = axles, etc.
// software SPI
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS, LIS3DH_MOSI, LIS3DH_MISO, LIS3DH_CLK);

// hardware SPI 1 LIS3DH->Feather: Power to Vin, Gnd to Gnd, SCL->SCK, SDA->MOSO, SDO->MOSI, CS->CS 14/15
// Sensor 1 Hardware SPI
Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS);
// Sensor 2 Hardware SPI
Adafruit_LIS3DH lis2 = Adafruit_LIS3DH(LIS3DH_CS2);

// Sensor I2C
//Adafruit_LIS3DH lis = Adafruit_LIS3DH();

//------------------------------------------------------------------------------
// define two tasks for Sensor Data and SD Write
void TaskGetData( void *pvParameters );
void TaskSDWrite( void *pvParameters );
void TaskSDFlush( void *pvParameters );
void TaskSDClose( void *pvParameters );
//------------------------------------------------------------------------------

// Start the scheduler so the created tasks start executing. Need this for ESP32???
//void vTaskStartScheduler();

// the setup function runs once when you press reset or power the board
void setup() {

// initialize serial communication at 115200 bits per second:
Serial.begin(250000);

SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));

//Outputs, Pins, Buttons, Etc.
pinMode(13, OUTPUT); //set Built in LED to show writing on SD Card
pinMode(4, INPUT); //button to turn recording on/off

//ACCEL Setup and RUN
if (! lis.begin(0x18)) { // change this to 0x19 for alternative i2c address
Serial.println(“Couldnt start”);
while (1) yield();
}
Serial.println(“LIS3DH Sensor 1 found!”);

if (! lis2.begin(0x19)) { // Sensor 2, SDO is 3V
Serial.println(“Couldnt start Sensor 2”);
while (1) yield();
}
Serial.println(“LIS3DH Sensor 2 found!”);

// Set accel range
lis.setRange(LIS3DH_RANGE_16_G); // 2, 4, 8 or 16 G!
lis2.setRange(LIS3DH_RANGE_16_G);
// Set DataRate
lis.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); //OPTIONS: LIS3DH_DATARATE_400_HZ, LIS3DH_DATARATE_LOWPOWER_1K6HZ, LIS3DH_DATARATE_LOWPOWER_5KHZ
lis2.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ);

// SD CARD SETUP ====================================================================
// see if the card is present and can be initialized: (Use highest SD clock possible, but lower if has error, 15 Mhz works, possible to go to to 25 Mhz if sample rate is low enough
if (!sd.begin(sdChipSelect, SD_SCK_MHZ(15))) {
Serial.println(“Card init. failed!”);
//error(2);
}

// Create filename scheme ====================================================================
char filename[15];
// Setup filename to be appropriate for what you are testing
strcpy(filename, “/DATA00.TXT”);
for (uint8_t i = 0; i < 100; i++) {
filename[5] = ‘0’ + i/10;
filename[6] = ‘0’ + i%10;
// create if does not exist, do not open existing, write, sync after write
if (! sd.exists(filename)) {
break;
}
}

// Create file and prepare it ============================================================
logfile = sd.open(filename, O_CREAT | O_WRITE); //O_WRONLY | O_CREAT | O_EXCL ); // | O_TRUNC ));
if( ! logfile ) {
Serial.print("Couldnt create ");
Serial.println(filename);
//error(3);
}
Serial.print("Writing to ");
Serial.println(filename);

//Column labels
logfile.print(“Time”);
logfile.print(",");
logfile.print(“Sensor 1 X”);
logfile.print(",");
logfile.print(“Sensor 1 Y”);
logfile.print(",");
logfile.print(“Sensor 1 Z”);
logfile.print(",");
logfile.print(“Sensor 2 X”);
logfile.print(",");
logfile.print(“Sensor 2 Y”);
logfile.print(",");
logfile.print(“Sensor 2 Z”);
logfile.println();

//Queue Setup
DataQueue = xQueueCreate(10, sizeof( &xData_t ));
if(DataQueue == NULL){
Serial.println(“Error Creating the Queue”);
}

// Setup up Tasks and where to run ============================================================
// Now set up tasks to run independently.
xTaskCreatePinnedToCore(
TaskGetData
, “Get Data from Accel to Queue” // A name just for humans
, 10000 // This stack size can be checked & adjusted by reading the Stack Highwater
, NULL
, 4 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, NULL
, TaskCore1);

xTaskCreatePinnedToCore(
TaskSDWrite
, “Get Data from Queue”
, 10000 // Stack size
, NULL
, 3 // Priority
, NULL
, TaskCore1);

xTaskCreatePinnedToCore(
TaskSDFlush
, “Write Data to Card”
, 10000 // Stack size
, NULL
, 3 // Priority
, NULL
, TaskCore0);

xTaskCreatePinnedToCore(
TaskSDClose
, “Close the File”
, 10000 // Stack size
, NULL
, 3 // Priority
, NULL
, TaskCore0);
}

void loop()
{
// Empty. Things are done in Tasks.
/--------------------------------------------------/
/---------------------- Tasks ---------------------/
/--------------------------------------------------/
}

void TaskGetData(void *pvParameters) // This is a task.
{
(void) pvParameters;

struct Data_t *pxPointerToxData_t;

for (;:wink: // A Task shall never return or exit.
{
pxPointerToxData_t = &xData_t;

if(xQueueSend( DataQueue, (void *) &pxPointerToxData_t, 12 ) != pdPASS )  //portMAX_DELAY
  {
    //Serial.println("xQueueSend is not working"); 
  }
  
sensors_event_t event;
lis.getEvent(&event);
sensors_event_t event2;
lis2.getEvent(&event2);
pxPointerToxData_t->usec = micros();
pxPointerToxData_t->value1X = event.acceleration.x;
pxPointerToxData_t->value1Y = event.acceleration.y;
pxPointerToxData_t->value1Z = event.acceleration.z;
pxPointerToxData_t->value2X = event2.acceleration.x;
pxPointerToxData_t->value2Y = event2.acceleration.y;
pxPointerToxData_t->value2Z = event2.acceleration.z;
/*Serial.print(pxPointerToxData_t->usec); 
Serial.print(',');
Serial.print(pxPointerToxData_t->valueX,5);
Serial.print(',');
Serial.print(pxPointerToxData_t->valueY,5);
Serial.print(',');
Serial.print(pxPointerToxData_t->valueZ,5);
Serial.println();*/

//digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
//vTaskDelay(100);  // one tick delay (15ms) in between reads for stability
//digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
//vTaskDelay(100);  // one tick delay (15ms) in between reads for stability

vTaskDelay(intervalTicks);  // one tick delay (1000 uSec/1 mSec) in between reads for 1000 Hz reading 
}
vTaskDelete( NULL );

}
//------------------------------------------------------------------------------
void TaskSDWrite(void *pvParameters) // This is a task.
{
(void) pvParameters;
struct Data_t xData_RCV, *pxData_RCV;

for (;:wink:
{

if(DataQueue != NULL ) 
{
  if( xQueueReceive( DataQueue, &( pxData_RCV ), 12 ) != pdPASS )   //portMAX_DELAY
  {
    //Serial.println("xQueueRecieve is not working");
  }
    
  // print interval between points
  /*if (last) {
    logfile.print(p->usec - last);
  } else {
    logfile.write("NA");
  }
  last = p->usec;*/
  //for (int i = 0; i <= 5000; i++) {
    logfile.print(pxData_RCV->usec);
    logfile.print(',');
    logfile.print(pxData_RCV->value1X,4);
    logfile.print(',');
    logfile.print(pxData_RCV->value1Y,4);
    logfile.print(',');
    logfile.print(pxData_RCV->value1Z,4);
    logfile.print(',');
    logfile.print(pxData_RCV->value2X,4);
    logfile.print(',');
    logfile.print(pxData_RCV->value2Y,4);
    logfile.print(',');
    logfile.print(pxData_RCV->value2Z,4);
    logfile.println(); 
    /*Serial.print(pxData_RCV->usec); 
    Serial.print(',');
    Serial.print(pxData_RCV->valueX,5);
    Serial.print(',');
    Serial.print(pxData_RCV->valueY,5);
    Serial.print(',');
    Serial.print(pxData_RCV->valueZ,5);
    Serial.println(); */
    //logfile.flush(); 
  //}
    //uint16_t FreeSpace = uxQueueSpacesAvailable( DataQueue ); 
    //Serial.println(FreeSpace); 
  //logfile.close();
  }

}
vTaskDelete( NULL );
}

//------------------------------------------------------------------------------
void TaskSDFlush(void *pvParameters) // This is a task.
{
(void) pvParameters;

for (;:wink:
{
vTaskDelay( 500 );
logfile.flush();
//Serial.println(“Flushed file”);

}
vTaskDelete ( NULL );
}

//------------------------------------------------------------------------------
void TaskSDClose(void *pvParameters) // This is a task.
{
(void) pvParameters;

for (;:wink:
{
vTaskDelay( 5000 );
logfile.close();
//Serial.println(“Close file”);

}
vTaskDelete ( NULL );
}[/Code]

My pleasure.

Unless I’m missing something (which, hey, absolutely possible!), You’re still sending the same pointer to the queue every time. You need to send a new pointer for each piece of data.

In terms of timing, Using ticks to time things using vTaskDelay is not going to be accurate.
Lets say you start a loop during tick #10, during the process, a tick occurs and switches you to a different task. By the time you get back to finish your loop, it’s almost tick #12. You finish your loop and vTaskDelay(1). So your next loop begins sometime after tick 12. You’ve gone from 10 to 12, giving you 500Hz instead of 1000 as you expect.

vTaskDelay is not a good way to time things that need to happen at a certain frequency.

Either fire a timer and process on that interrupt, or, use vTaskDelayUntil() and keep track of the “until” yourself.

Also see: https://www.freertos.org/a00127.html

vTaskDelay() does not therefore provide a good method of controlling the frequency of a
periodic task as the path taken through the code, as well as other task and interrupt activity,
will effect the frequency at which vTaskDelay() gets called and therefore the time at which the
task next executes. See vTaskDelayUntil() for an alternative API function designed to facilitate
fixed frequency execution. It does this by specifying an absolute time (rather than a relative
time) at which the calling task should unblock.

Alright, I’ve got some software timers in the script now. This is my first try at this so this are probably not correct or written to simply. I’m getting data now but if I try and go faster than 1 Hz it panics at Core1 and reboots. Too much trying to go into Queue and can’t write fast enough?

// Data logger:  For measuring acceleration from LIS3DH
// Hardware:  Adafruit ESP32, Adalogger feather+RTC, 1x LIS3DH accels (Currently, 2x in future) 
// Created:  May 12 2020
// Updated:
// Uses SPI for SD, I2C for Accels, hoping for 1000 Hz. sampling 
// files are saves text files, csv = NNNNNNNN.TXT
// See ReadME and photos for additional hook up info

//Use ESP32 duo core
const int TaskCore1  = 1;
const int TaskCore0 = 0;

//Libraries
#include <Wire.h>
#include <SPI.h>
#include "SdFat.h"
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
#include "stdio.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
//#include "freertos/Arduino_FreeRTOS.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
//#include "freertos/stream_buffer.h"
//------------------------------------------------------------------------------
// SD file definitions
const uint8_t sdChipSelect = 33;
SdFat sd;
SdFile file;
File logfile;
//------------------------------------------------------------------------------
// Queue definitions

// data type for Queue item
struct Data_t {
  uint32_t usec; 
  float value1X;
  float value1Y;
  float value1Z;
  float value2X;
  float value2Y;
  float value2Z;
} xData_t;

//Declare Queue data type for FreeRTOS
QueueHandle_t DataQueue = NULL;

// interval between points in units of 1000 usec
//const uint16_t intervalTicks = 2;

//------------------------------------------------------------------------------
// Accel Lis3dh definitions, SPI or I2C
// Used for software SPI
//#define LIS3DH_CLK 32  //SCL
//#define LIS3DH_MISO 15  //SDO
//#define LIS3DH_MOSI 27  //SDA
// Used for hardware & software SPI
#define LIS3DH_CS 14  //ESP32: 14/A6 , Cortex m0: 5, Use for upper accel (Sensor 1!!!) = hbar, seatpost, etc.
#define LIS3DH_CS2 15  //ESP32: 15/A8, Cortex m0: 9, Use for lower accel (Sensor 2!!!) = axles, etc. 
// software SPI
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS, LIS3DH_MOSI, LIS3DH_MISO, LIS3DH_CLK);

// hardware SPI 1 LIS3DH->Feather:  Power to Vin, Gnd to Gnd, SCL->SCK, SDA->MOSO, SDO->MOSI, CS->CS 14/15
// Sensor 1 Hardware SPI
Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS);
// Sensor 2 Hardware SPI
Adafruit_LIS3DH lis2 = Adafruit_LIS3DH(LIS3DH_CS2);

// Sensor I2C 
//Adafruit_LIS3DH lis = Adafruit_LIS3DH();

//------------------------------------------------------------------------------
// define tasks for Sensor Data and SD Write
//void TaskGetData( void *pvParameters );
void TaskSDWrite( void *pvParameters );
//void TaskSDFlush( void *pvParameters );
//void TaskSDClose( void *pvParameters );
//------------------------------------------------------------------------------

// Start the scheduler so the created tasks start executing. Need this for ESP32???
//void vTaskStartScheduler();
   
// the setup function runs once when you press reset or power the board
void setup() {

  // initialize serial communication at 115200 bits per second:
  Serial.begin(250000);

  // Create timer
  TimerHandle_t timer1 = xTimerCreate("HZ sample timer", pdMS_TO_TICKS(100), pdTRUE, 0, TaskGetData);
  TimerHandle_t timer2 = xTimerCreate("flush timer", pdMS_TO_TICKS(500), pdTRUE, 0, TaskSDFlush);
  TimerHandle_t timer3 = xTimerCreate("close file timer", pdMS_TO_TICKS(8000), pdTRUE, 0, TaskSDClose);
  if (timer1 == NULL) {
    Serial.println("Timer can not be created");
  } else {
    // Start timer
    if (xTimerStart(timer1, 0) == pdPASS) { // Start the scheduler
      Serial.println("Timer working");
    }
  }
  if (timer2 == NULL) {
    Serial.println("Timer 2 can not be created");
  } else {
    // Start timer
    if (xTimerStart(timer2, 0) == pdPASS) { // Start the scheduler
      Serial.println("Timer 2 working");
    }
  }
  
  //SPI.beginTransaction(SPISettings(80000000, LSBFIRST, SPI_MODE0));

  //Outputs, Pins, Buttons, Etc. 
  pinMode(13, OUTPUT);  //set Built in LED to show writing on SD Card
  pinMode(4, INPUT); //button to turn recording on/off
  
  //ACCEL Setup and RUN
  if (! lis.begin(0x18)) {   // change this to 0x19 for alternative i2c address
  Serial.println("Couldnt start");
  while (1) yield();
  }
  Serial.println("LIS3DH Sensor 1 found!");

  if (! lis2.begin(0x19)) {   // Sensor 2, SDO is 3V
  Serial.println("Couldnt start Sensor 2");
  while (1) yield();
  }
  Serial.println("LIS3DH Sensor 2 found!");
  
  // Set accel range  
  lis.setRange(LIS3DH_RANGE_16_G);   // 2, 4, 8 or 16 G!
  lis2.setRange(LIS3DH_RANGE_16_G);
  // Set DataRate
  lis.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); //OPTIONS:  LIS3DH_DATARATE_400_HZ, LIS3DH_DATARATE_LOWPOWER_1K6HZ, LIS3DH_DATARATE_LOWPOWER_5KHZ
  lis2.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); 


// SD CARD SETUP ====================================================================
// see if the card is present and can be initialized:  (Use highest SD clock possible, but lower if has error, 15 Mhz works, possible to go to to 25 Mhz if sample rate is low enough
if (!sd.begin(sdChipSelect, SD_SCK_MHZ(15))) {
  Serial.println("Card init. failed!");
  while (1) yield(); 
}

// Create filename scheme ====================================================================
  char filename[15];
  //  Setup filename to be appropriate for what you are testing
  strcpy(filename, "/DATA00.TXT");
  for (uint8_t i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! sd.exists(filename)) {
      break;
    }
  }

// Create file and prepare it ============================================================
  logfile = sd.open(filename, O_CREAT | O_WRITE);  //O_WRONLY | O_CREAT | O_EXCL ); // | O_TRUNC )); 
  if( ! logfile ) {
    Serial.print("Couldnt create "); 
    Serial.println(filename);
    while (1) yield(); 
  }
  Serial.print("Writing to "); 
  Serial.println(filename);

  //Column labels
  logfile.print("Time"); 
  logfile.print(",");
  logfile.print("Sensor 1 X");
  logfile.print(",");
  logfile.print("Sensor 1 Y");
  logfile.print(",");
  logfile.print("Sensor 1 Z");
  logfile.print(",");
  logfile.print("Sensor 2 X");
  logfile.print(",");
  logfile.print("Sensor 2 Y");
  logfile.print(",");
  logfile.print("Sensor 2 Z");
  logfile.println();
  
  //Queue Setup
  DataQueue = xQueueCreate(100, sizeof( &xData_t ));
  if(DataQueue == NULL){
     Serial.println("Error Creating the Queue");
   }
  
// Setup up Tasks and where to run ============================================================  
// Now set up tasks to run independently.
  /*xTaskCreatePinnedToCore(
    TaskGetData
    ,  "Get Data from Accel to Queue"   // A name just for humans
    ,  10000 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  4  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL 
    ,  TaskCore1);*/

  xTaskCreatePinnedToCore(
    TaskSDWrite
    ,  "Get Data from Queue"
    ,  20000 // Stack size
    ,  NULL
    ,  3 // Priority
    ,  NULL 
    ,  TaskCore1);

  /*xTaskCreatePinnedToCore(
    TaskSDFlush
    ,  "Write Data to Card"
    ,  10000 // Stack size
    ,  NULL
    ,  3  // Priority
    ,  NULL 
    ,  TaskCore1);*/

  /*xTaskCreatePinnedToCore(
    TaskSDClose
    ,  "Close the File"
    ,  10000 // Stack size
    ,  NULL
    ,  3  // Priority
    ,  NULL 
    ,  TaskCore1);*/    
}

void loop()
{
  // Empty. Things are done in Tasks.
/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/
}

void TaskGetData( TimerHandle_t timer1 )  // This is a task.
{
    struct Data_t *pxPointerToxData_t;

    pxPointerToxData_t = &xData_t; 

    if(xQueueSend( DataQueue, (void *) &pxPointerToxData_t, 2000 ) != pdPASS )  //portMAX_DELAY
      {
        Serial.println("xQueueSend is not working"); 
      }
       
    sensors_event_t event;
    lis.getEvent(&event);
    sensors_event_t event2;
    lis2.getEvent(&event2);
    pxPointerToxData_t->usec = micros();
    pxPointerToxData_t->value1X = event.acceleration.x;
    pxPointerToxData_t->value1Y = event.acceleration.y;
    pxPointerToxData_t->value1Z = event.acceleration.z;
    pxPointerToxData_t->value2X = event2.acceleration.x;
    pxPointerToxData_t->value2Y = event2.acceleration.y;
    pxPointerToxData_t->value2Z = event2.acceleration.z;
    Serial.print(pxPointerToxData_t->usec); 
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value1X,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value1Y,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value1Z,5);
    Serial.println();
        
}

void TaskSDFlush( TimerHandle_t timer2 )  // This is a task.
{
  logfile.flush(); 
}

void TaskSDClose( TimerHandle_t timer3 )  // This is a task.
{
  logfile.close(); 
}

//------------------------------------------------------------------------------
void TaskSDWrite(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  struct Data_t xData_RCV, *pxData_RCV;
  
  for (;;)
  {

    if(DataQueue != NULL ) 
    {
      if( xQueueReceive( DataQueue, &( pxData_RCV ), 2000 ) != pdPASS )   //portMAX_DELAY
      {
        //Serial.println("xQueueRecieve is not working");
      }
        
        logfile.print(pxData_RCV->usec);
        logfile.print(',');
        logfile.print(pxData_RCV->value1X,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value1Y,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value1Z,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value2X,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value2Y,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value2Z,4);
        logfile.println(); 
        /*Serial.print(pxData_RCV->usec); 
        Serial.print(',');
        Serial.print(pxData_RCV->valueX,5);
        Serial.print(',');
        Serial.print(pxData_RCV->valueY,5);
        Serial.print(',');
        Serial.print(pxData_RCV->valueZ,5);
        Serial.println(); */
 
        //uint16_t FreeSpace = uxQueueSpacesAvailable( DataQueue ); 
        //Serial.println(FreeSpace); 
      }
   }
   vTaskDelete( NULL ); 
}

Software timers are not super accurate. When I said fire a timer, I was referring to a hardware timer that fires an interrupt. You’re trying to maintain relatively accurate and fast timing - that’s what hardware interrupts are perfect for.

Also, you’re now sending a pointer to the queue that falls out of context as soon as the timer finishes.

You need to send unique pointers that are still in context when the read from the queue happens.

Honestly, the problems you’re having don’t have anything to do with FreeRTOS. FreeRTOS is doing exactly what you’re asking it to.

If you want accurate, fast timing, you’re going to have to use hardware. And, to prevent receiving junk or duplicated data, you have to send good data to the queue.

Ok, had to do a lot of reading this evening but I believe now established a hardware timer to fire an interrupt to tell the Task to get data. Other two tasks are still on software timers but this actually may be ok since they don’t need to that accurate (+/- 10 seconds is probably fine). I kind of understand your concern about my queue and pointers but I’ll have to dig in deeper. It does look like something is effecting my queue now because even when i run sample rate around 1 Hz the queue shows no spaces available. I also know i have an issue with my Timer because even when i try to go below 5000 uSec interrupt it just stays at 5000 uSec in the recorded data.

// Data logger:  For measuring acceleration from LIS3DH
// Hardware:  Adafruit ESP32, Adalogger feather+RTC, 1x LIS3DH accels (Currently, 2x in future) 
// Created:  May 12 2020
// Updated:
// Uses SPI for SD, I2C for Accels, hoping for 1000 Hz. sampling 
// files are saves text files, csv = NNNNNNNN.TXT
// See ReadME and photos for additional hook up info

//Use ESP32 duo core
const int TaskCore1  = 1;
const int TaskCore0 = 0;

//Libraries
#include <Wire.h>
#include <SPI.h>
#include "SdFat.h"
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
#include "stdio.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
//#include "freertos/Arduino_FreeRTOS.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
//#include "freertos/stream_buffer.h"

#define LED_BUILTIN LED_BUILTIN //LED light for notification
//------------------------------------------------------------------------------
// SD file definitions
const uint8_t sdChipSelect = 33;
SdFat sd;
SdFile file;
File logfile;
//------------------------------------------------------------------------------
// Queue definitions

// data type for Queue item
struct Data_t {
  uint32_t usec; 
  float value1X;
  float value1Y;
  float value1Z;
  float value2X;
  float value2Y;
  float value2Z;
} xData_t;

//Declare Queue data type for FreeRTOS
QueueHandle_t DataQueue = NULL;

//------------------------------------------------------------------------------
// Accel Lis3dh definitions, SPI or I2C
// Used for software SPI
//#define LIS3DH_CLK 32  //SCL
//#define LIS3DH_MISO 15  //SDO
//#define LIS3DH_MOSI 27  //SDA
// Used for hardware & software SPI
#define LIS3DH_CS 14  //ESP32: 14/A6 , Cortex m0: 5, Use for upper accel (Sensor 1!!!) = hbar, seatpost, etc.
#define LIS3DH_CS2 15  //ESP32: 15/A8, Cortex m0: 9, Use for lower accel (Sensor 2!!!) = axles, etc. 
// software SPI
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS, LIS3DH_MOSI, LIS3DH_MISO, LIS3DH_CLK);

// hardware SPI 1 LIS3DH->Feather:  Power to Vin, Gnd to Gnd, SCL->SCK, SDA->MOSO, SDO->MOSI, CS->CS 14/15
// Sensor 1 Hardware SPI
Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS);
// Sensor 2 Hardware SPI
Adafruit_LIS3DH lis2 = Adafruit_LIS3DH(LIS3DH_CS2);

// Sensor I2C 
//Adafruit_LIS3DH lis = Adafruit_LIS3DH();

//------------------------------------------------------------------------------
// define tasks for Sensor Data and SD Write
void TaskGetData( void *pvParameters );
void TaskSDWrite( void *pvParameters );
//void TaskSDFlush( void *pvParameters );
//void TaskSDClose( void *pvParameters );
//------------------------------------------------------------------------------

//Hardware Timer
hw_timer_t * timer = NULL;  //create timer handler
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t isrCounter = 0;
volatile uint32_t lastIsrAt = 0;

void IRAM_ATTR onTimer(){
// Increment the counter and set the time of ISR
portENTER_CRITICAL_ISR(&timerMux);
isrCounter++;
lastIsrAt = millis();
portEXIT_CRITICAL_ISR(&timerMux);
// Give a semaphore that we can check in the loop
xSemaphoreGiveFromISR(timerSemaphore, NULL);
// It is safe to use digitalRead/Write here if you want to toggle an output
  }
  
// the setup function runs once when you press reset or power the board
void setup() {

  // initialize serial communication at 115200 bits per second:
  Serial.begin(115200);

  //Queue Setup
  DataQueue = xQueueCreate(1000, sizeof( &xData_t ));
  if(DataQueue == NULL){
     Serial.println("Error Creating the Queue");
   }
   
  // Create timer
  //TimerHandle_t timer1 = xTimerCreate("HZ sample timer", pdMS_TO_TICKS(100), pdTRUE, 0, TaskGetData);
  TimerHandle_t timer2 = xTimerCreate("flush timer", pdMS_TO_TICKS(5000), pdTRUE, 0, TaskSDFlush);
  TimerHandle_t timer3 = xTimerCreate("close file timer", pdMS_TO_TICKS(8000), pdTRUE, 0, TaskSDClose);
  /*if (timer1 == NULL) {
    Serial.println("Timer can not be created");
  } else {
    // Start timer
    if (xTimerStart(timer1, 0) == pdPASS) { // Start the scheduler
      Serial.println("Timer working");
    }
  }*/
  if (timer2 == NULL) {
    Serial.println("Timer 2 can not be created");
  } else {
    // Start timer
    if (xTimerStart(timer2, 0) == pdPASS) { // Start the scheduler
      Serial.println("Timer 2 working");
    }
  }
    if (timer3 == NULL) {
    Serial.println("Timer 3 can not be created");
  } else {
    // Start timer
    if (xTimerStart(timer3, 0) == pdPASS) { // Start the scheduler
      Serial.println("Timer 3 working");
    }
  }

  // Create semaphore to inform us when the timer has fired
  timerSemaphore = xSemaphoreCreateBinary();
  // Use 1st timer of 4 (counted from zero).
  // Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more
  // info).
  timer = timerBegin(0, 80, true);
  // Attach onTimer function to our timer.
  timerAttachInterrupt(timer, &onTimer, true);
  // Set alarm to call onTimer function every second (value in microseconds).
  // Repeat the alarm (third parameter)
  timerAlarmWrite(timer, 10000, true);
  // Start an alarm
  timerAlarmEnable(timer);
  
  //SPI.beginTransaction(SPISettings(80000000, LSBFIRST, SPI_MODE0));

  //Outputs, Pins, Buttons, Etc. 
  pinMode(LED_BUILTIN, OUTPUT);  //set Built in LED to show writing on SD Card
  pinMode(27, INPUT); //button to turn recording on/off, In [HIGH]
  
  //ACCEL Setup and RUN
  if (! lis.begin(0x18)) {   // change this to 0x19 for alternative i2c address
  Serial.println("Couldnt start");
  while (1) yield();
  }
  Serial.println("LIS3DH Sensor 1 found!");

  if (! lis2.begin(0x19)) {   // Sensor 2, SDO is 3V
  Serial.println("Couldnt start Sensor 2");
  while (1) yield();
  }
  Serial.println("LIS3DH Sensor 2 found!");
  
  // Set accel range  
  lis.setRange(LIS3DH_RANGE_16_G);   // 2, 4, 8 or 16 G!
  lis2.setRange(LIS3DH_RANGE_16_G);
  // Set DataRate
  lis.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); //OPTIONS:  LIS3DH_DATARATE_400_HZ, LIS3DH_DATARATE_LOWPOWER_1K6HZ, LIS3DH_DATARATE_LOWPOWER_5KHZ
  lis2.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); 


// SD CARD SETUP ====================================================================
// see if the card is present and can be initialized:  (Use highest SD clock possible, but lower if has error, 15 Mhz works, possible to go to to 25 Mhz if sample rate is low enough
if (!sd.begin(sdChipSelect, SD_SCK_MHZ(15))) {
  Serial.println("Card init. failed!");
  while (1) yield(); 
}

// Create filename scheme ====================================================================
  char filename[15];
  //  Setup filename to be appropriate for what you are testing
  strcpy(filename, "/DATA00.TXT");
  for (uint8_t i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! sd.exists(filename)) {
      break;
    }
  }

// Create file and prepare it ============================================================
  logfile = sd.open(filename, O_CREAT | O_WRITE);  //O_WRONLY | O_CREAT | O_EXCL ); // | O_TRUNC )); 
  if( ! logfile ) {
    Serial.print("Couldnt create "); 
    Serial.println(filename);
    while (1) yield(); 
  }
  Serial.print("Writing to "); 
  Serial.println(filename);

  //Column labels
  logfile.print("Time"); 
  logfile.print(",");
  logfile.print("Sensor 1 X");
  logfile.print(",");
  logfile.print("Sensor 1 Y");
  logfile.print(",");
  logfile.print("Sensor 1 Z");
  logfile.print(",");
  logfile.print("Sensor 2 X");
  logfile.print(",");
  logfile.print("Sensor 2 Y");
  logfile.print(",");
  logfile.print("Sensor 2 Z");
  logfile.println();
  

  
// Setup up Tasks and where to run ============================================================  
// Now set up tasks to run independently.
  xTaskCreatePinnedToCore(
    TaskGetData
    ,  "Get Data from Accel to Queue"   // A name just for humans
    ,  10000 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  4  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL 
    ,  TaskCore1);

  xTaskCreatePinnedToCore(
    TaskSDWrite
    ,  "Get Data from Queue"
    ,  20000 // Stack size
    ,  NULL
    ,  3 // Priority
    ,  NULL 
    ,  TaskCore0);

  /*xTaskCreatePinnedToCore(
    TaskSDFlush
    ,  "Write Data to Card"
    ,  10000 // Stack size
    ,  NULL
    ,  3  // Priority
    ,  NULL 
    ,  TaskCore1);*/

  /*xTaskCreatePinnedToCore(
    TaskSDClose
    ,  "Close the File"
    ,  10000 // Stack size
    ,  NULL
    ,  3  // Priority
    ,  NULL 
    ,  TaskCore1);*/    
}

void loop()
{
  // Empty. Things are done in Tasks.
/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/
}

void TaskSDFlush( TimerHandle_t timer2 )  // This is a task.
{
  logfile.flush(); 
  Serial.print("The Flush is IN");
}

void TaskSDClose( TimerHandle_t timer3 )  // This is a task.
{
  logfile.close(); 
  Serial.print("File = Done");
}
void TaskGetData(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  
  struct Data_t *pxPointerToxData_t;

  for (;;) // A Task shall never return or exit.
  {
    pxPointerToxData_t = &xData_t; 

    if(xQueueSend( DataQueue, (void *) &pxPointerToxData_t, 2000 ) != pdPASS )  //portMAX_DELAY
      {
        //Serial.println("xQueueSend is not working"); 
      }
    if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE){    
    sensors_event_t event;
    lis.getEvent(&event);
    sensors_event_t event2;
    lis2.getEvent(&event2);
    pxPointerToxData_t->usec = micros();
    pxPointerToxData_t->value1X = event.acceleration.x;
    pxPointerToxData_t->value1Y = event.acceleration.y;
    pxPointerToxData_t->value1Z = event.acceleration.z;
    pxPointerToxData_t->value2X = event2.acceleration.x;
    pxPointerToxData_t->value2Y = event2.acceleration.y;
    pxPointerToxData_t->value2Z = event2.acceleration.z;
    /*Serial.print(pxPointerToxData_t->usec); 
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value1X,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value1Y,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value1Z,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value2X,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value2Y,5);
    Serial.print(',');
    Serial.print(pxPointerToxData_t->value2Z,5);
    Serial.println();*/
    }
    else {
      Serial.print("nothing happening");
    }
    //digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    //vTaskDelay(100);  // one tick delay (15ms) in between reads for stability
    //digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    //vTaskDelay(100);  // one tick delay (15ms) in between reads for stability
 
    //vTaskDelay(intervalTicks);  // one tick delay (1000 uSec/1 mSec) in between reads for 1000 Hz reading 
    }
    vTaskDelete( NULL );
}
//------------------------------------------------------------------------------
void TaskSDWrite(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  struct Data_t xData_RCV, *pxData_RCV;
  
  for (;;)
  {

    if(DataQueue != NULL ) 
    {
      if( xQueueReceive( DataQueue, &(pxData_RCV), 2000 ) != pdPASS )   //portMAX_DELAY
      {
        //Serial.println("xQueueRecieve is not working");
      }
        
        logfile.print(pxData_RCV->usec);
        logfile.print(',');
        logfile.print(pxData_RCV->value1X,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value1Y,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value1Z,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value2X,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value2Y,4);
        logfile.print(',');
        logfile.print(pxData_RCV->value2Z,4);
        logfile.println(); 
        Serial.print(pxData_RCV->usec); 
        Serial.print(',');
        Serial.print(pxData_RCV->value1X,5);
        Serial.print(',');
        Serial.print(pxData_RCV->value1Y,5);
        Serial.print(',');
        Serial.print(pxData_RCV->value1Z,5);
        Serial.print(',');
        Serial.print(pxData_RCV->value2X,5);
        Serial.print(',');
        Serial.print(pxData_RCV->value2Y,5);
        Serial.print(',');
        Serial.print(pxData_RCV->value2Z,5);
        Serial.println(); 
 
        uint16_t FreeSpace = uxQueueSpacesAvailable( DataQueue ); 
        Serial.println(FreeSpace); 
      }
   }
   vTaskDelete( NULL ); 
}


//------------------------------------------------------------------------------
/*void TaskSDFlush(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

  for (;;)
  {
    logfile.flush();
    //Serial.println("Flushed file"); 
    
  }
  vTaskDelete ( NULL ); 
}

//------------------------------------------------------------------------------
void TaskSDClose(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

  for (;;)
  {
    //vTaskDelay( 5000 );
    logfile.close();
    //Serial.println("Close file"); 
    
  }
  vTaskDelete ( NULL ); 
}*/

Sample of Data
353234,0.4598,-0.1149,10.0006,0.2299,-0.1149,9.5408
358617,0.2299,0.1149,10.0006,0.1149,0.1149,9.5408
363823,0.1149,0.0000,10.0006,0.2299,-0.1149,9.3109
369118,0.2299,-0.2299,9.8857,0.0000,0.1149,9.4259
374326,0.1149,0.0000,10.1156,0.2299,0.1149,9.3109
379537,0.1149,0.0000,10.1156,0.2299,0.1149,9.1960
384744,0.4598,-0.1149,9.8857,0.2299,0.1149,9.4259
389954,0.2299,-0.1149,10.0006,0.2299,0.2299,9.3109
395246,0.2299,-0.1149,10.1156,0.2299,0.0000,9.1960
400547,0.1149,0.0000,10.1156,0.3448,0.0000,9.1960
405756,0.2299,-0.1149,10.1156,0.2299,0.2299,9.6558
411045,0.2299,0.0000,10.1156,0.2299,0.1149,9.3109
416253,0.1149,0.0000,10.1156,0.3448,-0.1149,9.3109
421557,0.2299,-0.2299,10.0006,0.3448,0.1149,9.0810
426844,0.2299,-0.1149,9.8857,0.1149,0.0000,9.4259
432051,0.1149,-0.1149,10.0006,0.2299,0.0000,9.5408

For a better, more robust application you should use a queue of complete Data_t items of probably much less queue length. Question regarding queue length is how much pending or in-flight sensor data items do you expect i.e. measured data not yet written to SD card.
So just fetch the sensor data into a local Data_t SensorData variable and send it (as copy) to the queue. Receive a sensor data item from the queue into a also local Data_t RCVData variable and write it to SD card. This avoids any conflicts accessing the same (global) Data_t variable when fetching new sensor data while the previously fetched and send data is currently written to SD card. Besides the issue Pilot already mentioned that queuing pointers to the same data/variable doesn’t make sense because the data pointed to might change in between.
As long as the SD card write speed is and remains higher than the the sensor data rate you should be fine. The performance impact of additional serial debug logging was already mentioned. But you’ll easily see if the application can catch up the sensor data rate by checking the filling level of the queue.

Pretty often queue problems come from misunderstandings how they work. This in turn comes from not knowing the comprehensive FreeRTOS documentation.
Looking up first e.g. Queue Management could save a lot of time and pain.

You’re still sending the same data pointer to the queue every time. I’m not sure how many times I can say this.

Also, you’re now sending (the same) pointer to the queue as fast as the processor can possibly send it since you’re not actually waiting on the semaphore. No wonder the queue is filling up. And you probably have abysmal write speeds since your GetData task has a higher priority and it never blocks until the queue is full.

The data you are writing is probably complete garbage since as soon as your queue fills up, your writeSD task finally has a chance to run, but as soon as you remove one item from the queue, your GetData task immediately is able to run again which corrupts the data at the pointer.

It’s time to sit down and actually read the FreeRTOS documentation and take some time to learn about pointers and memory allocation.

Just wanted to add that hs2’s comments are all valid. The one thing I might do differently is to not send full Data_t items through the queue - that’s a lot of copying for no good reason. Sending pointers is fine, as long as they’re unique for each queue item.

I frequently use a queue for a TCP Socket where one tasks receives, and then another processes the receipt and responds. The receive task is high priority, while the process and send task is low priority. I send the received data between the tasks using a queue of pointers, and it works wonderfully with almost no useless copying of data.

Thanks for all the advice. I’ve done a lot reading and video watching, and now have a pretty solid script running.

  • read a lot on the queue and decided to go with Queue by copy instead of reference. It sounds like from documentation FreeRTOS does this quite and well and by reference is only necessary with “large” structures but what large is, is not defined. Working well enough for me right now.
  • Removed flush call and pulled logfile.close() into SD write since having seperate tasks that accidentally meet at wrong time is too much for SD write i believe, a flush called in the middle of logging data is not ideal, so having one Task to handle all interactions with SD I think is ideal, correct?
  • eventually want to add button functionality to have system armed but not started until button press, I imagine this involves vTAskSuspend until interrupt from semaphore to all tasks to say go?
  • put data lines (accels) and SD line (SD card) on seperate SPI lines. Ideally i would eventually use ESP32 SPI API but current SPI library seems to be working unless it is causing issue with problem below

The only problem i have now is I can record up to 500Hz very well, consistently and accurately, with very few accel log errors, but going above this forces a watchdog error, not every time but most of the time. I wonder if task is waiting on data but data isn’t coming? It looks like it has to do with TaskGetData.
Error:
Writing to /DATA05.TXT
E (11722) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (11722) task_wdt: - IDLE0 (CPU 0)
E (11722) task_wdt: Tasks currently running:
E (11722) task_wdt: CPU 0: Get Data from Q
E (11722) task_wdt: CPU 1: loopTask
E (11722) task_wdt: Aborting.
abort() was called at PC 0x400d7f53 on core 0

Backtrace: 0x4008c2e0:0x3ffbe170 0x4008c50d:0x3ffbe190 0x400d7f53:0x3ffbe1b0 0x40084e69:0x3ffbe1d0 0x400d4ef6:0x3ffc4400 0x400d1546:0x3ffc4420 0x400d297a:0x3ffc4440 0x400d2deb:0x3ffc4460 0x400d2509:0x3ffc4480 0x400d1cd1:0x3ffc44a0 0x400d0fe0:0x3ffc44d0 0x400d24ba:0x3ffc44f0 0x400d5715:0x3ffc4510 0x400d58c4:0x3ffc4560 0x400d5953:0x3ffc4580 0x400d0ebc:0x3ffc45a0 0x40088a91:0x3ffc45c0

// Data logger:  For measuring acceleration from LIS3DH/s
// Hardware:  Adafruit ESP32, Adalogger feather+RTC, 2x LIS3DH accels
// Created:  May 12 2020
// Updated:
// Uses SPI for SD & Accels, hoping for 1000 Hz. sampling 
// files are saves text files = DATANN.TXT
// See ReadME and photos for additional hook up info

const int SampleRate = 1000; //Hz, Set sample rate here
const int SampleLength = 10; //Seconds, Sample Length in Seconds

//Use ESP32 duo core
const int TaskCore1  = 1;
const int TaskCore0 = 0;
int SampleInt = 1000000 / SampleRate; 
int TotalCount = SampleLength * SampleRate;

//Libraries
#include <SPI.h>
#include "SdFat.h"
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
#include "stdio.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"

#define LED_BUILTIN LED_BUILTIN //LED light for notification
//------------------------------------------------------------------------------
// SD file definitions
const uint8_t sdChipSelect = 33;
SdFat sd;
SdFile file;
File logfile;
//------------------------------------------------------------------------------
// data type for Queue item
struct Data_t {
  uint32_t usec; 
  float value1X;
  float value1Y;
  float value1Z;
  float value2X;
  float value2Y;
  float value2Z;
} TX_Data_t, RX_Data_t;

//------------------------------------------------------------------------------
// Accel Lis3dh definitions, SPI or I2C
// hardware SPI 1 LIS3DH->Feather:  Power to Vin, Gnd to Gnd, SCL->SCK, SDA->MOSO, SDO->MOSI, CS->CS 14/15
// Sensor 1 Hardware SPI
//Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS);
// Sensor 2 Hardware SPI
//Adafruit_LIS3DH lis2 = Adafruit_LIS3DH(LIS3DH_CS2);

// Used for software SPI
#define LIS3DH_CLK 21
#define LIS3DH_MISO 33
#define LIS3DH_MOSI 32
#define LIS3DH2_CLK 25
#define LIS3DH2_MISO 26
#define LIS3DH2_MOSI 4
// Used for hardware & software SPI
#define LIS3DH_CS 14   //ESP32: 14/A6 , Cortex m0: 5, Use for upper accel (Sensor 1!!!) = hbar, seatpost, etc.
#define LIS3DH2_CS 15  //ESP32: 15/A8, Cortex m0: 9, Use for lower accel (Sensor 2!!!) = axles, etc. 

// software SPI
Adafruit_LIS3DH lis = Adafruit_LIS3DH(LIS3DH_CS, LIS3DH_MOSI, LIS3DH_MISO, LIS3DH_CLK);
// software SPI 2 
Adafruit_LIS3DH lis2 = Adafruit_LIS3DH(LIS3DH2_CS, LIS3DH2_MOSI, LIS3DH2_MISO, LIS3DH2_CLK);

//------------------------------------------------------------------------------
// define tasks for Sensor Data and SD Write
void TaskLed( void *pvParamaters );
void TaskGetData( void *pvParameters );
void TaskSDWrite( void *pvParameters );
//------------------------------------------------------------------------------

//Hardware Timer
hw_timer_t * timer = NULL;  //create timer handler

//Declare Queue data type for FreeRTOS
QueueHandle_t DataQueue; // 

//ISR tools
//Create Interrupt Semaphores
SemaphoreHandle_t timerSemaphore; 
SemaphoreHandle_t ButtonSemaphore;
SemaphoreHandle_t CountSemaphore; 
int Count = 0; 
int G = 0;
int H = 0; 
int F = 0;

/////////////////////////////////////////////////////////////////////////////////////
void IRAM_ATTR vTimerISR()  //Timer ISR 
  {
  xSemaphoreGiveFromISR(timerSemaphore, NULL);
  }
//------------------------------------------------------------------------------
void IRAM_ATTR ButtonISR() 
  {
  xSemaphoreGiveFromISR(ButtonSemaphore, NULL); //Gives permission from button interrupt
  }
//------------------------------------------------------------------------------
void TaskGetData(void *pvParameters)  // This is a task.
{
    (void) pvParameters;

  for (;;) // A Task shall never return or exit.
  {
    if (xSemaphoreTake(timerSemaphore, portMAX_DELAY ) == pdTRUE )
    {
    sensors_event_t event;
    lis.getEvent(&event);
    sensors_event_t event2;
    lis2.getEvent(&event2);
    TX_Data_t.usec = micros();
    TX_Data_t.value1X = event.acceleration.x;
    TX_Data_t.value1Y = event.acceleration.y;
    TX_Data_t.value1Z = event.acceleration.z;
    TX_Data_t.value2X = event2.acceleration.x;
    TX_Data_t.value2Y = event2.acceleration.y;
    TX_Data_t.value2Z = event2.acceleration.z;
    if(xQueueSend( DataQueue, ( void * ) &TX_Data_t, portMAX_DELAY ) != pdPASS )  //portMAX_DELAY
      {
        Serial.println("xQueueSend is not working"); 
      }
    }
  }
  vTaskDelete( NULL );
}
//------------------------------------------------------------------------------
void TaskSDWrite(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
  
  //struct Data_t *RCV_Data; 
  
  for (;;)
  {

      if( xQueueReceive( DataQueue, &( RX_Data_t ), portMAX_DELAY ) != pdPASS )   //portMAX_DELAY
      {
        Serial.println("xQueueRecieve is not working");
      }
      logfile.print(RX_Data_t.usec);
      logfile.print(',');
      logfile.print(RX_Data_t.value1X,4);
      logfile.print(',');
      logfile.print(RX_Data_t.value1Y,4);
      logfile.print(',');
      logfile.print(RX_Data_t.value1Z,4);
      logfile.print(',');
      logfile.print(RX_Data_t.value2X,4);
      logfile.print(',');
      logfile.print(RX_Data_t.value2Y,4);
      logfile.print(',');
      logfile.print(RX_Data_t.value2Z,4);
      logfile.println(); 
      Count++;
      //Serial.println(Count); 
      if ( Count == TotalCount )
        {
          logfile.close(); 
          vTaskDelay ( 8000 / portTICK_PERIOD_MS ); 
          Serial.println("All done here"); 
          vTaskDelay( 20000000 / portTICK_PERIOD_MS );
          //vTaskSuspendAll(); 
        }

      //uint16_t FreeSpace = uxQueueSpacesAvailable( DataQueue ); 
      //Serial.println(FreeSpace);
      }
   vTaskDelete( NULL ); 
}

//------------------------------------------------------------------------------
void TaskLed(void *pvParameters)
{
  (void) pvParameters;

  for (;;) 
    {
    // Take the semaphore.
    if( xSemaphoreTake(CountSemaphore, portMAX_DELAY) == pdPASS )
        {
        //vTaskSuspend( (void *) &TaskGetData );
        //vTaskSuspend( (void *) &TaskSDWrite );
        //Serial.println("Recieved count semaphore"); 
        //Serial.println("All done here");
        //vTaskDelay( 20000000 / portTICK_PERIOD_MS );
        //vTaskSuspendAll(); 
        }  
       
    if (xSemaphoreTake(ButtonSemaphore, portMAX_DELAY) == pdPASS) {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    }
   }
}
//===================================================================================================================
//===================================================================================================================
// the setup function runs once when you press reset or power the board
void setup() {

  // initialize serial communication at 115200 bits per second:
  Serial.begin(115200);

  //SPI
  //SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));

  //Queue Setup
  DataQueue = xQueueCreate(10, sizeof( Data_t ));
  if(DataQueue == NULL){
     Serial.println("Error Creating the Queue");
   }

  //============================================================================================================
  //Outputs, Pins, Buttons, Etc. 
  pinMode(LED_BUILTIN, OUTPUT);  //set Built in LED to show writing on SD Card
  pinMode(27, INPUT); //button to turn recording on/off, In [HIGH]

  //Create button Interrupt Semaphore
  ButtonSemaphore = xSemaphoreCreateBinary();
  if (ButtonSemaphore != NULL) {
    // Attach interrupt for Arduino digital pin
    attachInterrupt(digitalPinToInterrupt(27), ButtonISR, FALLING);
  }

  // Create semaphore to inform us when the timer has fired
  timerSemaphore = xSemaphoreCreateBinary();

  // Create semaphore for counting samples
  CountSemaphore = xSemaphoreCreateBinary(); 

  // Create semaphore for Flush samples
  //FlushSemaphore = xSemaphoreCreateCounting( Flush, 0 ); 
  
  //ACCEL Setup and RUN
  if (! lis.begin(0x18)) {   // change this to 0x19 for alternative i2c address
  Serial.println("Couldnt start");
  while (1) yield();
  }
  Serial.println("LIS3DH Sensor 1 found!");

  if (! lis2.begin(0x19)) {   // Sensor 2, SDO is 3V
  Serial.println("Couldnt start Sensor 2");
  while (1) yield();
  }
  Serial.println("LIS3DH Sensor 2 found!");
  Serial.println(SampleInt); //,"Sample time interval");
  Serial.println(TotalCount); //,"Total # of samples");
  
  // Set accel range  
  lis.setRange(LIS3DH_RANGE_16_G);   // 2, 4, 8 or 16 G!
  lis2.setRange(LIS3DH_RANGE_16_G);
  // Set DataRate
  lis.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); //OPTIONS:  LIS3DH_DATARATE_400_HZ, LIS3DH_DATARATE_LOWPOWER_1K6HZ, LIS3DH_DATARATE_LOWPOWER_5KHZ
  lis2.setDataRate(LIS3DH_DATARATE_LOWPOWER_5KHZ); 

  // SD CARD SETUP ====================================================================
  // see if the card is present and can be initialized:  (Use highest SD clock possible, but lower if has error, 15 Mhz works, possible to go to to 25 Mhz if sample rate is low enough
  if (!sd.begin(sdChipSelect, SD_SCK_MHZ(35))) {
    Serial.println("Card init. failed!");
    while (1) yield(); 
  }

  // Create filename scheme ====================================================================
  char filename[15];
  //  Setup filename to be appropriate for what you are testing
  strcpy(filename, "/DATA00.TXT");
  for (uint8_t i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! sd.exists(filename)) {
      break;
    }
  }

  // Create file and prepare it ============================================================
  logfile = sd.open(filename, O_CREAT | O_WRITE);  
  if( ! logfile ) {
    Serial.print("Couldnt create "); 
    Serial.println(filename);
    while (1) yield(); 
  }
  Serial.print("Writing to "); 
  Serial.println(filename);

  //Column labels
  logfile.print("Time uS"); 
  logfile.print(",");
  logfile.print("Sensor 1 X");
  logfile.print(",");
  logfile.print("Sensor 1 Y");
  logfile.print(",");
  logfile.print("Sensor 1 Z");
  logfile.print(",");
  logfile.print("Sensor 2 X");
  logfile.print(",");
  logfile.print("Sensor 2 Y");
  logfile.print(",");
  logfile.print("Sensor 2 Z");
  logfile.println();

  // Setup up Tasks and where to run ============================================================  
  // Now set up tasks to run independently.
  xTaskCreatePinnedToCore(
    TaskGetData
    ,  "Get Data from Accel to Queue"   // A name just for humans
    ,  10000 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  4  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL 
    ,  TaskCore1);

  xTaskCreatePinnedToCore(
    TaskSDWrite
    ,  "Get Data from Queue"
    ,  10000 // Stack size
    ,  NULL
    ,  3 // Priority
    ,  NULL 
    ,  TaskCore0);

    xTaskCreatePinnedToCore(
    TaskLed
    ,  "LED"
    ,  2000 // Stack size
    ,  NULL
    ,  3  // Priority
    ,  NULL 
    ,  TaskCore0);  
  
// Create Timer ===============================================================================
  // Use 1st timer of 4 (counted from zero).
  // Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more
  // info).
  timer = timerBegin(1, 80, true);
  // Attach onTimer function to our timer.
  timerAttachInterrupt(timer, &vTimerISR, true);
  // Set alarm to call onTimer function every second (value in microseconds).
  // Repeat the alarm (third parameter)
  timerAlarmWrite(timer, SampleInt, true);
  // Start an alarm
  timerAlarmEnable(timer);

}
 
//================================================================================================================================
void loop()
{
  // Empty. Things are done in Tasks.
/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/
}

//================================================================================================================================
//================================================================================================================================