Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 16 of 16

Thread: Minimalistic SdFat Datalogger for Teensy4.1 - Example?

  1. #1
    Junior Member
    Join Date
    Feb 2021
    Location
    Munich, Germany
    Posts
    6

    Minimalistic SdFat Datalogger for Teensy4.1 - Example?

    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 by ThomasD; 02-04-2021 at 10:21 AM.

  2. #2
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    8,230
    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:
    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
    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 by Frank B; 02-04-2021 at 10:51 AM.

  3. #3
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    8,230
    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.

  4. #4
    Junior Member
    Join Date
    Feb 2021
    Location
    Munich, Germany
    Posts
    6
    Hi Frank,

    thank you very much for your response!

    Quote Originally Posted by Frank B View Post
    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.


    Quote Originally Posted by Frank B View Post
    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

  5. #5
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    8,230
    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.

  6. #6
    Junior Member
    Join Date
    Feb 2021
    Location
    Munich, Germany
    Posts
    6
    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

  7. #7
    Senior Member
    Join Date
    Jul 2014
    Posts
    3,123
    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

  8. #8
    Junior Member
    Join Date
    Feb 2021
    Location
    Munich, Germany
    Posts
    6
    Quote Originally Posted by WMXZ View Post
    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.

    Quote Originally Posted by WMXZ View Post
    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.

  9. #9
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    6,796
    Over there years there has been several threads on data logging on this forum. If you do a search of the forum you probably can find them. Just a quick search for :fast data logger" gave me a couple that might be of interest:

    https://forum.pjrc.com/threads/60809...ast+datalogger

    https://forum.pjrc.com/threads/43621...ast+datalogger

  10. #10
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    8,890
    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...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.

  11. #11
    Junior Member
    Join Date
    Feb 2021
    Location
    Munich, Germany
    Posts
    6
    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!

  12. #12
    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/audi...n/src/main.cpp

  13. #13
    Junior Member
    Join Date
    Feb 2021
    Location
    Munich, Germany
    Posts
    6
    Quote Originally Posted by lightningmar View Post
    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

  14. #14
    Senior Member
    Join Date
    Jul 2014
    Posts
    3,123
    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.

  15. #15
    Quote Originally Posted by WMXZ View Post
    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!

  16. #16
    Senior Member
    Join Date
    Feb 2018
    Location
    Corvallis, OR
    Posts
    330
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •