Improve SD logger

jevgienij
Posts: 3
Joined: Sun Oct 23, 2022 9:40 am

Improve SD logger

Postby jevgienij » Sun Oct 23, 2022 9:51 am

I'm working on a project to read data over CAN, display it on LCD and log data to SD card. LCD is on HSPI and SD card on VSPI. CAN and LCD code is working on core 1 and SD on core 0, there's no wifi. It's all working pretty well but now I'm looking for further improvements. I set the log to 20 Hz and took a sample of 117024 lines. Below is the "uniq -c" output of the seconds that weren't logged at 20 Hz (left column is the actual rate and right column is a specific second in the log). So only 21 out of 117024 lines. But why does this happen and how can I make sure that every line is logged at 20 Hz?
  • 19 1632
    18 3195
    17 5393
    16 5401
    16 1622
    14 5397
    14 1628
    14 1625
    14 1624
    13 5400
    13 5398
    13 5396
    13 5395
    13 5394
    13 1626
    12 1631
    12 1630
    12 1627
    12 1623
    8 5399
    8 1629
  1. float timeStamp;
  2. char filenameBuffer[23];
  3. String filename;
  4. bool logStarted = 0;
  5. uint32_t lastSyncTime = 0, logStartTime = 0;
  6.  
  7. File file;
  8.  
  9. void initSD()
  10. {
  11.     if (SD.begin(5, SPI, 80000000))
  12.     {
  13.         Serial.println("Card Mount OK");
  14.         updateFromFS(SD);
  15.         generateFileName();
  16.         sdInitialised = true;
  17.     }
  18.     else
  19.     {
  20.         Serial.println("Card Mount Failed");
  21.         return;
  22.     }
  23. }
  24.  
  25. void logToSD()
  26. {
  27.     if (!sdInitialised) return;
  28.     if (!EMUfresh) return;
  29.  
  30.     if (!logStarted)
  31.     {
  32.         file = SD.open(filenameBuffer, FILE_APPEND);
  33.         if (file)
  34.         {
  35.             file.print("Time,RPM,MAP,TPS,IAT,CLT,Lambda,PulseWidth,EGT1,EGT2,Vbatt,Baro,IgnAngle,LambdaCorr,Gear,Pwm1,Vss,EmuTemp,CEL,FLAGS1,LambdaTarget,OilTemp,OilPress,CanState\r\n");
  36.             logStartTime = millis();
  37.             logStarted = true;
  38.         }
  39.     }
  40.    
  41.     uint32_t duration = millis() - logStartTime;
  42.     uint32_t seconds = duration / 1000;
  43.     uint32_t milliseconds = duration % 1000;
  44.     String dataString = "";
  45.    
  46.     dataString += seconds;
  47.     dataString += ".";
  48.     if (milliseconds < 100) { dataString += "0"; }
  49.     if (milliseconds < 10) { dataString += "0"; }
  50.     dataString += milliseconds;
  51.     dataString += ",";
  52.     dataString += String(emu_data.RPM);
  53.     dataString += ",";
  54.     dataString += String(emu_data.MAP);
  55.     dataString += ",";
  56.     dataString += String(emu_data.TPS);
  57.     dataString += ",";
  58.     dataString += String(emu_data.IAT);
  59.     dataString += ",";
  60.     dataString += String(emu_data.CLT);
  61.     dataString += ",";
  62.     dataString += String(emu_data.lambda);
  63.     dataString += ",";
  64.     dataString += String(emu_data.pulseWidth);
  65.     dataString += ",";
  66.     dataString += String(emu_data.EGT1);
  67.     dataString += ",";
  68.     dataString += String(emu_data.EGT2);
  69.     dataString += ",";
  70.     dataString += String(emu_data.vBat);
  71.     dataString += ",";
  72.     dataString += String(emu_data.baro);
  73.     dataString += ",";
  74.     dataString += String(emu_data.ignAngle, 0);
  75.     dataString += ",";
  76.     dataString += String(emu_data.lambdaCorr, 0);
  77.     dataString += ",";
  78.     dataString += String(emu_data.gear);
  79.     dataString += ",";
  80.     dataString += String(emu_data.pwm1);
  81.     dataString += ",";
  82.     dataString += String(emu_data.vssSpeed);
  83.     dataString += ",";
  84.     dataString += String(emu_data.emuTemp);
  85.     dataString += ",";
  86.     dataString += String(emu_data.cel);
  87.     dataString += ",";
  88.     dataString += String(emu_data.flags1);
  89.     dataString += ",";
  90.     dataString += String(emu_data.lambdaTarget);
  91.     dataString += ",";
  92.     dataString += String(emu_data.oilTemperature);
  93.     dataString += ",";
  94.     dataString += String(emu_data.oilPressure);
  95.     //dataString += ",";
  96.     //dataString += String(can_error_data.state[1]); //Only first character
  97.     dataString += "\r\n";
  98.  
  99.     file.print(dataString.c_str());
  100.  
  101.     if (millis() - lastSyncTime > 1000)
  102.     {
  103.         lastSyncTime = millis();
  104.         file.flush();
  105.     }
  106. }
  107.  
  108. void generateFileName()
  109. {
  110.     char intBuffer[5];
  111.    
  112.     strcpy(filenameBuffer, "/");
  113.     itoa(random(1000, 9999), intBuffer, 10);
  114.     strcat(filenameBuffer, intBuffer);
  115.     strcat(filenameBuffer, "-");
  116.     itoa(random(10), intBuffer, 10);
  117.     strcat(filenameBuffer, intBuffer);
  118.     strcat(filenameBuffer, "-");
  119.     itoa(random(10), intBuffer, 10);
  120.     strcat(filenameBuffer, intBuffer);
  121.     strcat(filenameBuffer, "_");
  122.     itoa(random(10), intBuffer, 10);
  123.     strcat(filenameBuffer, intBuffer);
  124.     strcat(filenameBuffer, ".");
  125.     itoa(random(10), intBuffer, 10);
  126.     strcat(filenameBuffer, intBuffer);
  127.     strcat(filenameBuffer, ".");
  128.     itoa(random(10), intBuffer, 10);
  129.     strcat(filenameBuffer, intBuffer);
  130.     strcat(filenameBuffer, ".csv");
  131.    
  132.     Serial.println(filenameBuffer);
  133. }

