Teensy 3.6, SD Card, missing time in milliseconds

Status
Not open for further replies.

eddieL

New member
Hi,
I am using this datalogger tutorial as the basis for a.. well, datalogger.
https://www.arduino.cc/en/tutorial/datalogger

I'm losing time (millis) at a pretty consistent interval(s).

In the tutorial, 3 analog pins are being read, written to sd card and spit out the the serial monitor

in my test.. i am just writing millis() to the sd card, no serial monitor and no pins.

Each write takes about 3-4 milliseconds.. however, after 6.8 seconds (varies per test - but are constant) i get a time drop. example: 1000 milliseconds was recorded, then the next line would be 1070 and the millis count continues from there... 6.8 seconds later.. another 50 milli jump and continues to count from there.

I'm using a teensy 3.6, with a class 10 san disk 32g uSD drive.

What could be the issue? with 8 analog sensors, spitting to the serial monitor, I get the same issue

I was investigating using an external RTC, but the resolution is to low

Any Ideas?
 
I guess that small delays at each write add up until every 6.8s, a kind of overflow occurs, especially since this Arduino tutorial code is highly inefficient.
An improved approach could be:
- not opening and closing the file each time you write, but defining a data acquisition period before and then opening the file once, write during the whole data acquisition period, and then closing the file once.
- pre-allocate disk space for your file at the beginning, so that the SD file system will not have to allocate new blocks during the data acquisition period, that pulls regularly the handbrake during continuous write operations.
- Cumulate the ADC readings in memory until you reach the block size of 512 Bytes and write these at once.
 
For a possible time reference the T_3.6 has a cycle counter that can be enabled.

Once enabled it counts each processor cycle based on clock speed of F_CPU.

So regular reference with a uint32_t will give the passing clock cycles - with 180M ( or whatever the compiled clock speed is ) passing between each second - or partial second. It will have error from true time based on the crystal/clocking of the Teensy at hand - maybe 500 or 2500 cycles off per second - but it will be relatively consistent - with some changes over time with change in crystal output.

Activate the cycle counter - not started by default on T_3.6:
Code:
  ARM_DEMCR |= ARM_DEMCR_TRCENA; // Assure Cycle Counter active
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

Read the cycle counter:
Code:
  static uint32_t LastCnt = 0;

  LastCnt = ARM_DWT_CYCCNT;

Doing math or compares against two readings like { ARM_DWT_CYCCNT - LastCnt } with unit32_t vars will work for the difference in the readings.

That doesn't solve the problem - but should give a true measure to compare against the millis() time slipping. If the 'datalogger' code disables interrupts across those long I/O periods that would explain the lost millis().

There are other posts on ARM_DWT_CYCCNT usage if the above leaves questions.

Also there are other 'fast data loggers' posted on forum.pjrc that may use better SD access methods that won't disable interrupts and cause time loss or other side effects.
 
crossposted with @Theremingenieur who makes great points to look for or implement. Ideally there are posted data loggers that will implement such things.

There is a Teensy included sample that will at least use Teensy hardware aware software : T:\arduino-1.8.12\hardware\teensy\avr\libraries\SD\examples\Datalogger\Datalogger.ino

To run that on T_3.6 use :: chipSelect = BUILTIN_SDCARD;

It still opens and closes the file each time and does not buffer the write data into 512 byte logical blocks, but it might be a starting point to implement that.
 
This data logger is missing so many of the elements of a true data logger that it really doesn't deserve the name "data logger"---but that's another discussion altogether.

The intermittent longer write times that you see are a common occurrence with SD cards. The file system buffers up your input and only writes to the SD card when it has accumulated a full sector of 512 bytes. The file open and close messes all this up, of course, and the file system has to write a partial sector at close and then read that sector on the next open, add your new data, then write the sector on the following close. That is terribly inefficient. The SD card internal controller may help by buffering the sectors and writing to a remapped and erased sector. However, at some point the SD card controller has to erase a new block of sectors. That is probably what is taking the extra 50milliseconds.

In a real data logger, you accumulate your data in an interrupt handler called from an interval timer and write the data to a large buffer. The loop() function monitors the state of that buffer and writes the data to the SD card when needed. It is important that the buffer be able to hold at least enough data to cover the longest time taken to write to the file. In your case that would be at least 50mSec worth of data.

There are two general ways to handle the buffering: Queues and ping-pong buffers. Both these techniques work better with binary data of fixed length than they do with the strings in the simplistic example.
 
Since I was less than complimentary about the capabilities of that simple logger example, I decided to add some code to back up my comments.

The following data logger example has the elements I consider necessary for a simple logger:

1. Commands to start and stop logging.
2. A command to play back the logged data (just lines of text sent to Serial in this program).
3. Interrupt-driven collection timing to collect at a specified sampling rate.
4. Buffering of data so that SD Card writes do not change the timing of sample collection.

The sample code lacks such niceties as setting proper times in the file directory entries and the
automatic generation of new file names based on the starting time---things I include in my more
sophisticated loggers. The code doesn't take advantage of the significant improvements
in SD file reading and writing in Bill Greiman's SDFat 2.0 Beta. It also skips the significant advantages
of the ADC object library written by PEDVIDE.

Code:
/**************************************************************
  Simple SD card datalogger for Teensy 3.6

  This example shows how to log data from three analog sensors
  to an SD card using the SD library.  As much as possible, the
  code uses the simplest Arduino functions, adding Teensy-specific
  code only where necessary (such as IntervalTimer). As a result, it doesn't 
  take advantage of options like the ADC library or the SDFat library.

  The circuit:
   analog sensors on analog ins A0, A1, and A2
   SD card attached to built-in SD Card slot on t3.6


  This example uses  buffering and interval timer to collect at regular
  intervals without collection timing jitter caused by SDC writes.
  A time stamp is saved with each record.

  A minimal user interface is added with three one-letter commands:
      'r'   Open file and start recording data
      'q'   Quit logging data and close file
      'p'   Play back data in CSV format


NOTES:
    1. A lot of the data handling routines could be shortened by broader
       use of pointers.  I stuck with indices into arrays because it 
       better illustrates some of the principles and the speed difference
       is minimal with the ARM GCC compiler.
    2. A lot of the code is overly simplified--lacking in parameters to 
       functions, etc. etc.
    3. More descriptive output, such as bytes written, timing data, etc,
       would be nice, but the example is long enough as it is.
    4. I used printf() and sprintf() to output multiple variables with
       a single line of code.
    5. I used the Arduino String object when reading the data file. 
       There are many reasons to avoid this object on smaller Arduino
       systems, as it can crash your program if it tries to read a malformed
       file.  Since I'm reading only strings that I have generated, I hope
       to get away with this and avoid many more lines of code to read the
       file for playback.
       

  
Written for Teensy 3.6 by M. Borgerson  May 6, 2020
********************************************************************/

