Can't see SD card files on PC using MTP_Teensy

Dr_Nic

Member
Teensy 4.1, Windows 11

I am writing 8 floats to the SD card every 10us using IntervalTimer & a ring buffer (full listing below) for 12 seconds.
Everything appears to working fine except after the 12s is up, I close the files & put the system into an endless MTP.loop() I cant see the generated files in the Win 11 explorer. The Teensy shows but not the file structure.

What am I missing here?

EDIT: Additional info, I can see the files if I take the SD card out of the Teensy & put it into an SD card reader.


C++:
#include <Arduino.h>
#include <SD.h>
#include <MTP_Teensy.h>
#include "SdFat.h"
#include "RingBuf.h"

#define SD_CONFIG SdioConfig(FIFO_SDIO) // Force native 4-bit SDIO mode

// Allocate a 128 KB Ring Buffer in DTCM
// 128 KB can cache about 4,000 samples (~40ms of backup buffer if the SD stalls)
const size_t RING_BUF_SIZE = 128 * 1024;

// Pre-allocate a 500 MB file block on the card to avoid allocation delays
const uint64_t FILE_SIZE = 500ULL * 1024ULL * 1024ULL;

SdFs sd;                                                                      // SD card instance
FsFile file;                                                                  // File instance for ring buffer to write to on the SD card
RingBuf<FsFile, RING_BUF_SIZE> rb;                                            // Ring buffer
IntervalTimer samplingTimer;                                                  // Timer IRQ instance

// Run time counter
uint32_t start_time;

// 8 floats = 32 bytes per sample packet
struct DataPacket
{
  float values[8];
};

// Volatile tracking variables for the ISR
volatile uint32_t totalSamplesLogged = 0;
volatile bool bufferOverflowed = false;

// Dummy data generator
void readSensors(DataPacket *packet)
{
  for (int i = 0; i < 8; i++)
  {
    packet->values[i] = 1.234f * (i + 1);
  }
}

// 10 Microsecond Interrupt Service Routine (ISR)
void samplingISR()
{
  DataPacket packet;
  readSensors(&packet);

  // Check if ring buffer has space for 32 bytes
  if (rb.bytesFree() >= sizeof(packet))
  {
    rb.write((const uint8_t*)&packet, sizeof(packet));
    totalSamplesLogged++;
  }
  else
  {
    bufferOverflowed = true; // Memory full! SD card couldn't keep up
  }
}

void setup()
{
  Serial.begin(9600);
  while (!Serial && millis() < 4000);

  MTP.begin();

  Serial.print("Initializing SD... ");
  if (!sd.begin(SD_CONFIG))
  {
    sd.initErrorHalt(&Serial);
  }
  else
  {
    MTP.addFilesystem(SD, "SD Card");
  }

  // Add backup file of previous run
  if (SD.exists("fastlog.bin"))
  {
    if (SD.exists("fastlog_old.bin"))
    {
       SD.remove("fastlog_old.bin");
    }
    SD.rename("fastlog.bin", "fastlog_old.bin");
  }
 
  if (!file.open("fastlog.bin", O_RDWR | O_CREAT | O_TRUNC))
  {
    Serial.println("Failed to open file.");
    return;
  }
  else
  {
    Serial.println("File opened.");
  }

  // Pre-allocate contiguous sectors on the physical flash memory
  Serial.print("Pre-allocating space... ");
  if (!file.preAllocate(FILE_SIZE))
  {
    Serial.println("Allocation failed!");
    return;
  }
 
  rb.begin(&file);
  Serial.println("\nSystem ready. Logging starting.");

  // Start the hardware timer to fire exactly every 10 microseconds (100 kHz)
  samplingTimer.begin(samplingISR, 10);

  start_time = millis();
}

void loop()
{
  // Check for disaster metrics
  if (bufferOverflowed)
  {
    samplingTimer.end(); // Emergency stop
    file.close();
    Serial.println("\n[CRITICAL ERROR]: Buffer Overflow! SD card write stalled too long.");
    while (1)
    {
      MTP.loop();
    }
  }

  // Maximum sector block size optimized for the Teensy 4.1 controller
  size_t n = rb.bytesUsed();
 
  // Only execute a write sector command if there's data AND the SD card is idle
  if (n >= 128 && !file.isBusy())
  {
    // Write full chunks of 128-byte sectors at a time
    rb.writeOut(128);
  }

  // Print statistical metrics safely outside the fast stream
  static uint32_t lastPrint = 0;
  if (millis() - lastPrint > 2000)
  {
    lastPrint = millis();
    Serial.printf("Logged total: %lu samples | Buffer free: %d bytes\n", totalSamplesLogged, rb.bytesFree());
  }

  // End condition, close SD file & eneter MTP loop
  if (millis() - start_time > 12000)
  {
    file.close();
    Serial.println("\nFinished sampling");

    while(1)
    {
      MTP.loop();
    }
  }
 
}
 
Last edited:
Add a call to MTP.reset() beefore entering the loop that calls MTP.loop(). I don't know the internals, but I think it causes the host to update its directory listing.

