Trouble achieving high sample rate with accelerometers

Datalogger - Huzzah32 + Adalogger feather + 2 LIS3DH accels

I've been working on a datalogger using the Adafruit Feather Huzzah ESP32 + Adafruit Adalogger feather (RTC + SD) + 2 Adafruit LIS3dh accelerometers using I2C and SPI (SD card). I'm successfully getting data and writing it to the card but i can't seem to get past about 333 Hz. I'd like to be somewhere between 3000-5000 Hz. I believe this is possible with this setup? Do i have too many logfiles, saving in float, any ideas why I can't achieve higher hertzs? Thanks!

// Bike Data logger for acceleration at hubs and touch points
// Hardware:  Adafruit ESP32, Adalogger feather+RTC, 2x LIS3DH accels, LED, push button
// Created:  Sep 7 2019
// Updated:  Oct 1 2019
// Uses I2C

#include <SPI.h>
#include <SD.h>
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
#include "RTClib.h"
#include <Wire.h>
#include <time.h>

// SD Card uses SPI, Set the selct pin
#define cardSelect 33

// I2C
Adafruit_LIS3DH lis = Adafruit_LIS3DH();  //Sensor 1, at hub 0x18
Adafruit_LIS3DH lis2 = Adafruit_LIS3DH(); //Sensor 2, at post 0x19 (on 3.3 at SDO)

// RTC
RTC_PCF8523 rtc;

//SD writing
File logfile;

//Counter
unsigned long Millisec = 0;
unsigned long Micro = 0;

//button and LED setup
const int button = 4; //pushbutton pin GPIO #4, A5
const int ledPin = 27; //led pin GPIO #27
int stateLED = HIGH;  //LED is high to start
int logData = 1; //how to run logging loop
int reading;  //button reading
int previous = HIGH; //button previous reading
long debounce = 200;  //mS for debounce
int x;