// SPI library not needed with Teensy built-in SD card
#include <SD.h>

IntervalTimer ADCTimer;

// keep sample rate less than 1000 for now
#define SAMPLERATE 100
const char *logfilename = "DATALOG.CSV";

// A simple structure to hold time stamp and three analog values
// The structure takes up 12 bytes to align ltime on 4-byte boundary
struct datrec {
  uint32_t ltime;
  uint16_t a0, a1, a2;
  uint16_t spare;
};

#define BUFFSIZE  128
// allocate two buffers, each of which holds 128 records
// If you collect faster than about 200 samples/second, you may
// need to allocate larger buffers.
struct datrec dbuff0[BUFFSIZE];
struct datrec dbuff1[BUFFSIZE];

File dataFile;

#define DEBUGPRINT true

// these variables are declared volatile because they are changed or used in the
// interrupt handler
volatile uint16_t saveidx = 0;
volatile int16_t writebuffnum = -1;
volatile uint16_t savebuffnum = 0;
volatile uint32_t filestartmilli = 0;

const int chipSelect = BUILTIN_SDCARD;
const int ledpin = 13;

#define LEDON  digitalWriteFast(ledpin, HIGH);
#define LEDOFF digitalWriteFast(ledpin, LOW);

const char compileTime [] = "Simple Data Logger  Compiled on " __DATE__ " " __TIME__;
void setup() {
  pinMode(ledpin, OUTPUT);
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println(compileTime);
  Serial.print("Initializing SD card...");

  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more except fast blink LED
    while (1) {
      LEDON
      delay(50);
      LEDOFF
      delay(50);

    }
  }
  Serial.println("card initialized.");
  Serial.println("Enter command selected from (r, q, p)");
}

void loop() {
  // put your main code here, to run repeatedly:
  char ch;
  if (Serial.available()) {
    ch = Serial.read();
    if (ch == 'r') StartLogging();
    if (ch == 'q') StopLogging();
    if (ch == 'p') PlaybackLog();
  }
  // Now check to see if a buffer is ready to be written to SD
  // writebuffnum will be set in the interrupt handler
  if (writebuffnum == 0) { //is dbuff0 ready?
    LEDON
    writebuffnum = -1;
    if (dataFile) { //if the file is open write dbuff0
      WriteCSV(&dbuff0[0], BUFFSIZE);
      if (DEBUGPRINT) {
        Serial.print("Writing dbuff0 to data file.  tmilli = ");
        Serial.printf(" %lu\n", dbuff0[0].ltime);
      }
    }
    LEDOFF
  }
  if (writebuffnum == 1) { // is dbuff1 ready?
    LEDON
    writebuffnum = -1;
    if (dataFile) { //if the file is open write dbuff0
      WriteCSV(&dbuff1[0], BUFFSIZE);
      if (DEBUGPRINT) {
        Serial.print("Writing dbuff1 to data file.  tmilli = ");
        Serial.printf(" %lu \n", dbuff1[0].ltime);
      }
    }
    LEDOFF
  }
  delay(5);
}  // end of the loop() function

// write a buffer to the file as CSV lines. This  takes more time and file space than
// binary format, but can be directly read on PC.
//
// On a T3.6 AT 168MHz, this usually takes about 25mSec, but that can increase to
// over 150mSec when the SD Card has to erase a new block.
// That's not a problem with 100Hz sampling, since we have over 1 second of buffered data.
void WriteCSV(volatile struct datrec *drp, size_t numlines){
uint16_t i;  
char outline[100];
  for(i= 0; i< numlines; i++){
    sprintf(outline,"%6lu, %5u, %5u, %5u\n", drp->ltime,drp->a0,drp->a1, drp->a2);
    dataFile.print(outline);
    drp++;  // advance pointer to next record in buffer
  }
}

// This is the interrupt handler for the interval timer.
// It saves the data in the data buffer structures.  The output is converted
// to CSV text when it is saved to the output file in the main loop.
// We don't want to do the conversion to a string in the interrupt handler
// since it takes a lot more time and many of the string routines are not
// reentrant. (Programmer talk for "They don't play nicely when interrupted")
void ADCChore(void) {
  uint32_t tmilli;
  tmilli = millis() - filestartmilli;
  // save in  the proper buffer--defined by savebuffnum
  if (savebuffnum == 0) { // put data in dbuff0
    dbuff0[saveidx].ltime = tmilli;
    dbuff0[saveidx].a0 = analogRead(A0);
    dbuff0[saveidx].a1 = analogRead(A1);
    dbuff0[saveidx].a2 = analogRead(A2);
    saveidx++;
    if (saveidx >= BUFFSIZE) { // mark buffer for write to  SD
      writebuffnum = 0;
      savebuffnum = 1;   // start saving in other buffer on next interrupt
      saveidx = 0;  // start at beginning of next buffer
    }
  } else {  // must be saving to dbuff1
    dbuff1[saveidx].ltime = tmilli;
    dbuff1[saveidx].a0 = analogRead(A0);
    dbuff1[saveidx].a1 = analogRead(A1);
    dbuff1[saveidx].a2 = analogRead(A2);
    saveidx++;
    if (saveidx >= BUFFSIZE) { // mark buffer for write to  SD
      writebuffnum = 1;
      savebuffnum = 0;   // start saving in other buffer on next interrupt
      saveidx = 0;   // start at beginning of next buffer
    }
  }

}

void StartLogging(void) {
  if (dataFile) {
    Serial.println("Already collecting!");
    return;
  }
  // we open in a mode that creates a new file each time
  // instead of appending as in the Arduino example
  if(SD.exists(logfilename)) SD.remove(logfilename);
  dataFile = SD.open(logfilename, FILE_WRITE);
  // if the file can't be opened, say so
  if (!dataFile) {
    Serial.println("Could not open output file!");
    return;
  }
  Serial.println("Starting logging");
  dataFile.println("  TIME,   A0,    A1,    A2");  // put header line in file
  // initialize some variables for the buffers
  saveidx = 0;   // start saving at beginning of buffer
  savebuffnum = 0;  // start saving in dbuff0
  writebuffnum = -1;  // indicates no buffer ready yet
  //  start the interval timer to begin logging
  filestartmilli = millis();
  ADCTimer.begin(ADCChore, 1000000 / SAMPLERATE); //begin() expects timer period in microseconds
}

void StopLogging(void) {
  Serial.println("Stopping logging");
  ADCTimer.end();
  if (DEBUGPRINT) Serial.println("ADCTimer halted");
  delay(10);
  if (dataFile) {
    Serial.println("Data file closed.");
    dataFile.close();  // if file was open, close it.
  } else {
    if (DEBUGPRINT) Serial.println("dataFile was not open!");
  }
  writebuffnum = -1;
  // in the interest of simplicity, we ignore any partial buffer at the end
}

void PlaybackLog(void) {  
  String datline;
 
  if(dataFile) StopLogging(); // if still recording, stop and close file
  dataFile = SD.open(logfilename, FILE_READ);
  if(!dataFile){
    Serial.println("Could not open data file for reading.");
    return;
  }
  while (dataFile.available()) {
    datline = dataFile.readStringUntil('\n');
    Serial.println(datline); 
  }
  dataFile.close();
  Serial.println("\nPlayback complete.");
}
 
Since I was less than complimentary about the capabilities of that simple logger example, I decided to add some code to back up my comments.

...

Nice looking example and follow up.

<edit>: It compiles and Runs! Easy to understand and change!

Made the three uint16_t in 32 bits and stored millis, micros and ARM_DWT_CYCCNT, and removed the spare. Swapped those first two ms and us between the two buffs [0]and [1] to see them toggle.

Compiled first time and ran to T_4.1 SD card with just short LED blinks after command 'r', then 'q' worked, and so did 'p' to see the results!

And added a LED on time check - where 'took' is us:
Code:
Starting logging
Writing dbuff0 to data file.  tmilli =  10 took 11734
Writing dbuff1 to data file.  tmilli =  1290 took 21199
Writing dbuff0 to data file.  tmilli =  2570 took 12102
Writing dbuff1 to data file.  tmilli =  3850 took 10900
Writing dbuff0 to data file.  tmilli =  5130 took 12170
Writing dbuff1 to data file.  tmilli =  6410 took 20940
Writing dbuff0 to data file.  tmilli =  7690 took 12049
Writing dbuff1 to data file.  tmilli =  8970 took [B]93407[/B]
Writing dbuff0 to data file.  tmilli =  10250 took 79691
Writing dbuff1 to data file.  tmilli =  11530 took 40186
Writing dbuff0 to data file.  tmilli =  12810 took 11898
Writing dbuff1 to data file.  tmilli =  14090 took 11988
…
Writing dbuff0 to data file.  tmilli =  43530 took 11053
Writing dbuff1 to data file.  tmilli =  44810 took [B]500645
[/B]Writing dbuff0 to data file.  tmilli =  46090 took [B]489097
[/B]Writing dbuff1 to data file.  tmilli =  47370 took 10848
…
Writing dbuff1 to data file.  tmilli =  131850 took [B]619179
[/B]Writing dbuff0 to data file.  tmilli =  133130 took [B]312047
[/B]Writing dbuff1 to data file.  tmilli =  134410 took 35150
Writing dbuff0 to data file.  tmilli =  135690 took 32091
 
Last edited:
Wow! Those long times at 44810 and 131850 indicate that there must be some significant block erase and remap times. The faster times on the low end (~11-12mSec) show the increased speed of the T4.1 in doing the conversion from binary structure to string output. I've got a T4.0 with an SD socket and I'm going to try the same thing with SDFat 2.0B using a pre-allocated EXFAT file.

Delays like those in the > 500mSec range are the kind of thing that reinforce the warning: "SD Cards can have long and variable write delays!" Luckily, a T3.6 has enough RAM to buffer over a second of data even at high collection rates. If I extend the data structure to be 4 x 32Bit values, logging at 10KHz will require that each buffer be 160KBytes in length. In that case the two buffers are going to use about 125% of the T3.6 RAM! Not Good. Looks like that is a job for a T4.1!

Logging at that high rate with the standard SD library gets problematic. Even worse, you end up with a LOT of data to sift through and text in the Serial monitor is not the way to handle that. For high-rate, long-term logging I usually save the more efficient binary records and add a file upload function so that I can analyze with MatLab. The problem with that approach for example code is that you need a PC Host program that knows how to talk to the Teensy and receive the upload data. Requiring the distribution of a PC executable file with the Teensy example code adds a whole new can of worms. On the plus side is the fact that the T4.0 can upload binary files at ~22MBytes/second. The T3.6 maxes out the USB link at about 1.08MBytes/second. The other alternative is to pull the SD Card and use a MatLab function to read the binary data on a PC. Since that requires MatLab, or some other program, to read the binary data, it is also awkward for demo code.

The large buffers needed for high-rate collection with the SD library may be a good argument for using a queue rather than ping-pong buffers. In many cases, one queue of 160KB can do the same job as two ping-pong buffers of 160KB each. The problem with queues is that the code gets more complex if you want to optimize for multi-sector writes as can be done with SDFat 2.0B. (The extra code is needed to handle the wrap around at the end of the queue buffer.)

Today's project will be to extend the example code to use SDFat 2.0B and EXFAT files. I'm going to focus more on the SD write delays and eliminate the ADC inputs, while saving just timing data. The playback function will spit out just the write delay after a pause to allow switching to the graphing mode of the serial monitor.
 
Those delay times on SD write numbers are typical - and ugly - not sure if they are any better with the way SDFat 2.0B does I/O - but generally SDFat writes are indeed much faster. Will look for SDFat version here - saves me from doing it to see.

I was considering adding a data element to record the prior 'SD write time' in the logged data so it could be tracked/plotted offline.

There are plans to get USB file access to 'Teensy Media' - was just mentioned on the T_4.1 Beta thread. Also USBHost function allows write to Media { FLASH, HDD } ...
 
I've included an updated version of the timing logger. This one has a single #define USE_EXFAT
and some #ifdefs sprinkled through the code where there are differences between SD file access and
EXFat file access with SDFat 2.0b. To test EXFat, you need to start with a card formatted for that file
system.

In general, the EXFat file writes are faster---on the order of 0.88mSec on a T3.6 when writing to a pre-allocated
contiguous file. This makes sense since there are no writes to the directory sector until the file is closed and there
are no reads and writes to the FAT, since all the FAT clusters are marked as used during the pre-allocation process.

However, if you use the sync() function to put the SD card into low-power mode, write times go up to about 5mSec
since the file system updates the directory block and that has to wait until the background SD card write of the data
block is finished.

I also tried this program on a T4.0 with an attached uSD card socket. Times were only about 20% shorter, indicating
that most of the time is taken up in the SDIO hardware and SD card. I haven't yet experimented with different SD
cards. The most recent tests with EXFat were done with a 16GB Samsun EVO card.