Glad to see you are using RingBuf and preAllocate() and checking file.isBusy() before writing to SD, but increase your write size from 128 to 512 as in the TeensySdioLogger example. SD is actually written in chunks of 512, so the intermediate writes of 128 bytes just get buffered in the driver until the 512 buffer is full.
 
Last edited:
Add a call to MTP.reset() beefore entering the loop that calls MTP.loop(). I don't know the internals, but I think it causes the host to update its directory listing.

Glad to see you are using RingBuf and preAllocate() and checking file.isBusy() before writing to SD, but increase your write size from 128 to 512 as in the TeensySdioLogger example. SD is actually written in chunks of 512, so the intermediate writes of 128 bytes just get buffered in the driver until the 512 buffer is full.


Still doesn't show the files with the additional reset. However when I've finished sampling & then reflash the Teensy with the code below the directory shows up with the data file.


C++:
#include <SD.h>
#include <MTP_Teensy.h>

void setup() {
  Serial.begin(9600);
  while (!Serial && millis() < 5000);

  // Start MTP and SD storage
  MTP.begin();
  if (!SD.begin(BUILTIN_SDCARD)) {
    Serial.println("SD card initialization failed!");
  } else {
    MTP.addFilesystem(SD, "SD Card");
  }
}

void loop()
{
  MTP.loop(); // Processes MTP requests from Windows
}
 
Last edited:
It has been a while since I have played much with MTP.

Note: MTP_Teensy library has been replaced with MTP built into the core...

After you sent the reset event, did you try refreshing your directory on your windows 11 explorer (F5).
I don't believe there is anything within the system that tells the Windows system to reread the MTP information automatically.

At one point was playing with:
Code:
bool send_addObjectEvent(uint32_t store, const char *pathname);
within MTP, where you manually tell the system you added some file and it hopefully maps that pathname into an object and then sends
an event to the host. If I remember correctly on some systems it worked on others...
 
That link to the original MTP version before incorporation into the core as @KurtE said. However reset is still available in the MTP core version:

C++:
  void reset() {
    // TODO check if session open
    // TODO clear storage info
    // TODO other state to zero or initialize?
    send_DeviceResetEvent();
  }
 
It has been a while since I have played much with MTP.

Note: MTP_Teensy library has been replaced with MTP built into the core...

After you sent the reset event, did you try refreshing your directory on your windows 11 explorer (F5).
I don't believe there is anything within the system that tells the Windows system to reread the MTP information automatically.

At one point was playing with:
Code:
bool send_addObjectEvent(uint32_t store, const char *pathname);
within MTP, where you manually tell the system you added some file and it hopefully maps that pathname into an object and then sends
an event to the host. If I remember correctly on some systems it worked on others...

Thanks didn't realize MTP was built into the core & have commented out the MTP_Teensy.h include & it all compiles fine but I still don't see the files even after refreshing the folder
 
Can you please post your modified sketch that uses MTP.reset() ?

Amended code below...

C++:
#include <Arduino.h>
#include <SD.h>
#include "SdFat.h"
#include "RingBuf.h"

#define SD_CONFIG SdioConfig(FIFO_SDIO) // Force native 4-bit SDIO mode

// How long to sample for in ms
uint32_t sample_interval = 12000;

// Allocate a 128 KB Ring Buffer in DTCM
// 128 KB can cache about 4,000 samples (~40ms of backup buffer if the SD stalls)
const size_t RING_BUF_SIZE = 128 * 1024;

// Pre-allocate a 500 MB file block on the card to avoid allocation delays
const uint64_t FILE_SIZE = 500ULL * 1024ULL * 1024ULL;

SdFs sd;                                                                      // SD card instance
FsFile file;                                                                  // File instance for ring buffer to write to on the SD card
RingBuf<FsFile, RING_BUF_SIZE> rb;                                            // Ring buffer
IntervalTimer samplingTimer;                                                  // Timer IRQ instance


// Run time counter
uint32_t start_time;

// Volatile tracking variables for the ISR
volatile uint32_t totalSamplesLogged = 0;
volatile bool bufferOverflowed = false;

// Dummy data
float packet[8] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8};


// 10 Microsecond Interrupt Service Routine (ISR)
void samplingISR()
{
  // Check if ring buffer has space for 64 bytes
  if (rb.bytesFree() >= sizeof(packet))
  {
    rb.write((const uint8_t*)&packet, sizeof(packet));
    totalSamplesLogged++;
  }
  else
  {
    bufferOverflowed = true; // Memory full! SD card couldn't keep up
  }
}