//=============================================================================================
void setup() {
  //Serial.begin(1000000);  //230400 works
  //Wire.begin();
  Wire.setClock(400000); //set I2C speed
 
  pinMode(13, OUTPUT);  //set LED to show writing on SD Card
  pinMode(ledPin, OUTPUT);  //set LED to show when switched off
  pinMode(button, INPUT); //button to turn recording on/off

      if (! lis.begin(0x18)) {   // Sensor 1, change this to 0x19 for alternative i2c address
    Serial.println("Couldnt start Sensor 1");
    while (1);
  }
  Serial.println("LIS3DH Sensor 1 found!");

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

    lis.setRange(LIS3DH_RANGE_16_G);   // 2, 4, 8 or 16 G!
    lis2.setRange(LIS3DH_RANGE_16_G);
 
  Serial.print("Range 1/2 = ");
  Serial.print(2 << lis.getRange());
  Serial.print("/");
  Serial.print(2 << lis2.getRange()); 
  Serial.println("G");

// see if the card is present and can be initialized:
  if (!SD.begin(cardSelect)) {
    Serial.println("Card init. failed!");
    //error(2);
 }

// Create filename scheme ====================================================================
  char filename[15];
  strcpy(filename, "/BDLDATA00.TXT");
  for (uint8_t i = 0; i < 100; i++) {
    filename[8] = '0' + i/10;
    filename[9] = '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, FILE_WRITE);
  if( ! logfile ) {
    Serial.print("Couldnt create ");
    Serial.println(filename);
    //error(3);
  }
  Serial.print("Writing to ");
  Serial.println(filename);

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

// Use RTC - Year/month/day for reference later ======================================
  DateTime now = rtc.now();
  logfile.print(now.year(), DEC);
  logfile.print("/");
  logfile.print(now.month(), DEC);
  logfile.print("/");
  logfile.print(now.day(), DEC);
  logfile.print("\t");
  logfile.print(now.hour(), DEC);
  logfile.print(":");
  logfile.print(now.minute(), DEC);
  logfile.println();
  logfile.println();

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

uint8_t i=0;

//==========================================================================================
void loop() {

  reading = digitalRead(button);
  if (reading == HIGH && previous == LOW && millis() - Millisec > debounce){   //= button state when releasing
    if (stateLED == HIGH){
      stateLED = LOW;
      logData = 0;
      //digitalWrite(13, HIGH);
    }
    else {
      stateLED = HIGH;   
      logData = 1;
      //logfile.flush();
      logfile.close();  //must save and close file to flush out RAM to card
    }

    Millisec = millis();  //for debounce
   
 }

    //digitalWrite(ledPin, stateLED);  //led on/off
    previous = reading; //reset previous
   
  // Turn datalogging on/off
  if (logData == 0){
    DataLogger();
    }
  else{
    //digitalWrite(13, LOW);
    }
}

//==================datalogging loop=====================================
void DataLogger(){

//for (int x = 0; x < 20000; x++){

  //Counter
  Micro = micros(); 
  logfile.print(Micro);
  logfile.print("\t");

  lis.read();     // get X Y and Z data at once (raw data) sensor 1
  lis2.read();    // get X Y and Z data at once (raw data) sensor 2
  // log raw data from Sensor 1 and 2
  // Note:  must normalize raw data which means measured values are between -32768 and 32767.  -Range G/+Range G = -32768/32767
  logfile.print(lis.x);
  logfile.print("\t");
  logfile.print(lis.y);
  logfile.print("\t");
  logfile.print(lis.z);
  logfile.print("\t");
  logfile.print(lis2.x);
  logfile.print("\t");
  logfile.print(lis2.y);
  logfile.print("\t");
  logfile.print(lis2.z);
  logfile.println();
 
   /* Or....get a new sensor event, normalized */
  /*sensors_event_t event;
  lis.getEvent(&event);
  sensors_event_t event2;
  lis2.getEvent(&event2); */

  //Display the results (acceleration is measured in m/s^2)
  /*Serial.print(" \tX: "); Serial.print(event.acceleration.x,4);
  Serial.print(" \tY: "); Serial.print(event.acceleration.y,4);
  Serial.print(" \tZ: "); Serial.print(event.acceleration.z,4);
  Serial.println(" m/s^2 "); */

  // log from Sensor 1 and 2
  /*logfile.print(event.acceleration.x,3);
  logfile.print("\t");
  logfile.print(event.acceleration.y,3);
  logfile.print("\t");
  logfile.print(event.acceleration.z,3);
  logfile.print("\t");
  logfile.print(event2.acceleration.x,3);
  logfile.print("\t");
  logfile.print(event2.acceleration.y,3);
  logfile.print("\t");
  logfile.print(event2.acceleration.z,3);
  logfile.println(); */
 
  delayMicroseconds(200);
}

//logfile.flush();
//x = 0;
//}

I believe this is possible with this setup?

If "setup" includes your code:

  delayMicroseconds(200);

I don't believe that it's possible.

If you were to use freeRTOS FreeRTOS API categories you can separate out your tasks to use differing cores.

If you were to use the ESP32 SPI API SPI Master Driver - ESP32 - — ESP-IDF Programming Guide latest documentation (which uses a freeRTOS queue) you could receive/send background queued data.

I have attached an example of using the ESP32 SPI API. The code from each task cycling takes a bit less than 280uS to run.


As a note on using delay() with the ESP32. As you may know the delay() stops program execution. If using freeRTOS, which is included with the ESP32 and vTaskDelay( ) is used, program execution is not stopped. freeRTOS will switch in other tasks that may be waiting to run during the time of vTaskDelay( ).

ESP32_MPU9250_SPI_API.ino (24.7 KB)

3-5KHz is a lot. Think about how much data must be transmitted to the SD card alone. Maybe 100kB per second? 6MB per minute? That is a lot of data for an Arduino to handle.

Then you appear to have both sensors and the SD card on the same SPI bus. So you cannot read from a sensor and write to the card at the same time. Add the overhead of starting and stopping communication with each device and you need many megabits per second. While the SPI bus can do up to 8Mbps even on the 'slow' 16MHz Arduinos, you can't keep it supplied with data at that rate for longer than a few microseconds.

Worse still, the standard SD.h library is a little slow and inefficient. Even with the much-better sdfat.h, the SD card will randomly insert delays of 10ms or more when it does its own housekeeping. That is 30-50 samples lost and unrecoverable.

If you can change your plans to sample 3-5KHz for only a few hundred samples and then write them to the card in a block then it will be much easier.

Data rates over 100Hz are very difficult to sustain if you are writing to an SD card. Even to get to that speed, you will have to do a lot of work and experiments.

Look at using something like a Teensy 3.6 which has the SD card on a dedicated 4-wire bus and a much faster core processor.

I'd like to be somewhere between 3000-5000 Hz

Why?

What good could that possibly do, that a lower data rate would not?

It would be useful to know a bit more about the project - if you sample accelerometers at say 3000hz , that is only needed if the acceleration parameter is changing more than 1500 times a second . What are you measuring acceleration of ?

There maybe an upper limit on the accelerometer itself , due to its mass and also phase delay maybe significant . The accelerometer may also be changing the performance of whatever it’s connected to .

Thanks so much for the help so far! I've gone back to the drawing board a bit based on the responses and i'd like to share where I am now but first answers to questions.

Why the high sample rate? I'm measuring vibration in some composite structures to understand their dynamic response. You need to measure at some high sample rates 4000-6000 Hz so you don't miss any small peak measurements. I know from past experience working with our test engineer and a really nice DAQ that this is the correct range for this application.

I think if i could get to 1000 Hz. for now or even 2000 Hz. i could get by for awhile, then eventually figure out how to go higher.
So new goal = 1000 Hz

Based on answers, i've gone back and gotten rid of the Sd library and moved th SDfat library. My new code uses much of the example datalogger code. Hopefully this much better buffer will help. I'm currently getting up to 333 Hz, past that I get the debug error of "Rate too fast". I also updated the code to reading just Y axis instead of all 3 for now.

I've got 520kB of RAM am i using all of it? I assume I have to stick with 512 blocks? It seems like I am able to get my sampler per down to 8 bytes, but i'm probably missing something.

BDL_v17.zip (7.36 KB)

Yea, freeRTOS has stream buffers that can work in the background or be put on the other core. Putting the read the sensor vibration on one 240Mhz core and the stream buffer on the other 240Mhz core may give an increase in operational performance. The fastest rate of reading the vibration sensor would be to use the ESP32 SPI API.

You will get a lot more help if you post your code inline instead of zipped.

I'll have to take a deeper dive into that freeRTOS, sounds like it could work but will probably take me awhile to figure out how to use it.

I'd love to put my code inline, but i'm limited to 9000 characters on this forum so it kept rejecting my post.

smailen:
I'll have to take a deeper dive into that freeRTOS, sounds like it could work but will probably take me awhile to figure out how to use it.

I'd love to put my code inline, but i'm limited to 9000 characters on this forum so it kept rejecting my post.

I just break my code up onto 9K bits, adds to post count.

It is mormally easier to trim the code down to just the portion that is causing the problem. If the central core of your 6kHz logger is bigger than a few hundred lines then it has no hope of working that fast.

You may, also, want to take a look at the ESP32 SD SPI Host Driver API

The SD SPI host driver allows using the SPI2 (HSPI) or SPI3 (VSPI) controller for communication with SD cards. This driver’s naming pattern was adopted from the SDMMC Hostdriver due to their similarity. Likewise, the APIs of both drivers are also very similar.

Be aware that if you use the HSPI SPI buss and its natural pins, the SPI device, on the HSPI buss, cannot 'talk' or send data during program upload time. As an example I have an AdaFruit Ultimate GPs that starts sending data as soon as its powered on. If I put the Ultimate GPS on the HSPI buss, program uploads fail.

Alright took my first crack at FreeRTOS. I'm able to get it to compile and load but i get the SD error:
23:57:12.237 -> SD problem
23:57:12.237 -> SD errorCode: 0X20,0X0

I haven't changed anything as far as hardware so i assume i'm missing something in a referenced file.

// Data logger based on a FIFO to decouple SD write latency from data
// acquisition.

// The FIFO uses two semaphores to synchronize between tasks.

#include <SPI.h>
//#include <FreeRTOS_AVR.h>
#include "SdFat.h"
#include <Wire.h>

// interval between points in units of 1024 usec
const uint16_t intervalTicks = 10;
//------------------------------------------------------------------------------
// SD file definitions
const uint8_t sdChipSelect = 33;
SdFat sd;
SdFile file;
//------------------------------------------------------------------------------
// Fifo definitions

// size of fifo in records
const size_t FIFO_SIZE = 20;

// count of data records in fifo
SemaphoreHandle_t fifoData;

// count of free buffers in fifo
SemaphoreHandle_t fifoSpace;

// data type for fifo item
struct FifoItem_t {
  uint32_t usec;  
  int value;
  int error;
};
// array of data items
FifoItem_t fifoArray[FIFO_SIZE];
//------------------------------------------------------------------------------
// handle for sensor task
TaskHandle_t sens;
static void Task1(void *arg) {
  // index of record to be filled
  size_t fifoHead = 0;
  
  // count of overrun errors
  int error = 0;
  
  // dummy data
  int count = 0;
  
  // initialise the ticks variable with the current time.
  TickType_t ticks = xTaskGetTickCount();
  
  while (1) {
    // wait until time for next data point
    vTaskDelayUntil(&ticks, intervalTicks);

    // get a buffer
    if (xSemaphoreTake(fifoSpace, 0) != pdTRUE) {
      // fifo full - indicate missed point
      error++;
      continue;
    }
    FifoItem_t* p = &fifoArray[fifoHead];
    p->usec = micros();
    
    // replace next line with data read from sensor such as
    // p->value = analogRead(0);
    p->value = count++;
    
    p->error = error;
    error = 0;
    
    // signal new data
    xSemaphoreGive(fifoData);
    
    // advance FIFO index
    fifoHead = fifoHead < (FIFO_SIZE - 1) ? fifoHead + 1 : 0;
  }
}
//------------------------------------------------------------------------------
// SD write task
static void Task2(void *arg) {
  // FIFO index for record to be written
  size_t fifoTail = 0;
  
  // time in micros of last point
  uint32_t last = 0;  
  
  while(1) {
    // wait for next data record
    xSemaphoreTake(fifoData, portMAX_DELAY);
    
    FifoItem_t* p = &fifoArray[fifoTail];

    // print interval between points
    if (last) {
      file.print(p->usec - last);
    } else {
      file.write("NA");
    }
    last = p->usec;
    file.write(',');
    file.print(p->value);
    file.write(',');
    file.println(p->error);

    // release record
    xSemaphoreGive(fifoSpace);
    
    // advance FIFO index
    fifoTail = fifoTail < (FIFO_SIZE - 1) ? fifoTail + 1 : 0;
    
    // check for end run
    if (Serial.available()) {
      // close file to insure data is saved correctly
      file.close();
      
      // print messages
      Serial.println(F("Done"));
      Serial.print(F("Task1 unused stack entries: "));
      Serial.println(uxTaskGetStackHighWaterMark(sens));
      Serial.print(F("Task2 unused stack entries: "));
      Serial.println(uxTaskGetStackHighWaterMark(0));
      Serial.print(F("Free heap (bytes): "));
      //Serial.println(freeHeap());
      while(1);
    }
  }
}
//------------------------------------------------------------------------------
void setup() {
  // task creation status
  portBASE_TYPE s1, s2;
  
  Serial.begin(9600);
  Serial.println(F("Type any character to begin"));
  while(!Serial.available()); 
  
  // open file
  if (!sd.begin(sdChipSelect)
    || !file.open("DATA.CSV", O_CREAT | O_WRITE | O_TRUNC)) {
    Serial.println(F("SD problem"));
    sd.errorHalt();
  }
  // initialize fifoData semaphore to no data available
  fifoData = xSemaphoreCreateCounting(FIFO_SIZE, 0);
  
  // initialize fifoSpace semaphore to FIFO_SIZE free records
  fifoSpace = xSemaphoreCreateCounting(FIFO_SIZE, FIFO_SIZE);
  
  // create sensor task at priority two
  s1 = xTaskCreate(Task1, NULL, configMINIMAL_STACK_SIZE, NULL, 2, &sens);
  
  // create SD write task at priority one
  s2 = xTaskCreate(Task2, NULL, configMINIMAL_STACK_SIZE + 200, NULL, 1, NULL);
  
  // check for creation errors
  if (fifoData == NULL || fifoSpace == NULL || s1 != pdPASS || s2 != pdPASS ) {
    Serial.println(F("Creation problem"));
    while(1); 
  }
  // throw away serial input
  while (Serial.read() >= 0);
  Serial.println(F("Type any character to end"));
  
  // start scheduler
  vTaskStartScheduler();
  Serial.println(F("Insufficient RAM"));
  while(1);
}
//------------------------------------------------------------------------------
void loop() {
  // Not used - idle loop has a very small, configMINIMAL_STACK_SIZE, stack
  // loop must never block
}

If you want something to run as fast as, why use time wasting serial prints?

I use

#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"

for the freeRTOS libraries with the ESP32.

I create tasks this way:
xTaskCreatePinnedToCore ( fParseLIDAR_ReceivedSerial, "fParseLIDAR_ReceivedSerial", TaskStack10K3, NULL, Priority5, NULL, TaskCore0 ); // assigned to core 0 you may want to use
xTaskCreate ( fParseLIDAR_ReceivedSerial, "fParseLIDAR_ReceivedSerial", TaskStack10K3, NULL, Priority5, NULL ); till you get the code working.

The ESP32 is not an AVR.

Here is the link for the freeRTOS API: FreeRTOS API categories
It is safe to default the task sizes to 10K stack size.
use code like this to get the operating stack size and adjust your task creation stack size.

void fDoLIDAR( void * pvParameters )
{
  int iLIDAR_Distance = 0;
  ////  int iLIDAR_Strength = 0;
  while (1)
  {
    xEventGroupWaitBits (eg, evtDoLIDAR, pdTRUE, pdTRUE, portMAX_DELAY) ;
    tfmini.externalTrigger();
    iLIDAR_Distance = tfmini.getDistance();
    if ( iLIDAR_Distance > LIDAR_Max_Distance )
    {
      Serial.print ( " TFMini distance issue " );
      Serial.println (  iLIDAR_Distance );
    }
    xSemaphoreTake( sema_LIDAR_INFO, xSemaphoreTicksToWait );
    x_LIDAR_INFO.Range[x_LIDAR_INFO.CurrentCell] = iLIDAR_Distance;
    xSemaphoreGive( sema_LIDAR_INFO );
    xEventGroupSetBits( eg, evtLIDAR_ServoAspectChange );
    // Serial.print( "fDoLIDAR " ); // uncomment to get stack size used.
    //        Serial.print(uxTaskGetStackHighWaterMark( NULL ));
    //            Serial.println();
    //            Serial.flush();
  }
  vTaskDelete( NULL );
}

Why complain about speed if you are going to use:

// wait until time for next data point
    vTaskDelayUntil(&ticks, intervalTicks)

If you look at the below code you will see that each task event triggers the other task, The speed is dictated by the time it takes to actually run the code. I call this RoundRobin.


The ESP32 has a 64bit uSec counter. High Resolution Timer (ESP Timer) - ESP32 - — ESP-IDF Programming Guide latest documentation
Use uint64_t last = esp_timer_get_time() to get the uS time with the ESP32.
Here is a simple use of freeRTOS on the ESP32

#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 <Adafruit_NeoPixel.h>
#include "AudioAnalyzer.h"
////
/* define event group and event bits */
EventGroupHandle_t eg;
#define evtDo_AudioReadFreq       ( 1 << 0 ) // 1
#define evtDo_LEDs                 ( 1 << 1 ) // 10
////
const int NeoPixelPin = 26;
const int LED_COUNT = 24; //total number of LEDs in the strip
const int NOISE = 150; // noise that you want to chop off
const int SEG = 6; // how many parts you want to separate the led strip into
const int SerialDataBits = 115200;
const int Priority4 = 4;
const int TaskStack40K = 40000;
const int TaskCore1  = 1;
const int TaskCore0 = 0;
const int AudioSampleSize = 6;
const int Brightness = 150;
const int A_D_ConversionBits = 4096; // arduino use 1024, ESP32 use 4096
////
Analyzer Audio = Analyzer( 5, 15, 36 );//Strobe pin ->15  RST pin ->2 Analog Pin ->36
// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
Adafruit_NeoPixel leds = Adafruit_NeoPixel( LED_COUNT, NeoPixelPin, NEO_GRB + NEO_KHZ800 );
////
int FreqVal[7];//create an array to store the value of different freq
////
void setup()
{
  eg = xEventGroupCreate();
  Serial.begin( SerialDataBits );
  Audio.Init(); // start the audio analyzer
  leds.begin(); // Call this to start up the LED strip.
  clearLEDs();  // This function, defined below, de-energizes all LEDs...
  leds.show();  // ...but the LEDs don't actually update until you call this.
  //////////////////////////////////////////////////////////////////////////////////////////////
  xTaskCreatePinnedToCore( fDo_AudioReadFreq, "fDo_ AudioReadFreq", TaskStack40K, NULL, Priority4, NULL, TaskCore1 ); //assigned to core
  xTaskCreatePinnedToCore( fDo_LEDs, "fDo_ LEDs", TaskStack40K, NULL, Priority4, NULL, TaskCore0 ); //assigned to core
  xEventGroupSetBits( eg, evtDo_AudioReadFreq );
} // setup()
////
void loop() {} // void loop
////
void fDo_LEDs( void *pvParameters )
{
  int j;
  leds.setBrightness( Brightness ); //  1 = min brightness (off), 255 = just below max brightness.
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDo_LEDs, pdTRUE, pdTRUE, portMAX_DELAY);
    j = 0;
    //assign different values for different parts of the led strip
    for (j = 0; j < LED_COUNT; j++)
    {
      if ( (0 <= j) && (j < (LED_COUNT / SEG)) )
      {
        set(j, FreqVal[0]); // set the color of led
      }
      else if ( ((LED_COUNT / SEG) <= j) && (j < (LED_COUNT / SEG * 2)) )
      {
        set(j, FreqVal[1]); //orginal code
      }
      else if ( ((LED_COUNT / SEG * 2) <= j) && (j < (LED_COUNT / SEG * 3)) )
      {
        set(j, FreqVal[2]);
      }
      else if ( ((LED_COUNT / SEG * 3) <= j) && (j < (LED_COUNT / SEG * 4)) )
      {
        set(j, FreqVal[3]);
      }
      else if ( ((LED_COUNT / SEG * 4) <= j) && (j < (LED_COUNT / SEG * 5)) )
      {
        set(j, FreqVal[4]);
      }
      else
      {
        set(j, FreqVal[5]);
      }
    }
    leds.show();
    xEventGroupSetBits( eg, evtDo_AudioReadFreq );
  }
  vTaskDelete( NULL );
} // void fDo_ LEDs( void *pvParameters )
////
void fDo_AudioReadFreq( void *pvParameters )
{
  //  int64_t EndTime = esp_timer_get_time();
  //  int64_t StartTime = esp_timer_get_time(); //gets time in uSeconds like Arduino Micros
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDo_AudioReadFreq, pdTRUE, pdTRUE, portMAX_DELAY);
    // EndTime = esp_timer_get_time() - StartTime;
    Audio.ReadFreq(FreqVal);
    for (int i = 0; i < 7; i++)
    {
      FreqVal[i] = constrain( FreqVal[i], NOISE, A_D_ConversionBits );
      FreqVal[i] = map( FreqVal[i], NOISE, A_D_ConversionBits, 0, 255 );
    }
    // StartTime = esp_timer_get_time();
    xEventGroupSetBits( eg, evtDo_LEDs );
  }
  vTaskDelete( NULL );
} // fDo_ AudioReadFreq( void *pvParameters )
////
void set(byte position, int value)
{
  if ( (0 <= position) && (position < LED_COUNT / SEG) ) // segment 0 (bottom to top), red
  {
    if ( value == 0 )
    {
      leds.setPixelColor( position, 0 );
    }
    else
    {
      leds.setPixelColor( position, leds.Color( value , 0, 0) );
    }
  }
  else if ( (LED_COUNT / SEG <= position) && (position < LED_COUNT / SEG * 2) ) // segment 1 yellow
  {
    if ( value == 0 )
    {
      leds.setPixelColor(position, leds.Color(0, 0, 0));
    }
    else
    {
      leds.setPixelColor(position, leds.Color( value, value, 0)); 
    }
  }
  else if ( (LED_COUNT / SEG * 2 <= position) && (position < LED_COUNT / SEG * 3) ) // segment 2 pink
  {
    if ( value == 0 )
    {
      leds.setPixelColor(position, leds.Color(0, 0, 0));
    }
    else
    {
        leds.setPixelColor(position, leds.Color( value, 0, value * .91) ); // pink
    }
  }
  else if ( (LED_COUNT / SEG * 3 <= position) && (position < LED_COUNT / SEG * 4) ) // seg 3, green
  {
    if ( value == 0 )
    {
      leds.setPixelColor(position, leds.Color( 0, 0, 0));
    }
    else
    {
      leds.setPixelColor( position, leds.Color( 0, value, 0) ); //
    }
  }
  else if ( (LED_COUNT / SEG * 4 <= position) && (position < LED_COUNT / SEG * 5) ) // segment 4, leds.color( R, G, B ), blue
  {
    if ( value == 0 )
    {
      leds.setPixelColor(position, leds.Color( 0, 0, 0));
    }
    else //
    {
      leds.setPixelColor(position, leds.Color( 0, 0, value) ); // blue
    }
  }
  else // segment 5
  {
    if ( value == 0 )
    {
      leds.setPixelColor(position, leds.Color( 0, 0, 0)); // only helps a little bit in turning the leds off
    }
    else
    {
      leds.setPixelColor( position, leds.Color( value, value * .3, 0) ); // orange
    }
  }
} // void set(byte position, int value)
////
void clearLEDs()
{
  for (int i = 0; i < LED_COUNT; i++)
  {
    leds.setPixelColor(i, 0);
  }
} // void clearLEDs()

