Display SD card as mass storage: Teensy 4.1

xyon1282

New member
Title^ I would like to use the teensy 4.1 as a mass storage device. Basically, I want when its plugged in, the host computer is allowed access to the sd card, similar to when plugging in a flash drive.

I havent been able to find any guides or much information related to such a thing, so I was hoping that someone within the community could point me in the right direction.

Thank you in advance to anyone who reads this and/or answers.
 
If you scan this large thread (https://forum.pjrc.com/threads/68139-Teensyduino-File-System-Integration-including-MTP-and-MSC), it talks about adding MTP support to the Teensy. Note, under my Linux system I haven't plugged in an auto file mounter, which means I can't access the files from regular programs. I can access the files on the Teensy via the file explorer utility program which knows about accessing remote files via MTP. There is likely a way to do the auto mounting, but I haven't looked into it detail. And Windows/MAC would likely have their own methods of dealing with MTP systems.

The main library that most people use to incorporate MTP support is at:


Here is a program that I tweaked from the example program. On a Teensy 4.1, it will bring up a MTP file system for 'sdio' for the SD card in the built-in slot, and 'sd1' if you have the audio adapter mounted and a SD card in the audio adapter. If you have a flash memory chip soldered to your Teensy, it will bring up files 'QSPI-NOR' or 'QSPI-NAND` depending on whether the flash memory uses NOR logic or NAND logic. If you have 1 or 2 PSram chips soldered to the Teensy, you can access the files via 'RAM1' or 'RAM2'. If you have a flash memory chip soldered to the audio adapter, you can access it via 'SPI-FLASH'. You can also access the file system in the normal Teensy program flash memory above the program with the name 'PROGRAM'. The program doesn't do anything, other that attach to MTP and let the MTP system handle file access.

Code:
// From .../MTP_t4.git/trunk/examples/mtp-basic/mtp-basic.ino

#include "SPI.h"
#include "SD.h"
#include "MTP.h"

#if defined(ARDUINO_TEENSY41)
// The Teensy 4.1 has two sets of solder pads underneath the Teensy that you
// can solder either 2 PSram chips, 1 flash memory chip, or 1 flash memory chip
// and a PSram chip.  The smaller flash chips tend to use NOR flash while the
// larger flash chips tend to use NAND flash.
#define USE_LFS_RAM			1	// T4.1 PSRAM (or RAM)
#define USE_LFS_QSPI_NOR		1	// T4.1 QSPI NOR flash (16MB)
#define USE_LFS_QSPI_NAND		1	// T4.1 QSPI NAND flash (128MB)

#else
#define USE_LFS_RAM			0	// T4.1 PSRAM (or RAM)
#define USE_LFS_QSPI_NOR		0	// T4.1 QSPI NOR flash (16MB)
#define USE_LFS_QSPI_NAND		0	// T4.1 QSPI NAND flash (128MB)
#endif

#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
// Teensy 4.0 and 4.1 can use the upper flash memory as a file system via
// LittleFS.  The top bytes (64K for Teensy 4.0 and 256K for Teensy 4.1) of
// this memory is reserved for EEPROM emulation and the LED blink restore
// program.  On the Teensy 4.0 you have 1 megabyte - 64K of flash for a
// filesystem.  On the Teensy 4.1, you have 7 megabytes - 256K of flash for the
// filesystem.
#define USE_LFS_PROGM			1	// T4.0/T4.1 Progam Flash
#else
#define USE_LFS_PROGM			0	// T4.0/T4.1 Progam Flash
#endif

#define USE_SD				1	// Use either the built-in micro SD card reader or the reader on pin 10 (audio adapter)
#define USE_LFS_SPI			1	// SPI Flash (soldered to the audio adapter or the built-in flash on the prop shield)

// If you use the audio adapter revision A through C on a Teensy LC, 3.2, 3.5,
// or 3.6, it needs to remap the MOSI/SCK pins because those pins (11 and 13)
// are also I2S pins used by the audio shield.  But the prop shield wants to
// use the normal definitions.  In general, I've used the prop shield mostly on
// the Teensy 3.2 and LC, while if I use it, I use the audio shield on the
// Teensy 3.5 or Teensy 3.6.
//
// The Teensy 4.0 and 4.1 uses the audio adapter revision D, and it uses the
// standard SPI pins.
#if defined(ARDUINO_TEENSY35) || defined(ARDUINO_TEENSY36)
#define USE_TEENSY3_AUDIO_SHIELD	1	// Whether to use the Audio shield MOSI/SCLK for Teensy 3.x processors
#else
#define USE_TEENSY3_AUDIO_SHIELD	0	// Use standard configuration
#endif

#if USE_EVENTS==1
  extern "C" int usb_init_events(void);
#else
  int usb_init_events(void) {}
#endif

#if USE_LFS_RAM==1 ||  USE_LFS_PROGM==1 || USE_LFS_QSPI_NOR==1 || USE_LFS_QSPI_NAND==1 || USE_LFS_SPI==1
  #include "LittleFS.h"
#endif

#if defined(__IMXRT1062__)
  // following only as long usb_mtp is not included in cores
  #if !__has_include("usb_mtp.h")
    #include "usb1_mtp.h"
  #endif
#else
  #ifndef BUILTIN_SDCARD 
    #define BUILTIN_SDCARD 254
  #endif
  void usb_mtp_configure(void) {}
#endif


/****  Start device specific change area  ****/
// SDClasses 
#if USE_SD==1
  // edit SPI to reflect your configuration
#if USE_TEENSY3_AUDIO_SHIELD
 // Teensy audio adapter for Teensy 3.2, 3.5, and 3.6 (revision A-C) needs to remap the MOSI and SCK pins
  #define SD_MOSI  7
  #define SD_MISO 12
  #define SD_SCK  14
#else
 // Standard Teensy configuration
  #define SD_MOSI 11
  #define SD_MISO 12
  #define SD_SCK  13
#endif

  #define SPI_SPEED SD_SCK_MHZ(33)				// adjust to sd card 

  #if defined (BUILTIN_SDCARD)
    const char *sd_str[]={"BUILTIN-SD", "AUDIO-SD"};		// edit to reflect your configuration
    const int cs[] = {BUILTIN_SDCARD, 10};			// edit to reflect your configuration
  #else
    const char *sd_str[]={"AUDIO-SD"};				// edit to reflect your configuration
    const int cs[] = {10};					// edit to reflect your configuration
  #endif
  const int nsd = sizeof(sd_str)/sizeof(const char *);

SDClass sdx[nsd];
#endif

//LittleFS classes
#if USE_LFS_RAM==1
  const char *lfs_ram_str[]	= {"RAM1","RAM2"};		// edit to reflect your configuration
  const int lfs_ram_size[]	= {2'000'000, 4'000'000};	// edit to reflect your configuration
  const int nfs_ram		= sizeof(lfs_ram_str)/sizeof(const char *);

  LittleFS_RAM ramfs[nfs_ram]; 
#endif

#if USE_LFS_QSPI_NOR==1
  const char *lfs_qspi_nor_str[]	= {"QSPI-NOR"};		// edit to reflect your configuration
  const int nfs_qspi_nor		= sizeof(lfs_qspi_nor_str)/sizeof(const char *);

  LittleFS_QSPIFlash qspifs_nor[nfs_qspi_nor]; 
#endif

#if USE_LFS_QSPI_NAND==1
  const char *lfs_qspi_nand_str[]	= {"QSPI-NAND"};	// edit to reflect your configuration
  const int nfs_qspi_nand		= sizeof(lfs_qspi_nand_str)/sizeof(const char *);

  LittleFS_QPINAND qspifs_nand[nfs_qspi_nand];
#endif

#if USE_LFS_PROGM==1
  const char *lfs_progm_str[]={"PROGRAM"};			// edit to reflect your configuration
  const int lfs_progm_size[] = {1'000'000};			// edit to reflect your configuration
  const int nfs_progm = sizeof(lfs_progm_str)/sizeof(const char *);

  LittleFS_Program progmfs[nfs_progm]; 
#endif

#if USE_LFS_SPI==1
  const char *lfs_spi_str[]={"SPI-FLASH"};			// edit to reflect your configuration
  const int lfs_cs[] = {6};					// edit to reflect your configuration
  const int nfs_spi = sizeof(lfs_spi_str)/sizeof(const char *);

LittleFS_SPIFlash spifs[nfs_spi];
#endif


MTPStorage_SD storage;
MTPD    mtpd(&storage);

void storage_configure()
{
  #if USE_SD==1
    #if defined SD_SCK
      SPI.setMOSI(SD_MOSI);
      SPI.setMISO(SD_MISO);
      SPI.setSCK(SD_SCK);
    #endif

    for(int ii=0; ii<nsd; ii++)
    { 
      #if defined(BUILTIN_SDCARD)
        if(cs[ii] == BUILTIN_SDCARD)
        {
          if(!sdx[ii].sdfs.begin(SdioConfig(FIFO_SDIO))) 
          { Serial.printf("SDIO Storage %d %d %s failed or missing",ii,cs[ii],sd_str[ii]);  Serial.println();
          }
          else
          {
            storage.addFilesystem(sdx[ii], sd_str[ii]);
            uint64_t totalSize = sdx[ii].totalSize();
            uint64_t usedSize  = sdx[ii].usedSize();
            Serial.printf("SDIO Storage %d %d %s ",ii,cs[ii],sd_str[ii]); 
            Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
          }
        }
        else if(cs[ii]<BUILTIN_SDCARD)
      #endif
      {
        pinMode(cs[ii],OUTPUT); digitalWriteFast(cs[ii],HIGH);
        if(!sdx[ii].sdfs.begin(SdSpiConfig(cs[ii], SHARED_SPI, SPI_SPEED))) 
        { Serial.printf("SD Storage %d %d %s failed or missing",ii,cs[ii],sd_str[ii]);  Serial.println();
        }
        else
        {
          storage.addFilesystem(sdx[ii], sd_str[ii]);
          uint64_t totalSize = sdx[ii].totalSize();
          uint64_t usedSize  = sdx[ii].usedSize();
          Serial.printf("SD Storage %d %d %s ",ii,cs[ii],sd_str[ii]); 
          Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
        }
      }
    }
    #endif

    #if USE_LFS_RAM==1
    for(int ii=0; ii<nfs_ram;ii++)
    {
      if(!ramfs[ii].begin(lfs_ram_size[ii])) 
      { Serial.printf("Ram Storage %d %s failed or missing",ii,lfs_ram_str[ii]); Serial.println();
      }
      else
      {
        storage.addFilesystem(ramfs[ii], lfs_ram_str[ii]);
        uint64_t totalSize = ramfs[ii].totalSize();
        uint64_t usedSize  = ramfs[ii].usedSize();
        Serial.printf("RAM Storage %d %s ",ii,lfs_ram_str[ii]); Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
      }
    }
    #endif

    #if USE_LFS_PROGM==1
    for(int ii=0; ii<nfs_progm;ii++)
    {
      if(!progmfs[ii].begin(lfs_progm_size[ii])) 
      { Serial.printf("Program Storage %d %s failed or missing",ii,lfs_progm_str[ii]); Serial.println();
      }
      else
      {
        storage.addFilesystem(progmfs[ii], lfs_progm_str[ii]);
        uint64_t totalSize = progmfs[ii].totalSize();
        uint64_t usedSize  = progmfs[ii].usedSize();
        Serial.printf("Program Storage %d %s ",ii,lfs_progm_str[ii]); Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
      }
    }
    #endif

    #if USE_LFS_QSPI_NOR==1
    for(int ii=0; ii<nfs_qspi_nor;ii++)
    {
      if(!qspifs_nor[ii].begin()) 
      { Serial.printf("QSPI Storage %d %s failed or missing",ii,lfs_qspi_nor_str[ii]); Serial.println();
      }
      else
      {
        storage.addFilesystem(qspifs_nor[ii], lfs_qspi_nor_str[ii]);
        uint64_t totalSize = qspifs_nor[ii].totalSize();
        uint64_t usedSize  = qspifs_nor[ii].usedSize();
        Serial.printf("QSPI NOR Storage %d %s ",ii,lfs_qspi_nor_str[ii]); Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
      }
    }
    #endif

    #if USE_LFS_QSPI_NAND==1
    for(int ii=0; ii<nfs_qspi_nand;ii++)
    {
      if(!qspifs_nand[ii].begin()) 
      { Serial.printf("QSPI Storage %d %s failed or missing",ii,lfs_qspi_nand_str[ii]); Serial.println();
      }
      else
      {
        storage.addFilesystem(qspifs_nand[ii], lfs_qspi_nand_str[ii]);
        uint64_t totalSize = qspifs_nand[ii].totalSize();
        uint64_t usedSize  = qspifs_nand[ii].usedSize();
        Serial.printf("QSPI NAND Storage %d %s ",ii,lfs_qspi_nand_str[ii]); Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
      }
    }
    #endif

    #if USE_LFS_SPI==1
    for(int ii=0; ii<nfs_spi;ii++)
    {
      if(!spifs[ii].begin(lfs_cs[ii])) 
      { Serial.printf("SPIFlash Storage %d %d %s failed or missing",ii,lfs_cs[ii],lfs_spi_str[ii]); Serial.println();
      }
      else
      {
        storage.addFilesystem(spifs[ii], lfs_spi_str[ii]);
        uint64_t totalSize = spifs[ii].totalSize();
        uint64_t usedSize  = spifs[ii].usedSize();
        Serial.printf("SPIFlash Storage %d %d %s ",ii,lfs_cs[ii],lfs_spi_str[ii]); Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
      }
    }
    #endif
}

void setup()
{ 
  while (!Serial && millis () < 3000)
    ;

  Serial.println("MTP_test");
  Serial.printf ("USE_LFS_RAM              = %d\n", USE_LFS_RAM);
  Serial.printf ("USE_LFS_QSPI_NOR         = %d\n", USE_LFS_QSPI_NOR);
  Serial.printf ("USE_LFS_QSPI_NAND        = %d\n", USE_LFS_QSPI_NAND);
  Serial.printf ("USE_LFS_PROGM            = %d\n", USE_LFS_PROGM);
  Serial.printf ("USE_SD                   = %d\n", USE_SD);
  Serial.printf ("USE_LFS_SPI              = %d\n", USE_LFS_SPI);
  Serial.printf ("USE_TEENSY3_AUDIO_SHIELD = %d\n", USE_TEENSY3_AUDIO_SHIELD);
  Serial.println ("");

  #if USE_EVENTS==1
    usb_init_events();
  #endif

  #if !__has_include("usb_mtp.h")
    usb_mtp_configure();
  #endif
  storage_configure();

}

void loop()
{ 
  mtpd.loop();

#if USE_EVENTS==1
  if(Serial.available())
  {
    char ch=Serial.read();
    Serial.println(ch);
    if(ch=='r') 
    {
      Serial.println("Reset");
      mtpd.send_DeviceResetEvent();
    }
  }
#endif
}
 
Thanks MichaelMeissner. I copied and pasted your code and it worked straight away!

One small thing - in Windows Explorer, the BUILTIN-SD capacity bar appears full red for me, despite the SD card being empty. I'm running a Teensy 4.1. Does this happen for you as well? Not a big deal honestly, just a little OCD on my part.

2023-10-10_12-07-11.png

Using Teensyduino 1.58.1, using SdFat Version 2 that came with 1.58.1, no other SdFat libraries. Using 32 GB microSDHC Fat32 formatted. MTP_t4 as linked above.
 
Last edited:
I fixed my issue. Problem was that sdx[ii].totalSize() and sdx[ii].usedSize() were returning 0.

In SD.h (teensyduino library) there is:

Code:
uint64_t usedSize() {
		if (!cardPreviouslyPresent) return (uint64_t)0;
		return (uint64_t)(sdfs.clusterCount() - sdfs.freeClusterCount())
		  * (uint64_t)sdfs.bytesPerCluster();
	}
	uint64_t totalSize() {
		if (!cardPreviouslyPresent) return (uint64_t)0;
		return (uint64_t)sdfs.clusterCount() * (uint64_t)sdfs.bytesPerCluster();
	}

I commented out the
Code:
if (!cardPreviouslyPresent) return (uint64_t)0;
statements and it now gets the total size and used size of the SD card.
 
@Berollos :
I made the same experience.
The boolean cardPreviouslyPresent is defined in SD.h and will be changed in SD.cpp but in the header file it behaves like a constant.

Code:
  // from MTP_t4\examples\mtp-test   approx. line 112 in original

        if(cs[ii] == BUILTIN_SDCARD)
        {
          //if(!sdx[ii].sdfs.begin(SdioConfig(FIFO_SDIO)))  ****  exchanged this  ****
          if(!sdx[ii].begin(cs[ii]))
          { Serial.printf("SDIO Storage %d %d %s failed or missing",ii,cs[ii],sd_str[ii]);  Serial.println();
          }
          else
          {
            storage.addFilesystem(sdx[ii], sd_str[ii]);
            uint64_t totalSize = sdx[ii].totalSize();
            uint64_t usedSize  = sdx[ii].usedSize();
            Serial.printf("SDIO Storage %d %d %s ",ii,cs[ii],sd_str[ii]);
            Serial.print(totalSize); Serial.print(" "); Serial.println(usedSize);
          }
        }

When I exchange the method for begin to the SDClass method it worked like expected.
No clue what is the reason ...

Edit :
Just made the experience when closing the Arduino I can not run the Teensy as an MTP device any longer with the Windows Explorer after reconnecting. It is simply not listed as a device.
Obviously I need the MTP connection via the USB which will be setup by the Arduino and opened Serial Monitor anyhow.
Does anybody has a hint what to do ? I dont want to open the Arduino to establish that MTP connection.
I think I will study the MTP to better understand how it works in detail.
 
Last edited:
Back
Top