void setup()
{
  Serial.begin(9600);
  while (!Serial && millis() < 4000);

  MTP.begin(); 

  Serial.print("Initializing SD... ");
  if (!sd.begin(SD_CONFIG))
  {
    sd.initErrorHalt(&Serial);
  }
  else
  {
    MTP.addFilesystem(SD, "SD Card");
  }

  // Add backup file of previous run
  if (SD.exists("fastlog.bin"))
  {
    if (SD.exists("fastlog_old.bin"))
    {
       SD.remove("fastlog_old.bin");
    }
    SD.rename("fastlog.bin", "fastlog_old.bin");
  }
 
  if (!file.open("fastlog.bin", O_RDWR | O_CREAT | O_TRUNC))
  {
    Serial.println("Failed to open file.");
    return;
  }
  else
  {
    Serial.println("File opened.");
  }

  // Pre-allocate contiguous sectors on the physical flash memory
  Serial.print("Pre-allocating space... ");
  if (!file.preAllocate(FILE_SIZE))
  {
    Serial.println("Allocation failed!");
    return;
  }
 
  rb.begin(&file);
  Serial.println("\nSystem ready. Logging starting.");

  // Start the hardware timer to fire exactly every 10 microseconds (100 kHz)
  samplingTimer.begin(samplingISR, 10);

  start_time = millis();;
}

void loop()
{
  // Check for disaster metrics
  if (bufferOverflowed)
  {
    samplingTimer.end(); // Emergency stop
    file.close();
    Serial.println("\n[CRITICAL ERROR]: Buffer Overflow! SD card write stalled too long.");
    MTP.reset();
    
    while (1)
    {
      MTP.loop();
    }
  }

  // Maximum sector block size optimized for the Teensy 4.1 controller
  size_t n = rb.bytesUsed();
 
  // Only execute a write sector command if there's data AND the SD card is idle
  if (n >= 512 && !file.isBusy())
  {
    // Write full chunks of 512-byte sectors at a time
    rb.writeOut(512);
  }

  // Print statistical metrics safely outside the fast stream
   static uint32_t lastPrint = 0;
   if (millis() - lastPrint > 2000)
   {
     lastPrint = millis();
     Serial.printf("Logged total: %lu samples | Buffer free: %d bytes\n", totalSamplesLogged, rb.bytesFree());
   }

  // End condition & close SD file
  if (millis() - start_time > sample_interval)
  {
    samplingTimer.end(); // Emergency stop
    file.close();
    Serial.println("\nFinished sampling");
    MTP.reset();
    
    while(1)
    {
      MTP.loop();
    }
  }
 
}
 
I don't know why your sketch doesn't work, but the sketch below does. When I run your sketch, it does not reliably complete logging, and rather than figure out why, I went back to one of my own sketches. This one started from the 1MSPS logger by @mborgerson. I added MTP. When the sketch runs, it prompts you to enter either "l" (log), or "d" (directory). If I open Windows Explorer and navigate to the Teensy SD folder, then press "l" <enter>, a new file is created on SD, and if I then press F5 within Windows Explorer, the new file is displayed. Give it a try and let us know if it works for you, too.

Code:
/********************************************************************
* T4_MTP_SD - MTP and SD test program (from @mborgerson 1MSPS logger)
*
* MJB 10/19/20  Modified to log 12-bit samples and save as uint16_t
* JWP 02/11/25  Use SdFat RingBuf and (file).isBusy() function
* JWP 04/08/25  Use YYYYMMDD as dirname and HHMMSS as filename
* JWP 04/08/25  Add MTP support
* JWP 09/16/26  Rebuild with TeensyDuino 1.62 (MTP in T4.1 core)
********************************************************************/
#include <SD.h>            // wrapper for SdFat
#include <TimeLib.h>        // for internal RTC
#include <RingBuf.h>        // buffer writes to SD

#define FILESIZE_MB (200)
#define TIMER_US (100)
#define MAXWRITES (2000)
#define RINGBUF_SIZE (64*1024)  // JWP - 100KB buffer for 2MBps data rate

#define CYC_TO_S(cycles)  ((double)((cycles)*(1.0/F_CPU_ACTUAL)))
#define CYC_TO_US(cycles) ((uint32_t)(0.5+(cycles)*(1e6/F_CPU_ACTUAL)))

const char compileTime [] = "MegaSample logger Compiled on " __DATE__ " " __TIME__;

// file and buffer for writing to SD
FsFile logFile;
RingBuf<FsFile,RINGBUF_SIZE> rb;

IntervalTimer timer;

/*******************************************************************************
* function to execute on IntervalTimer
*******************************************************************************/
void timerHandler( void ) {
  char *s = "one line of data one line of data one line of data\n";
  rb.memcpyIn( s, strlen(s) );
}

/*******************************************************************************
* Read the Teensy RTC and return a time_t (Unix Seconds) value
*******************************************************************************/
time_t getTeensy3Time() {
  return Teensy3Clock.get();
}

/*******************************************************************************
* User-provided callback function to provide date/time for dir/file listings:
* This function MUST be named "dateTime"
* See SdFile::dateTimeCallback() for usage.
*******************************************************************************/
void dateTime(uint16_t* date, uint16_t* time) {
  *date = FAT_DATE( year(), month(),  day() );     // pack y/m/d into uint16_t
  *time = FAT_TIME( hour(), minute(), second() );  // pack h/m/s into uint16_t
}