freeRTOS semaphores should be declared in the global section:

SemaphoreHandle_t sema_ReceiveSerial_LIDAR;
SemaphoreHandle_t sema_ParseLIDAR_ReceivedSerial;
SemaphoreHandle_t sema_AlarmMessage;

In setup() I initialize the semaphore, there are two types of semaphores

xTaskCreatePinnedToCore ( fParseLIDAR_ReceivedSerial, "fParseLIDAR_ReceivedSerial", TaskStack10K3, NULL, Priority5, NULL, TaskCore0 ); // assigned to core 0
  sema_ParseLIDAR_ReceivedSerial  = xSemaphoreCreateMutex();
  xSemaphoreGive ( sema_ParseLIDAR_ReceivedSerial );
 xTaskCreatePinnedToCore ( fInitial_Position, "fInitial_Position", TaskStack10K, NULL, Priority3, NULL, TaskCore1 ); // assigned to core
  sema_HexaPodAdjustment = xSemaphoreCreateBinary();

See the freeRTOS API doc for the difference.

Hello, replying to an old post but thought it would be appropriate to post here first before starting a new one since i'm still building off these previous comments. Just to summarize, I was able to get my old, simple data logger program to get me the data I needed at up to 2000 Hz but it did miss some data here and there with the SD write taking time. Overall I made it work but its not ideal.

