Minimalistic SdFat Datalogger for Teensy4.1 - Example?

Status
Not open for further replies.

ThomasD

Member
Hello everybody,

for my Master's Thesis, I am currently trying to implement a system which needs to log the data of five analog sensors with relatively high frequency (100-1000Hz logging frequency is desired).

For this project, I took a very big step out of my comfort zone going from exclusively using arduinos and building sketches with a handful of functions in the arduino IDE to using a Teensy 4.1 using the Platformio IDE and using custom includes etc. to implement the needed functionality. My understanding of programming and embedded systems is extremely limited, so please bear with me if I sound like a numb nut at times.

From my research up until this point, I determined that due to speed limitations, the regular SD.h library is not suitable for the job and that SdFat.h is a better choice, eventhough many people seemed to have issued using the T4.1 with SdFat.

I successfully implemented a couple of example projects in Platformio using the SdFat.h library and ran the ExFatFormatter sketch, the bench sketch and the TeensySdioDemo sketch successfully. I did not make any modifications to the source code in any of the example sketches. I am using the build-in SD slot of the T4.1 and a 16gig SanDisk SDHC card. The results of the tests were as follows:

_____________________________________

Benchmark Sketch
Manufacturer ID: 0X3
OEM ID: SD
Product: SS16G
Version: 8.0
Serial number: 0XAB268B1D
Manufacturing date: 8/2013

FILE_SIZE_MB = 5
BUF_SIZE = 512 bytes
Starting write test, please wait.

write speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
21276.60,5566,22,23
21834.06,5566,22,23

Starting read test, please wait.

read speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
22727.27,23,22,22
22624.43,23,22,22

Done
Type any character to start

TeensySdioDemo
Type '1' for FIFO SDIO
'2' for DMA SDIO
'3' for Dedicated SPI
'4' for Shared SPI

FIFO SDIO mode.

size,write,read
bytes,KB/sec,KB/sec
512,16815.76,22367.59
1024,11265.47,13363.32
2048,7524.61,22446.12
4096,20170.98,22544.69
8192,13861.48,22491.74
16384,20215.95,22569.13
32768,19819.98,22565.55

totalMicros 7082517
yieldMicros 254330
yieldCalls 265
yieldMaxUsec 6060
Done

_____________________________________

Now, these numbers don't mean too much to me, I was primarily happy that the SD card was recognized and the reading and writing works. If anyone can see an issue with the results, I would be grateful to hear about it.

My question now is: Can somebody point me to an example of a simple data logger using the SdFat library that I can use for my give application? So far, I've been using the SD.h library and always used the most basic workflow of begin(), open(), write() and close(). The data logger examples in the sdfat library are overwhelmingly complicated for me and I'm not sure how to make enough sense of what's happening there to use their functionality for my project.

The closest I came were the TeensyDdioLogger from the SdFat-beta library (I am aware of the fact that SdFat and SdFat-beta are two different libraries and can not be used at the same time).