/*******************************************************************************
* setup() -- called once on bootup
*******************************************************************************/
void setup() {
  Serial.begin(9600);
  while (!Serial) {}
  Serial.println( compileTime );

  if (!SD.begin( BUILTIN_SDCARD )) {
    Serial.println("SD.begin() failed");
    while (1) {}
  }
 
  Serial.println( "Starting MTP..." );
  MTP.begin();
  MTP.addFilesystem( SD, "SD Card" );
 
  uint8_t fat_type = SD.sdfs.fatType();
  if (fat_type == FAT_TYPE_EXFAT)
    Serial.println("Type is exFAT");
  else
    Serial.printf("Type is FAT%d\n", int16_t(fat_type));

  rb.begin(&logFile); // JWP - associate logFile with RingBuf 

  setSyncProvider(getTeensy3Time); // helps put time into file directory data
}

/*******************************************************************************
* loop() -- called repeatedly after setup()
*******************************************************************************/
void loop() {
  // MTP.loop() must be called for MTP - first call causes print to Serial
  MTP.loop();
  // process user input
  if (Serial.available()) {
    char ch = Serial.read();
    if (ch == 'l')  { LogData(); MTP.reset(); }
    if (ch == 'd')  SD.sdfs.ls(LS_SIZE | LS_DATE | LS_R);
  }
}

/*******************************************************************************
* write MAXWRITES sectors (512 bytes each) to LogFile
*******************************************************************************/
void LogData(void)
{
  if (!OpenLogFile()) {
    Serial.print("Did not open log file.");
    return;
  }
 
  // start timer (handler writes to RingBuf every TIMER_US us)
  timer.begin( timerHandler, TIMER_US );
 
  uint32_t nBytes = 0;
  uint32_t nWrites = 0;
  uint32_t rbMaxUsed = 0;
  uint32_t fileMaxCycles = 0;
  uint32_t start = ARM_DWT_CYCCNT;
  do {
    // track max number of bytes in RingBuf
    uint32_t n = rb.bytesUsed();
    if (n > rbMaxUsed)
      rbMaxUsed = n;
    
    if (rb.bytesUsed() >= 512 && !logFile.isBusy()) { // when data in buffer, write to SD card
      uint32_t start = ARM_DWT_CYCCNT;
      rb.writeOut( 512 );
      uint32_t cycles = ARM_DWT_CYCCNT - start;
      // track longest write time, skipping first which always takes 2x longer than others
      if (nWrites!=0 && cycles > fileMaxCycles)
        fileMaxCycles = cycles;
      nWrites++;
    }
  }  while (nWrites < MAXWRITES);
  uint32_t duration = ARM_DWT_CYCCNT - start;
  timer.end();

  if (logFile) {
    rb.sync(); // JWP - write remaining data from RingBuf to file
    logFile.truncate();  // truncate to amount actually written
    nBytes = logFile.position(); // size after truncate()
    logFile.close();
  }
  Serial.printf("Duration %1.3lf s\n", CYC_TO_S(duration));
  Serial.printf("%1lu bytes written\n", nBytes);
  Serial.printf("RingBuf max usage %u bytes of %u\n", rbMaxUsed, RINGBUF_SIZE);
  Serial.printf("File max write time %u us\n", CYC_TO_US(fileMaxCycles));
}

/*******************************************************************************
* create/open/preAllocate file YYYYMMDD/HHMMSS
*******************************************************************************/
bool OpenLogFile(void)
{
  char dirname[16], filename[16], pathname[32];
  uint64_t alloclength = FILESIZE_MB * (1024ULL * 1024ULL);
 
  sprintf( dirname, "%04hu%02hu%02hu", year(), month(), day() );
  sprintf( filename, "%02hu%02hu%02hu.txt", hour(), minute(), second() );
  sprintf( pathname, "%s//%s", dirname, filename );
 
  if (!SD.sdfs.exists( dirname )) {
    if (!SD.sdfs.mkdir( dirname )) {
      Serial.println("mkdir() failed.");
      return false;
    }
  }
  if (!logFile.open( pathname,  O_RDWR | O_CREAT | O_TRUNC )) {
    Serial.println("file open() failed.");
    return false;
  }
  if (!logFile.preAllocate(alloclength)) {
    Serial.println("preAllocate() failed.");
    return false;
  }
 
  Serial.printf( "Created file %s size %1llu bytes\n", pathname, alloclength );
  return true;
}
 
I don't know why your sketch doesn't work, but the sketch below does.
Got curious on why that sketch didn't work. Think I figured it out after playing with it - obvious once once you see the fix
//CHANGED sd to SD !!! and used BUILTIN_SDCARD in SD.begin(BUILTIN_SDCARD)

Rich (BB code):
#include <Arduino.h>
#include <SD.h>
#include "SdFat.h"
#include "RingBuf.h"

//#define SD_CONFIG SdioConfig(FIFO_SDIO) // Force native 4-bit SDIO mode