Now I want to get Version 2 going with a lot of the suggestions that were made here, specifically using FreeRTOS. I've created a data logger script based on suggestions here and some other examples I've found that use the ESP32. My code is ok i'm sure has mistakes, I think its actually built off Version 8 of FreeRTOS. I'm trying to use a fifo buffer but I'm guessing a Stream buffer in FreeRTOS would be better.
Overall i'm getting expected data to about 100 Hz (my sample rate) and ideally i would like to get data at 1000 Hz rate. Once I go above 100 Hz it gets data but doesn't seem to be recording the actual motion. I'm guessing its getting overrun, thus the data is lost very quickly, plus I'm sure my file.flush() is a poor placement. I'm thinking I probably need a stream buffer and appropriately placed interrupt.

// 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

Part1:

//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/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"

//------------------------------------------------------------------------------
// SD file definitions
const uint8_t sdChipSelect = 33;
SdFat sd;
SdFile file;
File logfile;
//------------------------------------------------------------------------------
// Fifo definitions

// size of fifo in records
const size_t FIFO_SIZE = 2000; //can go up to 6000

// count of data records in fifo
SemaphoreHandle_t fifoData;

// count of free buffers in fifo
SemaphoreHandle_t fifoSpace;

// data type for fifo item
struct FifoItem_t {
  uint32_t usec;  
  float valueX;
  float valueY;
  float valueZ; 
  uint8_t error;
};

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