A much simpler example of (documented in German, I'm sorry) of an Arduino Datalogger with the SdFat library is this one here.

Essentially, I'm looking for something like this, but using a Teensy4.1 instead of an arduino board. Can someone give me a hint where to find an example for that? Or tell me, how I can determine which elements of the arduino example I need to change to make it work with the T4.1?

Many thanks in advance and sorry if it is a dumb question - again, I'm way out of my comfort zone here and after a handful of days worth of googling, I decided to reach out for help because my time is running out and I need to get the system up and running.

Cheers,
Thomas
 
Last edited:
Hi Thomas,

you didn't mention the desired format of the file (binary would be best, or a human readable format like csv?),
and the desired analog resolution.
So, let's assume you get more than 8 bit from the sensors. This would mean to store 16 Bits = 2 Bytes per sample (binary format)
2 Bytes * 5 Sensors * 1000 Hz => 10000 Bytes per second Data.
10000 / 1024 = 9.7 KB/Sec

So, this looks good:

Code:
[B]TeensySdioDemo[/B]
Type '1' for FIFO SDIO
     '2' for DMA SDIO
     '3' for Dedicated SPI
     '4' for Shared SPI

FIFO SDIO mode.

size,write,read
bytes,KB/sec,KB/sec
[COLOR=#008000]512,16815.76,22367.59[/COLOR]
1024,11265.47,13363.32
2048,7524.61,22446.12
4096,20170.98,22544.69
8192,13861.48,22491.74
16384,20215.95,22569.13
32768,19819.98,22565.55

However, if you want to store in a human readable format, things are completely differnt, and you have to store way more.
"65536," are 6 characters, so 3 times more Bytes to store.
 
Last edited:
But you'll a technique to buffer the data before stoing, because SD can have a long latency.
In addition, you may want to sample the data while writing.
 
Hi Frank,

thank you very much for your response!

you didn't mention the desired format of the file (binary would be best, or a human readable format like csv?),
and the desired analog resolution.
[...]
However, if you want to store in a human readable format, things are completely differnt, and you have to store way more.
"65536," are 6 characters, so 3 times more Bytes to store.

My apologies for not including the nature of the sensor data and the storage format. Let me give you a brief description of what I have in mind:

I use three rotary potentiometers to track the position of rotating shafts. Their outputs are voltage signals between 0V and 3.3V. Additionally, I have two force sensors hooked up to an op-amp circuit which output a voltage signal between 0.5V and 3.3V respectively.

My goal is to read and store all sensor data in each iteration of the loop function. Then I planned to map the voltage valules from the sensors on meaningful physical data, positions in rad and forces in newton, using the arduino map function. This leaves me with five float variables, the position data values are between 0.000 and 3.142 (values in rad, max. approx. pi). The values of the force sensors will be between 0.00 and 40.00 (values in N).

I planned to set the Teensy4.1's ADC to a 12 bit resolution. I found this comment by Paul in another thread regarding using a 12bit resolution on the ADC:
"Yup, use analogReadResolution(12). You might also try analogReadAveraging(8) or even analogReadAveraging(32).
Also consider your analog circuitry. Low signal impedance is needed to actually achieve low enough noise to really use 12 bits."

So I'm actually no longer sure if 12bit resolution it's a smart play on my end.

I then wanted to call the millis() or micros() timer functions after the first analogRead(sensorPin) to create a precise timestamp at the moment the data is recorded.

Finally, I wanted to put the value of the timestamp in milli/microseconds, the three positions calculated from the potentiometer voltage readings and the two force values calculated from the force sensor voltage readings into a single string using the string() function. This string I then wanted to print to the logfile on the sd card and end the loop.

This may very well be a terrible concept, but it's what I did previously when needing to record data (without being concerned with logging speed at all)

I wanted to log the data as a csv file, which I then could read and interpret in MATLAB, which is where the models of my system live.


But you'll a technique to buffer the data before stoing, because SD can have a long latency.
In addition, you may want to sample the data while writing.

I'm not sure if I understand what you mean by that - buffering the data as in reading the sensor data multiple times and then writing the array(?) or recorded data to the SD card on one go? How would I best go about doing that, create an array, fill it up for about 10 loop iterations using a counter variable and then write it to the SD card once the counter hat reached it's set "compare value"?
Could you explain to me what you mean by sampling the data while writing?


Unfortunately, my confusion using SdFat spans from setting the correct parameters before the setup() function, over how to correctly initialize the card in the setup() function, all the way to how to correctly open and write to the file on the SD card. I had trouble finding a pattern across the various examples from the SdFat library, especially when it comes to setting the needed parameters before the setup() function.
I would be extremely thankful if someone (maybe you @FrankB?) can point me to the simplest way of correctly setting the parameters and initializing the card and then using the write() function correctly. Again, I'm sorry of this seems incompetent on my end, that's because I truly am in this domain...

Thanks in advance!
Thomas
 
Hi Thomas,
I can help you a bit, maybe, like trying to ask the right questions and give some hints, but I can't (and don't want to ) write your program.
You can reach me via EMAIL (in German) - klick on my name here.
 
Hi Frank,
thank you very much for your support! Of course, I am in no way expecting you to write my program for me, sorry if it seemed like I was asking for that. I just wanted to give an overview of the concept I had in mind for the program.
I will gladly reach out to you via email.
Thanks again,
Thomas
 
You best start with one of the standard examples in SD (using now SdFatV2) to understand the procedure.
consequently you adapt to your own requirements (sampling frequency, # of channels)
using 5 channels, you may need to thing about a dedicated ADC
 
You best start with one of the standard examples in SD (using now SdFatV2) to understand the procedure.
Thank you. As I said, I found this example of a Datalogger with Teensy in the SdFat-beta examples. I do not understand what's going on under the hood at all, it is way above my paygrade in terms of programming. However, if I could slightly modify this sketch to meed my needs, I'll be happy.

using 5 channels, you may need to thing about a dedicated ADC
Could you please elaborate on this? The sensor readings are the only part in my program that require an ADC.
 
Note: I have not done really any major data logging so there are many others, some who have already posted are much better sources of information, but thought I would throw out a few other things that you may want to look at, that may or may not help here.

ADC:
Again not sure what you are using to capture your analog data from the 5 channels. I am guessing you are probably starting off by using the analogRead(pin) way to start off with.
This works fine, but there are other options that can give you more flexibility. In particular you may want to look at the ADC library (https://github.com/pedvide/ADC)
Which is described in the thread: https://forum.pjrc.com/threads/25532-ADC-library-update-now-with-support-for-Teensy-3-1

Some of the interesting things you can do include: Use DMA to read analog data, likewise use a timer to control how often the read, and/or you can start up an analog read, go do something else and check to see if the conversion is complete and then get the results...

When a few of us were working with the ADC library owner on adding Teensy 4.x support, we also played around with some additional capabilities of the IMXRT ADC module, where we could setup to read more then one ADC pin, on the ADC Module. For example if all 5 of you analog pins are on one ADC module you could setup a chain of those 5, which would automatically read one after the other, and you could set up hardware timer that controlled exactly when each one would happen and again could have it setup to be done and stored to a buffer using DMA...

If you are interested in exploring this more, the chaining stuff is described in the IMXRT reference manual in chapter 67(ADC_ETC).
Note: the ADC library examples that setup DMA and/or timed reads, do use ADC_ETC and simply setup a chain of 1... So would not be difficult to then hack it, to add additional ADC pins to the chain...

SD:
I believe one of the things @Frank B was mentioning was mentioning and/or implying is that the speed of the SD system that the SD output can be a lot faster when all of your writes to it are done in specific write sizes, like in cluster sizes. Don't know if there is a difference here between writes in sector sizes versus cluster sizes?). But I believe for example if you do all of your writes lets say in multiples of 4K, you can get a lot faster performance versus a bunch of random small writes (like individual strings).

As for how much? Not sure, again I don't really do much of this. I mainly play with the pieces. Others here can give you a lot more useful suggestions.

Good luck.
 
Thank you very much for your input @KurtE! The more I read into the suggested topics, the more I understand I have zero clue about anything - always a good feeling. But I'll do my best to follow your advice, especially when it comes to the cluster sizes.

Thank you for the links @mjs513, I found entries like these when googling for example programs as well, however, my main problem is the inability to make sense of the information in them. Hence my initial question. Regardless, I appreciate the help!
 
I'm working on something similar. I am using a teensy 4.1 to sample data off of an mcp3008, timing is controlled by an Interrupt Service Routine (ISR), then I stuff data into a circular buffer and then write to SD card in chunks based on the optimal block size for the SD card. I had it working with 4 channels at 30kHZ on a previous version. I just refactored the code to clean it up and am testing the version below. The data is written to disk in raw bytes, a python script transforms it to a CSV in post processing.

https://github.com/julianblanco/audioSampleTeensy/blob/main/src/main.cpp
 
I had it working with 4 channels at 30kHZ on a previous version.

30kHz sounds crazy! Thanks for sharing :)

In the end, and with the help of @Frank B, I just went with the Beta Version of Teensyduino 1.54 and the regular SD.h, which is in this version works well fast enough for my purpose. As I understand, it's SdFat working it's magic in the background despite including the normal SD.h.

Thanks again everbody for your help, highly appreciate it!

Cheers,
Thomas
 
As we go into competition,
Here are the specs of my acquisition system (somehow defining upper performance limit)
6 channels - 96 kHz - 32 bits - 15 MB data buffer: (SPI disk limit due to possible buffer overrun (uSD latency issue)) SDIO is faster, but buffer also needed
No problems when compacting the data to relevant bits.
 
As we go into competition,
Here are the specs of my acquisition system (somehow defining upper performance limit)
6 channels - 96 kHz - 32 bits - 15 MB data buffer: (SPI disk limit due to possible buffer overrun (uSD latency issue)) SDIO is faster, but buffer also needed
No problems when compacting the data to relevant bits.

Nice! Do you have a project log? Would love to see how you did it!
 
I stripped down a logger I wrote some time ago to the bare bones:
* Open SD file
* write binary records consisting of timing data and 5 ADC values to a buffer
* when buffer is full, swap collection to another buffer and write the full buffer to SD.
* on request, display SD directory listing and sample ADC values.
* on request, close the SD file and stop collecting data.

I stripped some extras:

* Automatically starting logging after xxx seconds of no input at Serial();
* Automatically closing the existing file and starting a new file at user-defined intervals (normally about 6 hours)
* Setting the system to lower power when possible using the WFI instruction.

Here is the logger sketch:
Code:
/************************************************************
   Demo program to log time and 5 channels of  ADC data
   to the SD Card on a Teensy 4.1
   MBorgerson   2/5/2021
********************************************************/
#include <ADC.h>
#include <SD.h>
#include <TimeLib.h>

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

// specify the number of channels and which pins to use
#define NUMCHANNELS 5
const uint16_t ADCPins[NUMCHANNELS] = {A9, A8, A7, A6, A5};

// Specify how fast to collect samples
#define SAMPRATE  1000


// We will save a 32-bit Unix seconds value, 16-bit spare and NUMCHANNELS 16-bit ADC counts for each record
#define RECORDSIZE (4 +2 + NUMCHANNELS * 2)// size in bytes of each record 


// define a new data type for the samples
// with default packing, the structure will be a 
// multiple of 4 byte--so I added spare to make  it come
// out to 16 bytes.
typedef struct tSample {
  uint32_t useconds;
  uint16_t spare;   
  uint16_t avals[NUMCHANNELS];
} sampletype;  // each record is 14 bytes long for now

// now define two buffers, each holding 1024 samples.
// For efficiency, the number of samples in each buffer
// is a multiple of 512, which means that complete sectors
// are writen to the output file each time a buffer is written.
#define SAMPLESPERBUFFER 1024
tSample buffer1[SAMPLESPERBUFFER];
tSample buffer2[SAMPLESPERBUFFER];


// Define pointers to buffers used for ADC Collection
// and SD card file writing.
tSample *adcptr = NULL;
tSample *sdptr = NULL;

static uint32_t samplecount = 0;
volatile uint32_t bufferindex = 0;
volatile uint32_t overflows = 0;

const char compileTime [] = "\n\n5-channel ADC Test Compiled on " __DATE__ " " __TIME__;

static ADC *adc = new ADC(); // adc object
IntervalTimer CollectionTimer;

void setup() {
  uint16_t i;
  delay(500);
  Serial.begin(115200);
  delay(1000);
  for (i = 0; i < NUMCHANNELS; i++) {
    pinMode(ADCPins[i], INPUT_DISABLE);
  }

  Serial.printf("Size of tSample is %lu\n", sizeof(tSample));
  pinMode(ledpin, OUTPUT);
  CMSI();
  Serial.print("Initializing SD card...");

  if (!StartSDCard()) {
    Serial.println("initialization failed!");
    while (1) { // Fast blink LED if SD card not ready
      LEDON; delay(100);
      LEDOFF; delay(100);
    }
  }
  // use the default reference, the 3.3V analog voltage
  adc->adc0->setAveraging(1);
  adc->adc0->setResolution(12);
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED);   //was MED_SPEED
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed

  // Start the timer that controls ADC and DAC
  CollectionTimer.begin(ADC_ISR, 1000000 / SAMPRATE);

  Serial.println("Waiting for commands.");
  setSyncProvider(getTeensy3Time); // helps put time into file directory data
}

// This is the interrupt handler called by the collection interval timer
void ADC_ISR(void) {
  tSample *sptr;
  static uint32_t lastmicros;
  if (adcptr == NULL) return; // don't write unless adcptr is valid
  if (bufferindex >= SAMPLESPERBUFFER) {  // Switch buffers and signal write to SD
    if(sdptr != NULL) overflows++; // foreground didn't write buffer in time
    sdptr = adcptr; // notify foreground to write buffer to SD
    bufferindex = 0;
    if (adcptr == buffer1) {
      adcptr = buffer2;   // collect to buffer2 while buffer1 is written to SD
    } else { // use buffer 1 for collection
      adcptr = buffer1;
    }
  }
  // now we know that bufferindex is less than SAMPLESPERBUFFER
  // Please forgive the mix of pointers and array indices in the next line.
  sptr = (tSample *)&adcptr[bufferindex];
  // pure pointer arithmetic MIGHT be faster--depending on how well the compiler optimizes.
  sptr->useconds = now();
  sptr->spare = uint16_t(micros() - lastmicros);
  lastmicros =  micros();  // we can use this later  to check for sampling jitter
  sptr->avals[0] = (uint16_t)analogRead(ADCPins[0]);
  sptr->avals[1] = (uint16_t)analogRead(ADCPins[1]);
  sptr->avals[2] = (uint16_t)analogRead(ADCPins[2]);
  sptr->avals[3] = (uint16_t)analogRead(ADCPins[3]);
  sptr->avals[4] = (uint16_t)analogRead(ADCPins[4]);
  samplecount++;
  bufferindex++;
}

File adcFile;

void loop() {
  char ch;
  if (Serial.available()) {
    ch = Serial.read();
    Serial.println(ch);
    switch (ch) {
      case 'd' :
        Serial.println("SD Card Directory");
        SD.sdfs.ls(LS_DATE | LS_SIZE);
        Serial.println();
        break;
      case 's' :
        CMSI();
        break;
      case 'v' :
        ShowADCVolts();
        break;
      case 'q' :
        bufferindex = 0;
        if (adcFile) {
          adcFile.close();
          Serial.println("File Collection halted.");
          Serial.printf("Buffer overflows = %lu\n", overflows);
        }
        sdptr = NULL;
        adcptr = NULL;
        bufferindex = 0;
        break;
      case 'c' :
        if (adcFile) adcFile.close();
        adcFile = SD.open(NewFileName(), FILE_WRITE);
        samplecount = 0;
        Serial.printf("Opened file %s\n", NewFileName());
        bufferindex = 0;
        overflows = 0;
        
        adcptr = &buffer1[0];
        sdptr = NULL;
        break;
    } // end of switch(ch)

  }  // end of if(Serial.available())
  CheckFileState();
}

#define ADCVREF  3.291  // what I measured for V3.3 with my voltmeter
// Show voltages in first record of buffer1.  You will see a valid
// result only when  collection has been run.
void ShowADCVolts(void) {
  float volts;
  int i;
  Serial.println("Approximate ADC input voltages");
  for(i= 0; i<NUMCHANNELS; i++){
    volts = buffer1[0].avals[i] * ADCVREF/4096.0;
    Serial.printf(" %8.4f",volts); 
  }
  Serial.println();
}

void CMSI(void) {
  Serial.println();
  Serial.println(compileTime);
  if (adcFile) {
    Serial.printf("adcFile is open and contains %lu samples.\n", samplecount);
  } else {
    Serial.println("adcFile is closed.");
  }
  Serial.println("Valid commands are:");
  Serial.println("   s : Show this message");
  Serial.println("   d : Show SD Card Directory");
  Serial.println("   v : Show approximate volts");
  Serial.println("   c : Start data collection");
  Serial.println("   q : Stop data collection");
  Serial.println();
}

// Add data buffer to output file when needed
void CheckFileState(void) {

  // ADC ISR sets sdptr to a buffer point in ISR
  if (sdptr != NULL) { // write buffer to file
    if (adcFile) { // returns true when file is open
      LEDON
      adcFile.write(sdptr, SAMPLESPERBUFFER * sizeof(tSample));
      adcFile.flush();  // update directory and reduce card power
      LEDOFF
    } // end of if(adcfile)
    sdptr = NULL;  // reset pointer after file is written
  }  // end of if(sdptr != NULL)

}


/*****************************************************************************
   Read the Teensy RTC and return a time_t (Unix Seconds) value

 ******************************************************************************/
time_t getTeensy3Time() {
  return Teensy3Clock.get();
}

//------------------------------------------------------------------------------
//User provided date time callback function.
//   See SdFile::dateTimeCallback() for usage.
//
void dateTime(uint16_t* date, uint16_t* time) {
  // use the year(), month() day() etc. functions from timelib
  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(year(), month(), day());
  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(hour(), minute(), second());
}

bool StartSDCard() {
  if (!SD.begin(BUILTIN_SDCARD)) {
    Serial.println("\nSD File initialization failed.\n");
    return false;
  } else  Serial.println("initialization done.");
  // set date time callback function for file dates
  SdFile::dateTimeCallback(dateTime);
  return true;
}


// make a new file name based on time and date
char* NewFileName(void) {
  static char fname[36];
  time_t nn;
  nn = now();
  int mo = month(nn);
  int dd = day(nn);
  int hh = hour(nn);
  int mn = minute(nn);
  sprintf(fname, "ADC5CH_%02d%02d%02d%02d.dat", mo, dd, hh, mn);
  return &fname[0];
}

Since the OP mentioned using Matlab to review the data, here is a Matlab function to read the binary records and generate a Matlab structure with vectors for the time and adc variables.

Code:
function [dout]=loadadc5ch(filnam)
% load  5-channel analog logger data file
% file is structure is:
%  32-bit unsigned integer unix seconds
%  16-bit unsigned integer spare (used for microseconds between samples)
%  5 x 16-bit unsigned integer ADC counts
%  2/5/21  MJB

if nargin == 0
    [raw_name,temp,filterindex]=uigetfile('*.dat','Load ADC 5-channel Logger File');
    filnam=[temp raw_name];
else 
    filnam = [filnam];  % handy for functions that scan a directory
end

disp(sprintf('Reading data from %s',raw_name));
fid = fopen(filnam,'r');
fseek(fid,0,'eof'); % move to end of file
pos2 = ftell(fid); % pos2 is overall length of file
frewind(fid); % move back to beginning of file

%  each record is 16 bytes as described above

nrecords=floor((pos2)/16); % number of records
disp(sprintf('%d records of data in the file',nrecords));


%read unix seconds
fseek(fid,0,'bof');
dout.secs = fread(fid,nrecords,'uint32',12); % skip  12 bytes

fseek(fid,4,'bof'); % rewind to beginning of spare field
% next read spare
dout.spare = fread(fid,nrecords,'uint16',14); % skip  14

fseek(fid,6,'bof'); % start at beginning of CH0
dout.ch0 = fread(fid,nrecords,'uint16',14);

fseek(fid,8,'bof'); % start at beginning of CH1
dout.ch1 = fread(fid,nrecords,'uint16',14);

fseek(fid,10,'bof'); % start at beginning of CH2
dout.ch2 = fread(fid,nrecords,'uint16',14);

fseek(fid,12,'bof'); % start at beginning of CH3
dout.ch3 = fread(fid,nrecords,'uint16',14);

fseek(fid,14,'bof'); % start at beginning of CH4
dout.ch4 = fread(fid,nrecords,'uint16',14);

% Now convert to uncalibrated volts
adcref = 3.32;   % assumed value of adc reference
% in 12-bit mode max output count is 4095
dout.ch0  = dout.ch0 * (adcref/4095.0); % for testing, 2.5V precision Ref.
dout.ch1  = dout.ch1 * (adcref/4095.0); % ch0 divided down to ~1.0V
dout.ch2  = dout.ch2 * (adcref/4095.0); % Alkaline AA cell
dout.ch3  = dout.ch3 * (adcref/4095.0); % sine wave ~100Hz
dout.ch4  = dout.ch4 * (adcref/4095.0); % sine wave ~100Hz
fclose(fid);
plot(dout.ch0); % check noise in 2.5V reference sample

% now we determine the sample rate by seeing how many samples 
% occur between changes in the seconds value
tp = diff(dout.secs);  %  should be zero or one
tidx = find(tp); % get the indices of non-zero values in tp
samprate  = mean(diff(tidx)); %
disp(sprintf('Recovered sample rate is %6.2f samples per second',samprate));
dout.samprate = samprate;
end

The sketch is set up to log at 1000 samples per second, but could collect faster with larger buffers. The necessary modifications should be intuitively obvious to the casual observer. (Oh, how I used to hate it when my college physics prof would use that phrase!)

While I wrote and tested on at T4.1, the sketch code should work on a T3.6 or T3.5 also. I suspect the primary value in the example is the management of the ping-pong buffers with collecting data in an interval timer ISR and writing the data to the SD card in the foreground loop() function. On the T4.1, the buffers are in DTCM, so there is no need to muck about with cache deletes, etc.
 
Status
Not open for further replies.
Back
Top