// Allocate a 128 KB Ring Buffer in DTCM
// 128 KB can cache about 4,000 samples (~40ms of backup buffer if the SD stalls)
const size_t RING_BUF_SIZE = 128 * 1024;

// Pre-allocate a 500 MB file block on the card to avoid allocation delays
const uint64_t FILE_SIZE = 500ULL * 1024ULL * 1024ULL;

SdFs sd;                                                                      // SD card instance
FsFile file;                                                                  // File instance for ring buffer to write to on the SD card
RingBuf<FsFile, RING_BUF_SIZE> rb;                                            // Ring buffer
IntervalTimer samplingTimer;                                                  // Timer IRQ instance

// Run time counter
uint32_t start_time;

// 8 floats = 32 bytes per sample packet
struct DataPacket
{
  float values[8];
};

// Volatile tracking variables for the ISR
volatile uint32_t totalSamplesLogged = 0;
volatile bool bufferOverflowed = false;

// Dummy data generator
void readSensors(DataPacket *packet)
{
  for (int i = 0; i < 8; i++)
  {
    packet->values = 1.234f * (i + 1);
  }
}

// 10 Microsecond Interrupt Service Routine (ISR)
void samplingISR()
{
  DataPacket packet;
  readSensors(&packet);

  // Check if ring buffer has space for 32 bytes
  if (rb.bytesFree() >= sizeof(packet))
  {
    rb.write((const uint8_t*)&packet, sizeof(packet));
    totalSamplesLogged++;
  }
  else
  {
    bufferOverflowed = true; // Memory full! SD card couldn't keep up
  }
}

void setup()
{
  Serial.begin(9600);
  while (!Serial && millis() < 4000);

  MTP.begin();

  Serial.print("Initializing SD... ");
//CHANGED sd to SD !!! and used BUILTIN_SDCARD
  if (!SD.begin(BUILTIN_SDCARD))
  {
    sd.initErrorHalt(&Serial);
  }
  else
  {
    MTP.addFilesystem(SD, "SD Card");
  }

  // Add backup file of previous run
  if (SD.exists("fastlog.bin"))
  {
    if (SD.exists("fastlog_old.bin"))
    {
       SD.remove("fastlog_old.bin");
    }
    SD.rename("fastlog.bin", "fastlog_old.bin");
  }
 
  if (!file.open("fastlog.bin", O_RDWR | O_CREAT | O_TRUNC))
  {
    Serial.println("Failed to open file.");
    return;
  }
  else
  {
    Serial.println("File opened.");
  }

  // Pre-allocate contiguous sectors on the physical flash memory
  Serial.print("Pre-allocating space... ");
  if (!file.preAllocate(FILE_SIZE))
  {
    Serial.println("Allocation failed!");
    return;
  }
 
  rb.begin(&file);
  Serial.println("\nSystem ready. Logging starting.");

  // Start the hardware timer to fire exactly every 10 microseconds (100 kHz)
  samplingTimer.begin(samplingISR, 10);

  start_time = millis();
}

void loop()
{
  // Check for disaster metrics
  if (bufferOverflowed)
  {
    samplingTimer.end(); // Emergency stop
    file.close();
    Serial.println("\n[CRITICAL ERROR]: Buffer Overflow! SD card write stalled too long.");
    while (1)
    {
      MTP.loop();
    }
  }

  // Maximum sector block size optimized for the Teensy 4.1 controller
  size_t n = rb.bytesUsed();
 
  // Only execute a write sector command if there's data AND the SD card is idle
  if (n >= 128 && !file.isBusy())
  {
    // Write full chunks of 128-byte sectors at a time
    rb.writeOut(128);
  }

  // Print statistical metrics safely outside the fast stream
  static uint32_t lastPrint = 0;
  if (millis() - lastPrint > 2000)
  {
    lastPrint = millis();
    Serial.printf("Logged total: %lu samples | Buffer free: %d bytes\n", totalSamplesLogged, rb.bytesFree());
  }

  // End condition, close SD file & eneter MTP loop
  if (millis() - start_time > 12000)
  {
    file.close();
    Serial.println("\nFinished sampling");

    while(1)
    {
      MTP.loop();
    }
  }
 
}

Then for output got
Code:
MTP STARTED
File opened.
Pre-allocating space...
System ready. Logging starting.
Logged total: 0 samples | Buffer free: 131072 bytes
Logged total: 200026 samples | Buffer free: 131008 bytes

[CRITICAL ERROR]: Buffer Overflow! SD card write stalled too long.

and Teensy shows up with all associated info.
 
Last edited:
Still doesn't show the files with the additional reset. However when I've finished sampling & then reflash the Teensy with the code below the directory shows up with the data file.


C++:
#include <SD.h>
#include <MTP_Teensy.h>