// SD Buffer
//uint16_t count = 0;

// array of data items
FifoItem_t fifoArray[FIFO_SIZE];
//------------------------------------------------------------------------------
// Accel Lis3dh definitions, I2C
#define LIS3DH_CS 16 //should be 32  //ESP32: 14/A6 , Cortex m0: 5, Use for upper accel, hbar, seatpost, etc.
// Sensor I2C 
Adafruit_LIS3DH lis = Adafruit_LIS3DH();
//------------------------------------------------------------------------------
// define two tasks for Sensor Data and SD Write
void TaskGetData( void *pvParameters );
void TaskSDWrite( void *pvParameters );
//------------------------------------------------------------------------------

// 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
  //pinMode(ledPin, OUTPUT);  //set LED to show when switched off
  //pinMode(button, 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 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 50 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, FILE_WRITE);
  //file.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!");

  // initialize fifoData semaphore to no data available
  fifoData = xSemaphoreCreateCounting(FIFO_SIZE, 0);
  
  // initialize fifoSpace semaphore to FIFO_SIZE free records
  fifoSpace = xSemaphoreCreateCounting(FIFO_SIZE, FIFO_SIZE);

Part 2 (rest of code):

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

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

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

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

  for (;;) // A Task shall never return or exit.
  {
    // index of record to be filled
    size_t fifoHead = 0;

    // count of overrun errors
    int error = 0;

    // initialise the ticks variable with the current time.
    TickType_t ticks = xTaskGetTickCount();

    while (1) {
    // wait until time for next data point
    vTaskDelayUntil(&ticks, intervalTicks);

    // get a buffer
    if (xSemaphoreTake(fifoSpace, 0) != pdTRUE) {
      // fifo full - indicate missed point
      error++;
      continue;
    }
    FifoItem_t* p = &fifoArray[fifoHead];
    p->usec = micros();

    // replace next line with data read from sensor
    //Micro = micros(); 
    sensors_event_t event;
    lis.getEvent(&event);
    p->valueX = event.acceleration.x;
    p->valueY = event.acceleration.y;
    p->valueZ = event.acceleration.z;
    /*lis.read();
    p->valueX = lis.x;
    p->valueY = lis.y;
    p->valueZ = lis.z;*/
    //Serial.print(p->valueX,5);
    //Serial.write(',');
    //Serial.print(p->valueY,5);
    //Serial.write(',');
    //Serial.print(p->valueZ,5);
    //Serial.println();

    // signal new data
    xSemaphoreGive(fifoData);

    // advance FIFO index
    fifoHead = fifoHead < (FIFO_SIZE - 1) ? fifoHead + 1 : 0;
    
    //vTaskDelay(1000);  // one tick delay (1000 uSec/1 mSec) in between reads for 1000 Hz reading 
    
    //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
  }
}
}
//------------------------------------------------------------------------------
void TaskSDWrite(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
    // SD write task
    // FIFO index for record to be written
  size_t fifoTail = 0;
  
  // time in micros of last point
  uint32_t last = 0;  
  
  for (;;)
  {
 
  while(1) {
    // wait for next data record
    xSemaphoreTake(fifoData, portMAX_DELAY);
    
    FifoItem_t* p = &fifoArray[fifoTail];

    // print interval between points
    /*if (last) {
      logfile.print(p->usec - last);
    } else {
      logfile.write("NA");
    }
    last = p->usec;*/
    logfile.print(p->usec); 
    logfile.write(',');
    logfile.print(p->valueX,5);
    logfile.write(',');
    logfile.print(p->valueY,5);
    logfile.write(',');
    logfile.print(p->valueZ,5);
    logfile.print(',');
    logfile.print(p->error);
    logfile.println(); 
    //count++; 
    /*Serial.println(p->usec);
    Serial.print(p->valueX,5);
    Serial.write(',');
    Serial.print(p->valueY,5);
    logfile.write(',');
    Serial.print(p->valueZ,5);
    //Serial.println(p->error);
    Serial.println();*/
    
    // release record
    xSemaphoreGive(fifoSpace);
    
    // advance FIFO index
    fifoTail = fifoTail < (FIFO_SIZE - 1) ? fifoTail + 1 : 0;
    
    //if (count == 512){
    logfile.flush(); 
    //count = 0;
    //}
    
    //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
      
     // while(1);
  //}
 }
} 
}