Code:
/**************************************************************
  Simple SD card timing datalogger for Teensy 3.6

  This example shows how to log timing data to an SD card using the SD library
  and SDFat 2.0b  
  As much as possible, the code uses the simplest Arduino functions, adding 
  teensy-specific code only where necessary (such as IntervalTimer). 
 
  The circuit:

   SD card attached to built-in SD Card slot on t3.6


  This example uses  buffering and interval timer to collect at regular
  intervals without collection timing jitter caused by SDC writes.
  A time stamp is saved with each record.

  A minimal user interface is added with three one-letter commands:
      'r'   Open file and start recording data with verbose output
      'g'   Open file and start recording data with only block write delay output
      'q'   Quit logging data and close file
      'p'   Play back data in CSV format


  NOTES:
    1. Unlike the Simple Logger, this version saves only the binary data.
       Conversion to strings is done on playback.
    2. Playback is done one record at a time.  This is less efficient,
       but output is going to the Serial port, so speed isn't as much
       of an issue.
    3. if you uncomment #define USE_EXFAT the program will use the EXFat
       file system of SDFat 2.0beta.  You will need an SD Card formatted
       for the EXFat file system.  That can be done with the tools
       in the SDFat examples




  Written for Teensy 3.6 by M. Borgerson  May 7, 2020
********************************************************************/

// SPI library not needed with Teensy built-in SD card

//#define USE_EXFAT

#ifdef USE_EXFAT

#include "SdFat.h"
#include "sdios.h"
#include "ExFatlib\ExFatLib.h"
SdExFat sd;
SdioCard sdc;
ExFile dataFile;
#define SD_CONFIG SdioConfig(FIFO_SDIO)

#else

#include <SD.h>
File dataFile;

#endif

IntervalTimer ADCTimer;


#define SAMPLERATE 1000
const char *logfilename = "TIMELOG.DAT";

// A simple structure to hold time stamp and three analog values
// The structure takes up 16 bytes to align millitime on 4-byte boundary
struct datrec {
  uint32_t millitime;
  uint32_t microtime;
  uint32_t DWTCount;
  uint32_t byteswritten;
};
// Allocate two buffers, each holding over 1 second of data.
// For efficiency, the total length should be a multiple of 512.
// Note that the T3.6 will run out of RAM at about 8000Hz sampling
// as each buffer would have to be 8000 * 16 or 128,000 bytes, leaving
// very little room for the stack, heap, and USB buffers.
#define BUFFSIZE  1024
// allocate two buffers, each of which holds 1024 records
// each buffer is 1024 * 16 or 16KBytes
struct datrec dbuff0[BUFFSIZE];
struct datrec dbuff1[BUFFSIZE];



#define DEBUGPRINT true


// when graphout is true, during recording, only the block write
// interval is displayed.  This allows graphing with the Arduino
// Serial Plotter display.
bool graphout = false;

// these variables are declared volatile because they are changed or used in the
// interrupt handler
volatile uint16_t saveidx = 0;
volatile int16_t writebuffnum = -1;
volatile uint16_t savebuffnum = 0;
volatile uint32_t filestartmilli = 0;
volatile uint32_t byteswritten = 0;

#ifndef USE_EXFAT
const int chipSelect = BUILTIN_SDCARD;
#endif
const int ledpin = 13;

#define LEDON  digitalWriteFast(ledpin, HIGH);
#define LEDOFF digitalWriteFast(ledpin, LOW);

const char compileTime [] = "Timing Data Logger  Compiled on " __DATE__ " " __TIME__;
void setup() {
  // activate ARM cycle counter
  ARM_DEMCR |= ARM_DEMCR_TRCENA; // Assure Cycle Counter active
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  pinMode(ledpin, OUTPUT);
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println(compileTime);
  // see if the card is present and can be initialized:
  Serial.print("Initializing SD card...");
#ifdef USE_EXFAT
  StartEXFat();
#else
  StartSD();
#endif

  Serial.println("Enter command selected from (r, g, q, p)");
}


#ifdef USE_EXFAT
void StartEXFat(void) {
  if (!sd.cardBegin(SD_CONFIG)) {
    Serial.print("cardBegin failed");
  }
  if (!sd.volumeBegin()) {
    Serial.print("  volumeBegin failed");
  }
  if (!sd.begin(SdioConfig(FIFO_SDIO))) {
    Serial.println("\nSD File  system initialization failed.\n");
    FastBlink();
  } else  Serial.print("EXFat initialization done.");

  if (sd.fatType() == FAT_TYPE_EXFAT) {
    Serial.println("  Card Type is exFAT");
  } else {
    FastBlink();
  }
  Serial.println("card initialized.");
}

#else
void StartSD(void) {
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, not FAT32, or not present");
    // don't do anything more except fast blink LED
    FastBlink();
  }
  Serial.println("card initialized.");
}
#endif