void setup() {
  Serial.begin(9600);
  while (!Serial && millis() < 5000);

  // Start MTP and SD storage
  MTP.begin();
  if (!SD.begin(BUILTIN_SDCARD)) {
    Serial.println("SD card initialization failed!");
  } else {
[QUOTE="Dr_Nic, post: 370117, member: 123776"]
Teensy 4.1, Windows 11

I am writing 8 floats to the SD card every 10us using [I]IntervalTimer[/I] & a ring buffer (full listing below) for 12 seconds.
Everything appears to working fine except after the 12s is up, I close the files & put the system into an endless [I]MTP.loop() [/I]I cant see the generated files in the Win 11 explorer. The Teensy shows but not the file structure.

What am I missing here?

EDIT: Additional info, I can see the files if I take the SD card out of the Teensy & put it into an SD card reader.


[CODE=cpp]#include <Arduino.h>
#include <SD.h>
#include <MTP_Teensy.h>
#include "SdFat.h"
#include "RingBuf.h"

#define SD_CONFIG SdioConfig(FIFO_SDIO) // Force native 4-bit SDIO mode

// Allocate a 128 KB Ring Buffer in DTCM
// 128 KB can cache about 4,000 samples (~40ms of backup buffer if the SD stalls)
const size_t RING_BUF_SIZE = 128 * 1024;

// Pre-allocate a 500 MB file block on the card to avoid allocation delays
const uint64_t FILE_SIZE = 500ULL * 1024ULL * 1024ULL;

SdFs sd;                                                                      // SD card instance
FsFile file;                                                                  // File instance for ring buffer to write to on the SD card
RingBuf<FsFile, RING_BUF_SIZE> rb;                                            // Ring buffer
IntervalTimer samplingTimer;                                                  // Timer IRQ instance

// Run time counter
uint32_t start_time;

// 8 floats = 32 bytes per sample packet
struct DataPacket
{
  float values[8];
};

// Volatile tracking variables for the ISR
volatile uint32_t totalSamplesLogged = 0;
volatile bool bufferOverflowed = false;

// Dummy data generator
void readSensors(DataPacket *packet)
{
  for (int i = 0; i < 8; i++)
  {
    packet->values[i] = 1.234f * (i + 1);
  }
}

// 10 Microsecond Interrupt Service Routine (ISR)
void samplingISR()
{
  DataPacket packet;
  readSensors(&packet);

  // Check if ring buffer has space for 32 bytes
  if (rb.bytesFree() >= sizeof(packet))
  {
    rb.write((const uint8_t*)&packet, sizeof(packet));
    totalSamplesLogged++;
  }
  else
  {
    bufferOverflowed = true; // Memory full! SD card couldn't keep up
  }
}

void setup()
{
  Serial.begin(9600);
  while (!Serial && millis() < 4000);

  MTP.begin();

  Serial.print("Initializing SD... ");
  if (!sd.begin(SD_CONFIG))
  {
    sd.initErrorHalt(&Serial);
  }
  else
  {
    MTP.addFilesystem(SD, "SD Card");
  }

  // Add backup file of previous run
  if (SD.exists("fastlog.bin"))
  {
    if (SD.exists("fastlog_old.bin"))
    {
       SD.remove("fastlog_old.bin");
    }
    SD.rename("fastlog.bin", "fastlog_old.bin");
  }
 
  if (!file.open("fastlog.bin", O_RDWR | O_CREAT | O_TRUNC))
  {
    Serial.println("Failed to open file.");
    return;
  }
  else
  {
    Serial.println("File opened.");
  }

  // Pre-allocate contiguous sectors on the physical flash memory
  Serial.print("Pre-allocating space... ");
  if (!file.preAllocate(FILE_SIZE))
  {
    Serial.println("Allocation failed!");
    return;
  }
 
  rb.begin(&file);
  Serial.println("\nSystem ready. Logging starting.");

  // Start the hardware timer to fire exactly every 10 microseconds (100 kHz)
  samplingTimer.begin(samplingISR, 10);

  start_time = millis();
}

void loop()
{
  // Check for disaster metrics
  if (bufferOverflowed)
  {
    samplingTimer.end(); // Emergency stop
    file.close();
    Serial.println("\n[CRITICAL ERROR]: Buffer Overflow! SD card write stalled too long.");
    while (1)
    {
      MTP.loop();
    }
  }

  // Maximum sector block size optimized for the Teensy 4.1 controller
  size_t n = rb.bytesUsed();
 
  // Only execute a write sector command if there's data AND the SD card is idle
  if (n >= 128 && !file.isBusy())
  {
    // Write full chunks of 128-byte sectors at a time
    rb.writeOut(128);
  }

  // Print statistical metrics safely outside the fast stream
  static uint32_t lastPrint = 0;
  if (millis() - lastPrint > 2000)
  {
    lastPrint = millis();
    Serial.printf("Logged total: %lu samples | Buffer free: %d bytes\n", totalSamplesLogged, rb.bytesFree());
  }

  // End condition, close SD file & eneter MTP loop
  if (millis() - start_time > 12000)
  {
    file.close();
    Serial.println("\nFinished sampling");

    while(1)
    {
      MTP.loop();
    }
  }
 
}

MTP.addFilesystem(SD, "SD Card");
}
}