markdh102
Posts: 11
Joined: Sat Apr 18, 2020 12:03 pm

Re: Improve SD logger

Postby markdh102 » Fri Oct 28, 2022 6:29 am

I do something very similar. I log GPS/MEMS/BME/Bluetooth OBD/Timestamp every 100mS and store to an SD card.
I found that most writes to the SD card took <5mS. Sometimes however they took approx 320mS.
This is due to the way the blocks are written to the SD card at a low level and can't be changed (at least I don't think so).
So, I created a circular RAM buffer to hold my records. This all happens at 10Hz on the main core.
In the 2nd processor core, I pull 1 record at a time out of this circular buffer and write it to the SD card. That way, it doesn't matter if it takes a while to write as I'm still storing records into my RAM buffer.
I no longer have timestamps other than 100ms intervals.
Hope this helps.

jevgienij
Posts: 3
Joined: Sun Oct 23, 2022 9:40 am

Re: Improve SD logger

Postby jevgienij » Tue Jan 03, 2023 11:31 pm

Can you show some example code of it?

markdh102
Posts: 11
Joined: Sat Apr 18, 2020 12:03 pm

Re: Improve SD logger

Postby markdh102 » Wed Jan 04, 2023 4:01 pm

This is a very abbreviated form of the code I use. Hopefully it will show you what I am doing.
I don't know if this will compile. I've not got my system to hand. And it is MOST DEFINITELY NOT the most elegant of code.