void FastBlink(void) {  // blink forever
  while (1) {
    LEDON
    delay(50);
    LEDOFF
    delay(50);
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  char ch;
  uint32_t microselapsed;
  elapsedMicros wrtelapsed = 0;

  if (Serial.available()) {
    ch = Serial.read();
    if (ch == 'r') {
      graphout = false;
      StartLogging();
    }
    if (ch == 'g') {
      graphout = true;
      StartLogging();
    }
    if (ch == 'q') StopLogging();
    if (ch == 'p') PlaybackLog();
  }
  // Now check to see if a buffer is ready to be written to SD
  // writebuffnum will be set in the interrupt handler
  if (writebuffnum == 0) { //is dbuff0 ready?
    LEDON
    writebuffnum = -1;
    if (dataFile) { //if the file is open write dbuff0
      wrtelapsed = 0;
      WriteBinary(&dbuff0[0], BUFFSIZE);
      microselapsed = wrtelapsed;
      if (DEBUGPRINT) {
        if (graphout) {
          Serial.println((float)microselapsed / 1000.0);
        } else {
          Serial.print("Writing dbuff0 to data file.  tmilli = ");
          Serial.printf(" %lu  took %6.2f mSec\n", dbuff0[0].millitime, (float)microselapsed / 1000.0);
        }
      }
    }
    LEDOFF
  }
  if (writebuffnum == 1) { // is dbuff1 ready?
    LEDON
    writebuffnum = -1;
    if (dataFile) { //if the file is open write dbuff0
      wrtelapsed = 0;
      WriteBinary(&dbuff1[0], BUFFSIZE);
      microselapsed = wrtelapsed;
      if (DEBUGPRINT) {
        if (graphout) {
          Serial.println((float)microselapsed / 1000.0);
        } else {
          Serial.print("Writing dbuff1 to data file.  tmilli = ");
          Serial.printf(" %lu  took %6.2f mSec\n", dbuff1[0].millitime, (float)microselapsed / 1000.0);
        }
      }
    }
    LEDOFF
  }
  delay(5);
}  // end of the loop() function

// write a buffer to the file as binary record. This  takes more time and file space than
// binary format, but can be directly read on PC.
// Note that we have to cast datrec pointer to a uint8_t pointer
// to keep the SD libraryhappy
void WriteBinary(volatile struct datrec *drp, size_t numstructs) {
  dataFile.write((uint8_t *)drp, numstructs * sizeof(datrec));
  
#ifdef USE_EXFAT
 // sync() updates directory and puts SD card in low-power idle mode
 // it increases the block write time to 6-10mSec, since the file system
 // must wait for the long block write to finish before it can update the
 // directory and put the SD card into idle mode
 // Without the sync, the block write returns in 0.88mSec on T3.6 at 168MHz
 // because all the multi-block write stuff happens in interrupt routines in 
 // the SDFat driver.
 // Sync() will reduce power consumption by about 20 to 40mA in the ~1 second
 // between block writes. That can be significant for low-power loggers.
 
//    dataFile.sync(); // uncomment for slower writes, but reduced SD power
#endif
}

// This is the interrupt handler for the interval timer.
// It saves the data in the data buffer structures.  The output is converted
// to CSV text when it is saved to the output file in the main loop.
// We don't want to do the conversion to a string in the interrupt handler
// since it takes a lot more time and many of the string routines are not
// reentrant. (Programmer talk for "They don't play nicely when interrupted")
void ADCChore(void) {
  uint32_t tmilli;
  tmilli = millis() - filestartmilli;
  byteswritten += sizeof(datrec); // update global bytes written count
  // save in  the proper buffer--defined by savebuffnum
  if (savebuffnum == 0) { // put data in dbuff0
    dbuff0[saveidx].millitime = tmilli;
    dbuff0[saveidx].microtime =  micros();
    dbuff0[saveidx].DWTCount = ARM_DWT_CYCCNT;
    dbuff0[saveidx].byteswritten = byteswritten;
    saveidx++;
    if (saveidx >= BUFFSIZE) { // mark buffer for write to  SD
      writebuffnum = 0;
      savebuffnum = 1;   // start saving in other buffer on next interrupt
      saveidx = 0;  // start at beginning of next buffer
    }
  } else {  // must be saving to dbuff1
    dbuff1[saveidx].millitime = tmilli;
    dbuff1[saveidx].microtime =  micros();
    dbuff1[saveidx].DWTCount = ARM_DWT_CYCCNT;
    dbuff1[saveidx].byteswritten = byteswritten;
    saveidx++;
    if (saveidx >= BUFFSIZE) { // mark buffer for write to  SD
      writebuffnum = 1;
      savebuffnum = 0;   // start saving in other buffer on next interrupt
      saveidx = 0;   // start at beginning of next buffer
    }
  }
}

void StartLogging(void) {
  if (dataFile) {
    Serial.println("Already collecting!");
    return;
  }
  // we open in a mode that creates a new file each time
  // instead of appending as in the Arduino example
#ifdef USE_EXFAT

  if (!dataFile.open(logfilename, O_WRITE | O_CREAT | O_TRUNC)) {
    Serial1.println("Open File failed");
    return;
  }
  // pre-allocate 1GB
  uint64_t alloclength = (uint64_t)(1024l * 1024l * 1024l);
  Serial1.print("Pre-allocating 1GB file space ");
  if (!dataFile.preAllocate(alloclength)) {
    Serial.println("Allocation failed. Proceeding anyway.");
  } else {
    Serial.println("Allocation succeeded.");
  }

#else

  if (SD.exists(logfilename)) SD.remove(logfilename);
  dataFile = SD.open(logfilename, FILE_WRITE);

#endif
  // if the file can't be opened, say so
  if (!dataFile) {
    Serial.println("Could not open output file!");
    return;
  }

  Serial.println("Starting logging");
  // initialize some variables for the buffers
  saveidx = 0;   // start saving at beginning of buffer
  savebuffnum = 0;  // start saving in dbuff0
  writebuffnum = -1;  // indicates no buffer ready yet
  //  start the interval timer to begin logging
  filestartmilli = millis();
  byteswritten = 0;
  ADCTimer.begin(ADCChore, 1000000 / SAMPLERATE); //begin() expects timer period in microseconds
}

void StopLogging(void) {
  Serial.println("Stopping logging");
  ADCTimer.end();
  if (DEBUGPRINT) Serial.println("ADCTimer halted");
  delay(10);
  if (dataFile) {
    Serial.println("Data file closed.");
#ifdef USE_EXFAT
    dataFile.truncate();  // Truncate down to data actually written
#endif
    dataFile.close();  // if file was open, close it.
  } else {
    if (DEBUGPRINT) Serial.println("dataFile was not open!");
  }
  writebuffnum = -1;
  // dont' write tottal bytes if graphing--it messes up the plot!
  if(!graphout){
    Serial.printf("%lu KBytes written to file.\n", byteswritten / 1024);
  }
  // in the interest of simplicity, we ignore any partial buffer at the end
}

void PlaybackLog(void) {
  struct datrec temprec;
  if (dataFile) StopLogging(); // if still recording, stop and close file
#ifdef USE_EXFAT
  if (!dataFile.open(logfilename, O_READ)) {
    Serial1.println("Open File failed");
    return;
  }
#else
  dataFile = SD.open(logfilename, FILE_READ);
#endif
  if (!dataFile) {
    Serial.println("Could not open data file for reading.");
    return;
  }
  while (dataFile.available()) {
    dataFile.read(&temprec, sizeof(datrec));
    Serial.printf("%lu, %lu, %lu\n", temprec.millitime, temprec.microtime, temprec.DWTCount);
  }
  dataFile.close();
  Serial.println("\nPlayback complete.");
}

Here's ascreen capture showing why it's a good idea to have at least 1 second of buffer space. Times on the Y axis are milliseconds to complete a block write of 16KBytes on a T3.6 with a FAT32 SD Card using the Arduino SD library.

timingCapture.JPG
 
Great work - thanks for posting - look to try it soon. I've seen other fast data logger sketches posted or around but never bothered to look - will give this a try.

It shows why a big enough buffer is needed for sure as they generally write FAST - but not always. Also shows the import of having an _isr() doing the data logging so as to not miss timing intervals.

I ran prior edited code overnight at a slower log rate {#define SAMPLERATE 10} and stopped it and printed and all seems well and uncorrupted for proper writes from both buffers as presented.

Will have to try it on pending Teensy 4.x with logging to 8MB PSRAM and see if SD writes work at the same time without crashing on colliding QSPI access during SD write when _isr() triggers new log entries. Maybe even onto the 16MB added Flash chip could work - if I can get the code right for that.

<EDIT> Copied code COMPILED! and RUNS! - this SD card was FAT32.

Changed to exFAT and much faster now - on native T_4.1 SD - 13.16 is the slowest time so far:
Code:
T:\tCode\sd\SDfat_Logger\SDfat_Logger.ino May  7 2020 13:36:18
Timing Data Logger  Compiled on May  7 2020 13:36:18
Initializing SD card...EXFat initialization done.  Card Type is exFAT
card initialized.
Enter command selected from (r, g, q, p)
Allocation succeeded.
Starting logging
Writing dbuff0 to data file.  tmilli =  1  took   1.20 mSec
Writing dbuff1 to data file.  tmilli =  1025  took   0.72 mSec
Writing dbuff0 to data file.  tmilli =  2049  took   0.72 mSec
Writing dbuff1 to data file.  tmilli =  3073  took   0.72 mSec
Writing dbuff0 to data file.  tmilli =  4097  took   1.12 mSec
Writing dbuff1 to data file.  tmilli =  5121  took   2.75 mSec
…
Writing dbuff1 to data file.  tmilli =  56321  took   1.12 mSec
Writing dbuff0 to data file.  tmilli =  57345  took  [B][U]13.16 mSec[/U][/B]
Writing dbuff1 to data file.  tmilli =  58369  took   1.24 mSec
…
Writing dbuff1 to data file.  tmilli =  136193  took   0.72 mSec
Writing dbuff0 to data file.  tmilli =  137217  took   1.83 mSec
Writing dbuff1 to data file.  tmilli =  138241  took   1.23 mSec
Writing dbuff0 to data file.  tmilli =  139265  took   0.72 mSec
Writing dbuff1 to data file.  tmilli =  140289  took   0.72 mSec
Writing dbuff0 to data file.  tmilli =  141313  took   0.72 mSec
Writing dbuff1 to data file.  tmilli =  142337  took  [B][U]13.07 mSec[/U][/B]
Writing dbuff0 to data file.  tmilli =  143361  took   1.31 mSec
Writing dbuff1 to data file.  tmilli =  144385  took   0.72 mSec
Writing dbuff0 to data file.  tmilli =  145409  took   0.72 mSec
Writing dbuff1 to data file.  tmilli =  146433  took   0.72 mSec
Writing dbuff0 to data file.  tmilli =  147457  took   1.18 mSec
Writing dbuff1 to data file.  tmilli =  148481  took   2.07 mSec
…
Writing dbuff1 to data file.  tmilli =  228353  took  12.58 mSec
Writing dbuff0 to data file.  tmilli =  229377  took   1.24 mSec
Writing dbuff1 to data file.  tmilli =  230401  took   0.72 mSec
Writing dbuff0 to data file.  tmilli =  231425  took   0.72 mSec

Only 3 double digit times in 5 minutes.

Some follow up on T_4.1 Beta thread :: pjrc.com/threads/60532-Teensy-4-1-Beta-Test
20K samples/sec with twin 64 KB buffer to exFAT:
Code:
Writing dbuff0 to data file.  tmilli =  691814  took   2.88 mSec
Writing dbuff1 to data file.  tmilli =  692019  took   2.88 mSec
Writing dbuff0 to data file.  tmilli =  692224  took   2.88 mSec
Writing dbuff1 to data file.  tmilli =  692429  took   2.88 mSec
Writing dbuff0 to data file.  tmilli =  692633  took   2.88 mSec
Writing dbuff1 to data file.  tmilli =  692838  took   2.88 mSec
Writing dbuff0 to data file.  tmilli =  693043  took   2.88 mSec
Writing dbuff1 to data file.  tmilli =  693248  took   2.88 mSec
Writing dbuff0 to data file.  tmilli =  693453  took   2.88 mSec
Stopping logging
ADCTimer halted
Data file closed.
216780 KBytes written to file.

<edit> This above code also working on T_4.0 on a FRDM 4236 breakout to the same exFAT SD card.
 
Last edited:
Thanks a bunch for this sample code. I used your first example from 05-06-2020, 08:42 PM as it was easier for me to understand. I have one TeensyLC device, but have not worked my way up to it yet, so I ported this onto an Atmega326P. But not after making a few modifications. For example I'm using Timer-1 as my interrupt handler since the 328 doesn't understand the ADCTimer function. Your code works great!

But I have a question: your data structure has 12 bytes and the variable BUFFSIZE is assigned to 128. Wouldn't that make a 1528 byte buffer when the previously discussed SD card page-write limit is 512 bytes at a time? The index variable "saveidx" is indexed one at a time until it exceeds BUFFSIZE. At this point I had to make my buffers much smaller due to the limits of the Amtega chip. But soon, I will grow onto the Teensy perhaps out of necessity.

Aside: I'm playing with datalogging of home water pressure wave forms. Professor Patel from UW describes how one sensor can discriminate amongst individual valves in a home. Each valve has its own fingerprint. Once you identify the valve, you can estimate the water consumption. Alternatively, the water pressure drop itself is an indication but the relationship is not linear.
 
@ mborgerson: Again, thanks for the code example.

Wouldn't that make a 1528 byte buffer ... ?

My mistake: strike 1528 and replace with 1536.

For others following this thread: I also found that writes to the SD card caused a voltage sag on connected circutry despite using a large capacitor. This caused a dent in my ADC readings. To solve this I did two things: 1. The SD Card reader was given a dedicated power supply. 2. Each ADC reading was adjusted by by a modified VCC based on AREF. Andreas Spiess shows how to do this in his video #10.

But now I've got silky smooth ADC readings indicating that the sensors have very little noise.
 
I tried this code. This is really good. I have a question if anyone would like to answer; Could I go to StopLogging(void) using pre-defined time instead of using q or p?
 
I tried this code. This is really good. I have a question if anyone would like to answer; Could I go to StopLogging(void) using pre-defined time instead of using q or p?

It should be straightforward to log for a fixed amount of time by using an elapsedMillis timer which is reset when starting logging. In the loop you would add code to check the elapsedMillis timer and call StopLogging() when the timer reaches your pre-determined value.

If you want to stop logging at a particular time some time (Hours to weeks to months) in the future, you will have to use the functions in timeLib to calculate the desired end time and check for that time in the loop() function. This will be significantly more complex, but still shouldn't take more than 20-30 lines of code.

A trick I've often used to start a new file at regular intervals is to check the unix time modulus the file interval---something like this

Code:
uint32_t fileInterval = 6 * 3600;   // New File every 6 hours

in loop:

  if((now() % fileInterval) == 0)  StartNewFile();

Some logic tests are required to make sure that you don't try to start a dozen new files during the second in which the modulus value is zero.
 
Thank you. I tried this but every time I recorded samples, The number of samples is different. Is there a way I can get a constant number of samples for a specific time duration like 10 seconds?
 
Thank you. I tried this but every time I recorded samples, The number of samples is different. Is there a way I can get a constant number of samples for a specific time duration like 10 seconds?
The most straightforward way to do this is:
Code:
uint32_t totalSamples = secondsToSample * SAMPLERATE;   // pre-calculate number of samples needed based on duration of sampling

volatile void samplesCollected = totalSamples;

in the ADC interrupt handler you add:

    if(samplesCollected >= totalSamples) return;    // no more collection when all samples taken

Then in your loop function, you monitor samplesCollected and finish up the collection when samplesCollected == totalSamples;

To start collect, you add this to the StartCollection function:

samplesCollected = 0; // starts collection at next interrupt
 
I tried this.
#define SAMPLERATE 1024*1
uint32_t totalSamples = 10 * SAMPLERATE;
#define BUFFSIZE 1024

when SAMPLERATE is 1024 and I wrote for 10s, it actually took 10s to write 1024 samples. That's great.! Keeping the SAMPLERATE 1024 and increasing the time to 20s, to write 1024*20 samples took 20s, it's also good.
but
when I increased SAMPLERATE to 1024*2 and kept time 10s, it actually took 40s to write 1024*2*10.
similarly, when I increased SAMPLERATE to 1024*3 and kept time 10s, it actually took 90s to write 1024*2*10.

What's wrong with changing SAMPLERATE?
 
I tried this.
#define SAMPLERATE 1024*1
uint32_t totalSamples = 10 * SAMPLERATE;
#define BUFFSIZE 1024

when SAMPLERATE is 1024 and I wrote for 10s, it actually took 10s to write 1024 samples. That's great.! Keeping the SAMPLERATE 1024 and increasing the time to 20s, to write 1024*20 samples took 20s, it's also good.
but
when I increased SAMPLERATE to 1024*2 and kept time 10s, it actually took 40s to write 1024*2*10.
similarly, when I increased SAMPLERATE to 1024*3 and kept time 10s, it actually took 90s to write 1024*2*10.

What's wrong with changing SAMPLERATE?

I'll need to see the code to give you any kind of definitive answer to that. My first GUESS is that doubling or tripling the sample rate caused some buffer overflows and you may need larger buffers. My usual rule-of-thumb for writing to SD cards is that you need at least one second's worth of buffer space to avoid overflows when you get extended SD write time when the card has to erase a new block of sectors. The original code sampled at 1000 samples per second and had buffer space for 1024 samples. You've doubled or tripled the sample rate, but you haven't mentioned increasing the buffer size accordingly.
 
Code:
#include <Wire.h>
#include <SD.h>
#include <SPI.h>

#include "LIS2DH12.h" //the library
LIS2DH12 accel;       //Create instance
File myFile; //File name to be saved
IntervalTimer myTimer;
#define Fs 1024
uint32_t totalSamples = 10*Fs + 50; // no. of samples
long int  samplesCollected = 0;

char filename[26]="";
const int chipSelect = BUILTIN_SDCARD; // Builtin function for SD card

#define DEBUGPRINT true
volatile uint16_t saveidx = 0;
volatile int16_t writebuffnum = -1;
volatile uint16_t savebuffnum = 0;
volatile uint32_t filestartmilli = 0;

struct dataRec {
  uint32_t ltime;
  float x, y, z;
  uint16_t spare;
};
#define BUFFSIZE  Fs
// allocate two buffers, each of which holds Fs records
struct dataRec dBuff0[BUFFSIZE];
struct dataRec dBuff1[BUFFSIZE];

const int ledpin = 13;
#define LEDON  digitalWriteFast(ledpin, HIGH);
#define LEDOFF digitalWriteFast(ledpin, LOW);

const char compileTime [] = "Simple Data Logger  Compiled on " __DATE__ " " __TIME__;

void setup()
{
  pinMode(ledpin, OUTPUT);
  Serial.begin(115200);
  while (!Serial) {
    ; // wait for serial port to connect.
  }
  Serial.println(compileTime);

  Wire.begin();
  Wire.setClock(1000000);  // For MaxOutput
  if (accel.begin() == false)
  {
    Serial.println("Accelerometer not detected. Check address jumper and wiring. Freezing...");
    while (1)
      ;
  }

 /////////////////////////////
 // Select g Scale, // no. of bits, // and frequency
  //accel.setScale(LIS2DH12_2g);
  //accel.setScale(LIS2DH12_4g);
  //accel.setScale(LIS2DH12_8g);
  accel.setScale(LIS2DH12_16g);
  int currentScale = accel.getScale();
  Serial.print("Current scale (0 to 3 = 2g, 4g, 8g, 16g): ");
  Serial.println(currentScale);

  //accel.setMode(LIS2DH12_LP_8bit);
  //accel.setMode(LIS2DH12_NM_10bit);
  accel.setMode(LIS2DH12_HR_12bit);
  int currentMode = accel.getMode();
  Serial.print("Current mode (0 to 2 = 8bits, 10bits, 12bits): ");
  Serial.println(currentMode);

  //Sensor output rate ranges from 1 to 5.376kHz
  //The lower the output rate the lower the power consumption
  //accel.setDataRate(LIS2DH12_POWER_DOWN);
  //accel.setDataRate(LIS2DH12_ODR_1Hz);
  //accel.setDataRate(LIS2DH12_ODR_10Hz);
  //accel.setDataRate(LIS2DH12_ODR_25Hz);
  //accel.setDataRate(LIS2DH12_ODR_50Hz);
  //accel.setDataRate(LIS2DH12_ODR_100Hz);
  //accel.setDataRate(LIS2DH12_ODR_200Hz);
  //accel.setDataRate(LIS2DH12_ODR_400Hz);
  accel.setDataRate(LIS2DH12_ODR_1kHz620_LP);
  //accel.setDataRate(LIS2DH12_ODR_5kHz376_LP_1kHz344_NM_HP);
  int currentRate = accel.getDataRate();
  Serial.print("Current rate (0 to 9 = Powe_Down, 1Hz, 10Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz, 1kHz, 5kHz): ");
  Serial.println(currentRate);

//  accel.disableTemperature();
/////////////////////////////////////////
  
    Serial.print("Initializing SD card...");

// Make the file name with timestamp and extension
  char fname[] = "DL";
  char tmstmp[] = "1G8G5A60";
//  char tmstmp[21];
//  sprintf(tmstmp,"%4d%02d%02d",year(),month(),day());    // not working (dynamic name of the .csv file)
  char fext[] = ".csv";
  strcat(filename, fname);
  strcat(filename, &tmstmp[2]);
  strcat(filename, fext);
  
  Serial.println(filename);

  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  Serial.println("Enter command.! (r, q, p) for (RequestLogging, QuitLogging, PlaybackLog");
}

long measurements = 0;  // For MaxOutput
///////////////////////////////////////////////////
void loop()
{
    char ch;
  if (Serial.available()) {
    ch = Serial.read();
    if (ch == 'r') RequestLogging();
    if (ch == 'q') QuitLogging();
    if (ch == 'p') PlaybackLog();
  }
    if (samplesCollected == totalSamples){
    PlaybackLog();
  }
    if (writebuffnum == 0) { //is dbuff0 ready?
    LEDON
    writebuffnum = -1;
    if (myFile) { //if the file is open write dbuff0
      WriteCSV(&dBuff0[0], BUFFSIZE);
      if (DEBUGPRINT) {
        Serial.print("Writing dBuff0 to data file.  tmilli = ");
        Serial.printf(" %lu\n", dBuff0[0].ltime);
      }
    }
    LEDOFF
  }
  if (writebuffnum == 1) { // is dbuff1 ready?
    LEDON
    writebuffnum = -1;
    if (myFile) { //if the file is open write dbuff0
      WriteCSV(&dBuff1[0], BUFFSIZE);
      if (DEBUGPRINT) {
        Serial.print("Writing dBuff1 to data file.  tmilli = ");
        Serial.printf(" %lu \n", dBuff1[0].ltime);
      }
    }
    LEDOFF
  }
  delay(5);
}
////////////////////////////////
void WriteCSV(volatile struct dataRec *drp, size_t numlines){
uint16_t i;  
char dataString[100];
  for(i= 0; i< numlines; i++){
    sprintf(dataString,"%5u, %2f, %2f, %2f\n", drp->ltime,drp->x,drp->y, drp->z);
    myFile.print(dataString);
    drp++;  // advance pointer to next record in buffer
  }
}
///////////////////////////////
void fillBuffer(void) {
   if(samplesCollected >= totalSamples) return; // no more collection when all samples taken

  uint32_t tmilli;
  tmilli = millis() - filestartmilli;
  // save in  the proper buffer--defined by savebuffnum

  if (accel.available()) {
    if (savebuffnum == 0) { // put data in dbuff0
    dBuff0[saveidx].ltime = tmilli;
    dBuff0[saveidx].x = accel.getX();
    dBuff0[saveidx].y = accel.getY();
    dBuff0[saveidx].z = accel.getZ();
    samplesCollected++;
    measurements++;
    saveidx++;

    if (measurements % Fs == 0) {
      int hertz = measurements * Fs / millis();
      Serial.print("measurements: ");
      Serial.print(measurements);
      Serial.print(" time: ");
      Serial.print(millis());
      Serial.print(" Measurement rate: ");
      Serial.print(hertz);
      Serial.print("Hz");
      Serial.println();
    }
    if (saveidx >= BUFFSIZE) {  // mark buffer for write to  SD
      writebuffnum = 0;
      savebuffnum = 1;   // start saving in other buffer on next interrupt
      saveidx = 0;  // start at beginning of next buffer
    }
    }
    else {  // must be saving to dbuff1
    dBuff1[saveidx].ltime = tmilli;
////////////////////////////////////////////////
    // float to buffer storage ?
    
    //  float accelX = accel.getX();
    //  float accelY = accel.getY();
    //  float accelZ = accel.getZ();

    // char dtostrfbuffer1[2];
    // char dtostrfbuffer2[2];
    // char dtostrfbuffer3[2];

    //dtostrf(accelX, 1, 0, dtostrfbuffer1);
    //dtostrf(accelY, 1, 0, dtostrfbuffer2);
    //dtostrf(accelZ, 1, 0, dtostrfbuffer3);

 //   dBuff1[saveidx].x = dtostrfbuffer1;
 //   dBuff1[saveidx].y = dtostrfbuffer2;
 //   dBuff1[saveidx].z = dtostrfbuffer3;
///////////////////////////////////////////////
    // just uncomment to go previous mode
    dBuff1[saveidx].x = accel.getX();
    dBuff1[saveidx].y = accel.getY();
    dBuff1[saveidx].z = accel.getZ();
 
////////////////////////////////////////////////
//This was used just to compare the values
    //Serial.println(accelX);
    //Serial.println(dtostrfbuffer1);
    //Serial.println(dBuff1[saveidx].x);
    
    //Serial.println(accelY);
    //Serial.println(dtostrfbuffer2);
    //Serial.println(dBuff1[saveidx].y);
        
    //Serial.println(accelZ);
    //Serial.println(dtostrfbuffer3);
    //Serial.println(dBuff1[saveidx].z);
////////////////////////////////////////////////
    
    samplesCollected++;
    measurements++;
    saveidx++;
    if (measurements % Fs == 0) {
      int hertz = measurements * Fs / millis();
      Serial.print("measurements: ");
      Serial.print(measurements);
      Serial.print(" time: ");
      Serial.print(millis());
      Serial.print(" Measurement rate: ");
      Serial.print(hertz);
      Serial.print("Hz");
      Serial.println();
    }
    if (saveidx >= BUFFSIZE) { // mark buffer for write to  SD
      writebuffnum = 1;
      savebuffnum = 0;   // start saving in other buffer on next interrupt
      saveidx = 0;   // start at beginning of next buffer  
    }
    }
  }
}
//////////////////////////////////////////////////////////
void RequestLogging(void) {
  if (myFile) {
    Serial.println("Already collecting!");
    return;
  }
  // we open in a mode that creates a new file each time
  // instead of appending as in the Arduino example
  if(SD.exists(filename)) SD.remove(filename);
  myFile = SD.open(filename, FILE_WRITE);
  // if the file can't be opened, say so
  if (!myFile) {
    Serial.println("Could not open output file!");
    return;
  }
  Serial.println("Requested ... Starting Logging");
  myFile.println("  TIME,   X,    Y,    Z");  // put header line in file
  // initialize some variables for the buffers
  saveidx = 0;   // start saving at beginning of buffer
  savebuffnum = 0;  // start saving in dbuff0
  writebuffnum = -1;  // indicates no buffer ready yet
  //  start the interval timer to begin logging
  filestartmilli = millis();
  
  myTimer.begin(fillBuffer, 1e6/Fs); //begin() expects timer period in microseconds
}
////////////////////////////////////////////////////////
void QuitLogging(void) {
  Serial.println("Quit ... Stopping Logging");
  myTimer.end();
  if (DEBUGPRINT) Serial.println("myTimer halted");
  delay(10);
  if (myFile) {
    Serial.println("Data file closed.");
    myFile.close();  // if file was open, close it.
  } else {
    if (DEBUGPRINT) Serial.println("myFile was not open!");
  }
  writebuffnum = -1;
  // in the interest of simplicity, we ignore any partial buffer at the end
}
//////////////////////////////////////////////////////////
void PlaybackLog(void) {  
  String datline;
 
  if(myFile) QuitLogging(); // if still recording, stop and close file
  myFile = SD.open(filename, FILE_READ);
  if(!myFile){
    Serial.println("Could not open data file for reading.");
    return;
  }
  while (myFile.available()) {
    datline = myFile.readStringUntil('\n');
    Serial.println(datline); 
  }
  myFile.close();
  Serial.println("\nPlayback complete.");
}

This is the code, I am using to get my data from the accelerometer. Does this code give me constant sampling? When I ploted the frequency spectrum, I found that my plot is harmonic frequency graph.
 
Status
Not open for further replies.
Back
Top