void loop()
{
MTP.loop(); // Processes MTP requests from Windows
}
[/CODE]
[/QUOTE]
I know I may catch some flak here, however, feed your code into ChatGPT (cut and paste) it will help with a few suggestions as well as information.
 
When people post to a tech forum with a problem I think they expect a more informed response than "just use AI".
 
I know I'm probably going to catch some flak here, I know, a lot of people hate AI, however, for debugging it's a great tool,


Teensy 4.1, Windows 11

I am writing 8 floats to the SD card every 10us using IntervalTimer & a ring buffer (full listing below) for 12 seconds.
Everything appears to working fine except after the 12s is up, I close the files & put the system into an endless MTP.loop() I cant see the generated files in the Win 11 explorer. The Teensy shows but not the file structure.

What am I missing here?

EDIT: Additional info, I can see the files if I take the SD card out of the Teensy & put it into an SD card reader.


C++:
#include <Arduino.h>
#include <SD.h>
#include <MTP_Teensy.h>
#include "SdFat.h"
#include "RingBuf.h"

#define SD_CONFIG SdioConfig(FIFO_SDIO) // Force native 4-bit SDIO mode

// Allocate a 128 KB Ring Buffer in DTCM
// 128 KB can cache about 4,000 samples (~40ms of backup buffer if the SD stalls)
const size_t RING_BUF_SIZE = 128 * 1024;

// Pre-allocate a 500 MB file block on the card to avoid allocation delays
const uint64_t FILE_SIZE = 500ULL * 1024ULL * 1024ULL;

SdFs sd;                                                                      // SD card instance
FsFile file;                                                                  // File instance for ring buffer to write to on the SD card
RingBuf<FsFile, RING_BUF_SIZE> rb;                                            // Ring buffer
IntervalTimer samplingTimer;                                                  // Timer IRQ instance

// Run time counter
uint32_t start_time;

// 8 floats = 32 bytes per sample packet
struct DataPacket
{
  float values[8];
};

// Volatile tracking variables for the ISR
volatile uint32_t totalSamplesLogged = 0;
volatile bool bufferOverflowed = false;

// Dummy data generator
void readSensors(DataPacket *packet)
{
  for (int i = 0; i < 8; i++)
  {
    packet->values[i] = 1.234f * (i + 1);
  }
}

// 10 Microsecond Interrupt Service Routine (ISR)
void samplingISR()
{
  DataPacket packet;
  readSensors(&packet);

  // Check if ring buffer has space for 32 bytes
  if (rb.bytesFree() >= sizeof(packet))
  {
    rb.write((const uint8_t*)&packet, sizeof(packet));
    totalSamplesLogged++;
  }
  else
  {
    bufferOverflowed = true; // Memory full! SD card couldn't keep up
  }
}

void setup()
{
  Serial.begin(9600);
  while (!Serial && millis() < 4000);

  MTP.begin();

  Serial.print("Initializing SD... ");
  if (!sd.begin(SD_CONFIG))
  {
    sd.initErrorHalt(&Serial);
  }
  else
  {
    MTP.addFilesystem(SD, "SD Card");
  }

  // Add backup file of previous run
  if (SD.exists("fastlog.bin"))
  {
    if (SD.exists("fastlog_old.bin"))
    {
       SD.remove("fastlog_old.bin");
    }
    SD.rename("fastlog.bin", "fastlog_old.bin");
  }
 
  if (!file.open("fastlog.bin", O_RDWR | O_CREAT | O_TRUNC))
  {
    Serial.println("Failed to open file.");
    return;
  }
  else
  {
    Serial.println("File opened.");
  }

  // Pre-allocate contiguous sectors on the physical flash memory
  Serial.print("Pre-allocating space... ");
  if (!file.preAllocate(FILE_SIZE))
  {
    Serial.println("Allocation failed!");
    return;
  }
 
  rb.begin(&file);
  Serial.println("\nSystem ready. Logging starting.");

  // Start the hardware timer to fire exactly every 10 microseconds (100 kHz)
  samplingTimer.begin(samplingISR, 10);

  start_time = millis();
}

void loop()
{
  // Check for disaster metrics
  if (bufferOverflowed)
  {
    samplingTimer.end(); // Emergency stop
    file.close();
    Serial.println("\n[CRITICAL ERROR]: Buffer Overflow! SD card write stalled too long.");
    while (1)
    {
      MTP.loop();
    }
  }

  // Maximum sector block size optimized for the Teensy 4.1 controller
  size_t n = rb.bytesUsed();
 
  // Only execute a write sector command if there's data AND the SD card is idle
  if (n >= 128 && !file.isBusy())
  {
    // Write full chunks of 128-byte sectors at a time
    rb.writeOut(128);
  }

  // Print statistical metrics safely outside the fast stream
  static uint32_t lastPrint = 0;
  if (millis() - lastPrint > 2000)
  {
    lastPrint = millis();
    Serial.printf("Logged total: %lu samples | Buffer free: %d bytes\n", totalSamplesLogged, rb.bytesFree());
  }

  // End condition, close SD file & eneter MTP loop
  if (millis() - start_time > 12000)
  {
    file.close();
    Serial.println("\nFinished sampling");

    while(1)
    {
      MTP.loop();
    }
  }
 
}