Code: Select all

#include <Arduino.h>
#include "SdFat.h"


SdFat                   _sd;
File                    _myFile;


#define MAX_RECORDS                   100
#define MAX_COMBINED_OBD_DATA_LENGTH  200
#define MAX_RECORD_LENGTH             232 + MAX_COMBINED_OBD_DATA_LENGTH


uint32_t                _ulngRecordCount;
int volatile            _intInCount;
int volatile            _intOutCount;
char volatile           _bytDataArray[MAX_RECORDS][MAX_RECORD_LENGTH];
char                    _strTempSDcardData[MAX_RECORD_LENGTH];
char                    _strCombinedOBDdata[MAX_COMBINED_OBD_DATA_LENGTH];
bool volatile           _blnRecordLocked;
bool volatile           _blnStopLogging;
char                    _bytFileInUse;


int setupSDcard(void)
{
  uint32_t  cardSize;
  int       intSDcardStatus = 0;
  
  // Initialize at the highest speed supported by the board that is
  // not over 50 MHz. Try a lower speed if SPI errors occur.
  if (!_sd.begin(SS, SD_SCK_MHZ(20))) 
  {
    #ifdef SERIAL_DEBUG
    Serial.println(F("SD: Mount failed"));
    #endif
    return -1;
  }

  cardSize = _sd.card()->cardSize();
  if (cardSize == 0) 
  {
    #ifdef SERIAL_DEBUG
    Serial.println(F("SD: Cardsize failed"));
    #endif
    return -1;
  }
  
  switch (_sd.card()->type()) 
  {
    case SD_CARD_TYPE_SD1:
      #ifdef SERIAL_DEBUG
      Serial.println(F("SD: Type SD1"));
      #endif
    break;

    case SD_CARD_TYPE_SD2:
      #ifdef SERIAL_DEBUG
      Serial.println(F("SD: Type SD2"));
      #endif
    break;

    case SD_CARD_TYPE_SDHC:
      #ifdef SERIAL_DEBUG
      if (cardSize < 70000000) 
      {
        Serial.println(F("SD: Type SDHC"));
      } 
      else 
      {
        Serial.println(F("SD: Type SDXC"));
      }
      #endif
      break;
    
    default:
      intSDcardStatus = -2;      
  }
  
  if (intSDcardStatus == 0)
  {
    #ifdef SERIAL_DEBUG
    Serial.printf("SD: Size = %d BYTES\r\n", (uint32_t) (cardSize * 0.512));

    _sd.ls("/", LS_SIZE | LS_R );
    #endif
  }  
  return intSDcardStatus;
}


// This code runs on the 2nd processor core and handles writing to the SD card
void Task1Code(void *parameter)
{
  int   next;
  
  for (;;)
  {
    delay(1);
    
    // Only if the foreground loop() is not adding a new record
    if (_blnRecordLocked == false)
    {
      // If we are trying to stop logging then make sure we can't write any more records
      if (_blnStopLogging == true)
      {
        _blnRecordLocked = true;
      }
      
      // Run a circular buffer
      if (_intInCount != _intOutCount)
      {
        next = _intOutCount + 1;
        if (next >= MAX_RECORDS)
          next = 0;
        
        if (strlen((char *)&_bytDataArray[_intOutCount][0]) < MAX_RECORD_LENGTH)
          strcpy(_strTempSDcardData, (char *)&_bytDataArray[_intOutCount][0]);
        else
          strcpy(_strTempSDcardData, "TOO LONG\r\n");

        // This normally takes < 5mS, but occasionally (when it writes the buffer) it takes about 320mS !!!
        // This is why we run it in a seperate core so it can do it's own thing without holding anyone up
        _myFile.println(_strTempSDcardData);

        _intOutCount = next;
      }

      // The other core checks this flag when trying to stop logging
      // When we get here, the SD card has been fully written so allow the
      // foreground task on the other core to continue
      _blnStopLogging = false;
    }
  }
}


// I have read that the 'idle' task runs at priority 0 so create any other tasks with a higher one or else 'weirdness' may result???
// Setup() and Loop() run on core 1 at priority 1
// WiFi is handled by core 0 and runs at MAX_PRIORITY-1 (I think this equates to 24)
// Bluetooth is handled by core 0 and runs at priority 18
void createAtask(void)
{
  //                      Function   Name     Stack  I     P  Handle  Core
  // Stack is siize in words
  // I is task input parameter
  // P is priority 0 = lowest
  xTaskCreatePinnedToCore(Task1Code, "Task1", 10000, NULL, 1, &_task1, 0);  
}


void stopLogging(void)
{
  // Only if in use or paused
  if (_bytFileInUse == 'Y' || _bytFileInUse == 'P')
  {
    if (_sd.exists(_strFilename))
    {
      // Stop the process on the other core accessing the SD card
      _blnStopLogging = true;
      while (_blnStopLogging == true) ;

      _myFile.close();
      _bytFileInUse = 'N';
    }
  }  
}


void startLogging(void)
{
  // Only if not already logging
  if (_bytFileInUse == 'N')
  {
    // Reset the SD card writer engine
    _blnStopLogging = false;
    _intOutCount = _intInCount = 0;
    _blnRecordLocked = false; 
       
    _ulngRecordCount = 0;

		// NOTE I HAVE AN RTC DEVICE FITTED

    // We need to start a new file so update the date/time registers from the RTC
    //readDateTime();
    // We are limited to an 8.3 filename format so try to be as unique as we can...
    // YMMDDHHm.mss
    //sprintf(_strFilename, "%d%02d%02d%02d%d.%d%02d", getYears() % 10, getMon(), getDate(), getHour(), getMinute() / 10, getMinute() % 10, getSecond());
    // set date time callback function
    //SdFile::dateTimeCallback(dateTime);

		strcpy(_strFilename, "TEST.DAT");

    // Write my header data (I've removed the OBD calls)
    // O_WRITE | OCREAT is supposedly faster than FILE_WRITE
    _myFile = _sd.open(_strFilename, O_WRITE | O_CREAT);
    _myFile.print("Battery Volts : ");
    _myFile.print("OBD standards supported : ");
    _myFile.print("DTC codes : ");
    _myFile.print("VIN :");
    _myFile.print("Supported PIDS : ");
    _myFile.println("\r\n");
    
    _bytFileInUse = 'Y';
  }

  // If paused just continue logging
  if (_bytFileInUse == 'P')
  {
    _bytFileInUse = 'Y';
  }
}


void updateTheFile(void)
{
  char  strCompleteRecord[MAX_RECORD_LENGTH];
  
  if (SD.totalBytes() - SD.usedBytes() > 1000)
  {
    sprintf(strCompleteRecord, "ALL OF MY DATA"); 
    
    _ulngRecordCount++;
    
    if (strlen(strCompleteRecord) < MAX_RECORD_LENGTH)
    {
      int next;
      
      _blnRecordLocked = true;
      next = _intInCount + 1;
      if (next >= MAX_RECORDS)
      {
        next = 0;
      }
      if (next != _intOutCount)
      {
        strcpy((char *)&_bytDataArray[_intInCount][0], strCompleteRecord);
        _intInCount = next;
      }
      _blnRecordLocked = false;
    }
  }  
}


void setup() 
{
  _ulngRecordCount = 0;
  _bytFileInUse = 'N';
  _intOutCount = _intInCount = 0;
  _blnRecordLocked = false;
  _blnStopLogging = false;

  // Get the SD card task running
  createAtask();

  setupSDcard()
  
  startLogging();
}


void loop() 
{
  if (_bytFileInUse == 'Y')
  {
    // I call this every 100mS
    updateTheFile();
  }
}


Who is online

Users browsing this forum: No registered users and 58 guests