however, I just fed your code into ChatGPT and the response was interesting. Here's what it spit out. -
#include <Arduino.h>
#include <SD.h>
#include <MTP_Teensy.h>
#include "SdFat.h"
#include "RingBuf.h"

#define SD_CONFIG SdioConfig(FIFO_SDIO)

const size_t RING_BUF_SIZE = 128 * 1024;
const uint64_t FILE_SIZE = 500ULL * 1024ULL * 1024ULL;
const uint32_t LOG_TIME_MS = 12000;

struct DataPacket
{
float values[8];
};

FsFile file;
RingBuf<FsFile, RING_BUF_SIZE> rb;
IntervalTimer samplingTimer;

uint32_t start_time;

volatile uint32_t totalSamplesLogged = 0;
volatile bool bufferOverflowed = false;

void readSensors(DataPacket *packet)
{
for (int i = 0; i < 8; i++)
{
packet->values = 1.234f * (i + 1);
}
}

void samplingISR()
{
DataPacket packet;
readSensors(&packet);

rb.beginISR();

if (rb.bytesFree() >= sizeof(packet))
{
rb.write((const uint8_t *)&packet, sizeof(packet));
totalSamplesLogged++;
}
else
{
bufferOverflowed = true;
}

rb.endISR();
}

bool drainRingBuffer()
{
while (rb.bytesUsed() > 0)
{
if (!file.isBusy())
{
size_t n = rb.bytesUsed();

if (n > 512)
{
n = 512;
}

if (rb.writeOut(n) != n)
{
Serial.println("ERROR: final RingBuf writeOut() failed.");
return false;
}
}
}

return true;
}

void finishLogging()
{
samplingTimer.end();

Serial.println();
Serial.println("Stopping sampling...");

if (!drainRingBuffer())
{
Serial.println("ERROR: could not completely drain ring buffer.");
}

if (!file.truncate())
{
Serial.println("WARNING: file.truncate() failed.");
}

if (!file.sync())
{
Serial.println("WARNING: file.sync() failed.");
}

file.close();

Serial.printf("Finished sampling. Total samples: %lu\n",
(unsigned long)totalSamplesLogged);

Serial.println("Starting MTP...");
Serial.println("The SD card should now appear in Windows Explorer.");

MTP.begin();
MTP.addFilesystem(SD, "SD Card");

while (1)
{
MTP.loop();
}
}

void setup()
{
Serial.begin(9600);
while (!Serial && millis() < 4000)
{
}

Serial.print("Initializing SD... ");

if (!SD.sdfs.begin(SD_CONFIG))
{
SD.sdfs.initErrorHalt(&Serial);
}

Serial.println("OK");

if (SD.exists("fastlog.bin"))
{
if (SD.exists("fastlog_old.bin"))
{
SD.remove("fastlog_old.bin");
}

if (!SD.rename("fastlog.bin", "fastlog_old.bin"))
{
Serial.println("WARNING: Could not rename previous fastlog.bin");
}
}

if (!file.open("fastlog.bin", O_RDWR | O_CREAT | O_TRUNC))
{
Serial.println("Failed to open fastlog.bin");
while (1)
{
}
}

Serial.println("File opened.");

Serial.print("Pre-allocating 500 MB... ");

if (!file.preAllocate(FILE_SIZE))
{
Serial.println("FAILED");
file.close();

while (1)
{
}
}

Serial.println("OK");

rb.begin(&file);

totalSamplesLogged = 0;
bufferOverflowed = false;

Serial.println("System ready. Logging for 12 seconds...");

start_time = millis();

samplingTimer.begin(samplingISR, 10);
}

void loop()
{
if (bufferOverflowed)
{
samplingTimer.end();

Serial.println();
Serial.println("[CRITICAL ERROR] Ring buffer overflow.");

drainRingBuffer();

file.truncate();
file.sync();
file.close();

MTP.begin();
MTP.addFilesystem(SD, "SD Card");

while (1)
{
MTP.loop();
}
}

size_t n = rb.bytesUsed();

if (n >= 512 && !file.isBusy())
{
if (rb.writeOut(512) != 512)
{
Serial.println("ERROR: rb.writeOut(512) failed.");
bufferOverflowed = true;
}
}

static uint32_t lastPrint = 0;

if (millis() - lastPrint >= 2000)
{
lastPrint = millis();

Serial.printf(
"Logged total: %lu samples | Buffer used: %u bytes | Buffer free: %u bytes\n",
(unsigned long)totalSamplesLogged,
(unsigned int)rb.bytesUsed(),
(unsigned int)rb.bytesFree()
);
}

if (millis() - start_time >= LOG_TIME_MS)
{
finishLogging();
}
}
 
Back
Top