SD card interface: Teensy 4.1 vs Teensy 4.0 + Audio adapter

Status
Not open for further replies.

even_rats

Member
Hello,

I'm looking to build an audio project that reads and writes audio to/from an SD card, and requires 8 analog inputs (pots to control volume etc) as well as audio I/O. As someone diving into this world for the first time, I initially chose the Teensy 4.1 because of its built in SD card interface and plentiful I/O; I would also get the compatible Audio Adapter for it.

But then I noticed that the Audio Adapter has an SD card slot of its own! So my questions are:

  • Are there any disadvantages or differences to accessing the SD using a Teensy 4.0 with the Audio Adapter's SD card slot, vs using a 4.1's integrated SD slot?
  • Would I still have enough (8) analog inputs available with the 4.0, after the audio adapter is connected?
  • (out of curiosity) if I connected a Teensy 4.1 to an audio adapter, could I use both SD card slots at the same time?

Thank you,
A music nerd
 
  • Are there any disadvantages or differences to accessing the SD using a Teensy 4.0 with the Audio Adapter's SD card slot, vs using a 4.1's integrated SD slot?
  • Would I still have enough (8) analog inputs available with the 4.0, after the audio adapter is connected?
  • (out of curiosity) if I connected a Teensy 4.1 to an audio adapter, could I use both SD card slots at the same time?
Access to T4.1 Sd card is faster (it uses 4-bit parallel SDIO protocol, while AudioAdapter user 1-bit SPI prorocol)
Analog input on Teensy are independent from Audio I/O of Audio Adapter
yes you can use both SD Cards (T4.1 and AudioAdapter). only access speed is different (see above)
 
I've tested both, and for reading a single file:
  • Teensy 4.0 using audio shield: 0.9 Mbyte/second;
  • Teensy 4.0 using SD card via soldered pins: 1.2 Mbyte/second;
  • Teensy 4.1 using built-in micro SD card reader: 1.2 Mbyte/second;
  • Teensy 3.2, 3.5, and 3.6 have roughly the same speed using the audio shield as the Teensy 4.0; (and)
  • Teensy 3.5 and 3.6 have roughly the same speed using the built-in micro SD card reader.

This is using the standard '<SD.h>' library. I haven't tested other libraries.
 
Very helpful replies. Good to know the read speed in practice. Thanks!


Any idea if the write speeds would be similar?

I didn't test it. My main interest is in having pre-recorded sounds and playing them. The test I used was the example Audio -> HardwareTesting -> SDCardTest, with the appropriate modifications for audio and built-in sd cards.

I was disappointed that the built-in sd reader wasn't faster.

I am hoping that when Paul's rewrite of the file system support goes into TeensyDunio 1.54 will allow for easier support of flash memory (either on the audio shield, or soldered onto the Teensy 4.1).
 
I am hoping that when Paul's rewrite of the file system support goes into TeensyDunio 1.54 will allow for easier support of flash memory (either on the audio shield, or soldered onto the Teensy 4.1).

In the short term, I'm aiming for a much lower goal... just to make the File class from SD into a base class which can be shared among libraries.

Years ago I started a redesign of the SD library. It only supports SPI and only on Teensy 3.x, and so far only offers read-only access to the card. Eventually I will revisit that, or perhaps merge Bill's work on SdFat. But that is a much longer term goal. It's a huge project. It also didn't make as much sense on Teensy 3.0 to 3.2, where we have much less RAM for caching. SD cards have high command latency, so the trick to good performance is many-sector access and lots of caching.
 
In the short term, I'm aiming for a much lower goal... just to make the File class from SD into a base class which can be shared among libraries.
As a coworker of mine used to say before he retired "It's a journey, not an event".

If would be nice if we could use a common FILE base class among the filesystems, and for WAV/RAW/MP3/etc. files. But I realize it likely will take some amount of time to get there.

And of course, real life has a tendency to get in the way of things. I need to get back to programming Teensies.
 
In the short term, I'm aiming for a much lower goal... just to make the File class from SD into a base class which can be shared among libraries.

Would it make sense, to define right now a virtual base class that implements a user interface? Next we could try to think about the different implementations.
Not sure if the old Arduino interface is still the right way. what about Unix style API?
Anyhow, someone must IMHO indicate the road, we will then run along.
 
I'm moving back around to this on my current project and would be happy to contribute however I can as soon as there is some sort of marching orders. My personal goals are some level of compatibility between SD and NAND storage and a non-blocking read call.
 
I've tested both, and for reading a single file:
  • Teensy 4.0 using audio shield: 0.9 Mbyte/second;
  • Teensy 4.0 using SD card via soldered pins: 1.2 Mbyte/second;
  • Teensy 4.1 using built-in micro SD card reader: 1.2 Mbyte/second;
  • Teensy 3.2, 3.5, and 3.6 have roughly the same speed using the audio shield as the Teensy 4.0; (and)
  • Teensy 3.5 and 3.6 have roughly the same speed using the built-in micro SD card reader.

This is using the standard '<SD.h>' library. I haven't tested other libraries.

If read/write performance is an issue, you should be using Bill Greiman's SDFat 2.0b and the built-in SD card on the T3.6 or T4.1. I've found SDFat to be very stable and with MUCH higher performance when you read and write in blocks of 4KB. I think the speed increase is probably due to SDFat's use of multi-sector reads and writes.

Code:
SdFs  32GB Sandisk ExFat   T3.6 180MHz
Write                       15.645 MB/Sec
Random read 4K blocks        4.096 MB/Sec
Sequential read             19.527 MB/Sec

SdFs  32GB Sandisk FAT32   T3.6 180MHz
Write                       13.224 MB/Sec
Random Read 4K blocks        4.410 MB/Sec
Sequential read             19.523 MB/Sec



SdFs  32GB Sandisk ExFat   T4.1 600MHz
Write                       17.494 MB/Sec
Random read 4K blocks        4.681 MB/Sec
Sequential read             22.864 MB/Sec

SdFs  32GB Sandisk FAT32    T4.1 600MHz
Write                       17.172 MB/Sec
Random Read                  5.079 MB/Sec
Sequential read             22.859 MB/Sec

Note that the speeds don't increase much with the T4.1, despite its much higher CPU clock speed. That indicates that the speed limit is probably imposed by either the SD card or the SDIO hardware. I have found that there are significant differences between SD cards, so you probably need to do your own tests with the cards you plan to use.

Here's the benchmark program I used to collect the data:
Code:
/*********************************************************************
 * Benchmark test for SD FAT32 sequential and random access
 * M. Borgerson 8/24/2020
 ********************************************************************/
#include "SdFat.h"
#include "sdios.h"
#include <TimeLib.h>

// NOTE:  I used SdFs because it will use either FAT32 or ExFAT, depending on the format of the disk
SdFs sd;
FsFile BenchFile;


#define SD_CONFIG SdioConfig(FIFO_SDIO)

// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else  // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif  // SDCARD_SS_PIN


const char compileTime [] = "SdFs Benchmark  Compiled on " __DATE__ " " __TIME__;

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

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


void setup() {
  // put your setup code here, to run once:

  while (!Serial) {}
  Serial.begin(9600);
  delay(1000);

  Serial.println(compileTime); 
  if (!sd.begin(SD_CONFIG)) {
    Serial.println("\nSDIO Card initialization failed.\n");
  } else  Serial.println("SDIO initialization done.");

  if (sd.fatType() == FAT_TYPE_EXFAT) {
    Serial.println("Type is exFAT");
  } else {
    Serial.printf("Type is FAT%d\n", int16_t(sd.fatType()));
  }
  // set date time callback function  so file gets a good date
  SdFile::dateTimeCallback(dateTime);
  setSyncProvider(getTeensy3Time);
}

char fname[] = "btest.dat";
void loop() {
  // put your main code here, to run repeatedly:
  char ch;
  if (Serial.available()) {
    ch = Serial.read();
    if (ch == 'w')  WriteBenchFile(fname);
    if (ch == 's')  SeqReadBenchFile(fname);
    if (ch == 'r')  RandReadBenchFile(fname);
    if (ch == 'd')  sd.ls(LS_SIZE | LS_DATE | LS_R);
  }
}

#define RBUFFSIZE 4096
// Write a 256MB  contiguous file for benchmark testing
//  
// data in the file will be random data from stack area
void WriteBenchFile(char *filename) {
  uint64_t alloclength;
  uint32_t i, num;
  FsFile benchFile;
  float seconds;
  uint32_t startmilli, dmilli, blockmax, mbytes;
  unsigned char benchbuff[RBUFFSIZE];

  Serial.printf("\n\nWriting 256MB Benchmark file.\n");
  // Open the file
  if (!benchFile.open(filename,  O_RDWR | O_CREAT | O_TRUNC)) {
    Serial.printf("Unable to open <%s> for writing.", filename);
    return;
  }


  // now write the data in blocks of RBUFFSIZE --   65536 blocks
  // send out a message every 10MB or 256 blocks
  blockmax = 65536;
  mbytes = 0;
  startmilli = millis();
  for(i=0; i<blockmax; i++){
    benchFile.write(&benchbuff, 4096);
    if((i%2560) == 0){
      Serial.printf("%lu MBytes\n",mbytes);
      mbytes+= 10;
    }
  }
  dmilli = millis()-startmilli;

  benchFile.close();
  seconds = (float)dmilli/1000.0;
  Serial.printf("\nWriting 256MBytes took %4.2f seconds", (float)dmilli/1000.0);
  Serial.printf(" For an average rate of %6.3f Mbytes/second\n", (256)/seconds);
}


// Read 40 MBytes from benchFile and keep track of max and average read time

#define BUFFSTOREAD 10240   // 40MByte read
void SeqReadBenchFile(const char *filename) {
  uint16_t idx, numread;
  FsFile benchFile;
  uint8_t rbuffer[RBUFFSIZE];
  uint32_t startmicro, dmicro, maxmicro, gt500us;
  float microsum;

  if (!benchFile.open(filename, O_READ)) {
    Serial.printf("\nCould not open <%s> for reading.", filename);
    return;
  }
  startmicro = micros();  // save starting time
  Serial.printf("\n\nReading %lu sequential buffers of %u bytes\n",BUFFSTOREAD, RBUFFSIZE);
  maxmicro = 0; 
  microsum = 0.0;
  gt500us = 0;
  for(idx = 0; idx<BUFFSTOREAD; idx++){
    startmicro = micros();
    numread = benchFile.read(&rbuffer, RBUFFSIZE);
    dmicro = micros()-startmicro;
    if(dmicro > maxmicro) maxmicro = dmicro;
    if(dmicro > 500) gt500us++;
    microsum += dmicro;

  }
  Serial.printf("Buffer read times: average = %4.2f usec   Maximum = %lu usec    >500uSec: %lu\n",
                              microsum/BUFFSTOREAD, maxmicro, gt500us);
  Serial.printf("Average Read Rate = %6.3f MBytes/second)", (float)(10240 *4096)/(float)microsum);
  
  benchFile.close();
  Serial.println();

}

void RandReadBenchFile(const char *filename) {

  uint16_t idx, numread;
  FsFile benchFile;
  uint8_t rbuffer[RBUFFSIZE];
  uint32_t startmicro, dmicro, maxmicro, gt500us, fpos;
  float microsum;

  if (!benchFile.open(filename, O_READ)) {
    Serial.printf("\nCould not open <%s> for reading.", filename);
    return;
  }
  startmicro = micros();  // save starting time
  Serial.printf("\n\nReading %lu random buffers of %u bytes\n",BUFFSTOREAD, RBUFFSIZE);
  maxmicro = 0; 
  microsum = 0.0;
  gt500us = 0;
  for(idx = 0; idx<BUFFSTOREAD; idx++){
    startmicro = micros();
    fpos = random(0,5000)*RBUFFSIZE;
    benchFile.seek(fpos);
    numread = benchFile.read(&rbuffer, RBUFFSIZE);
    dmicro = micros()-startmicro;
    if(dmicro > maxmicro) maxmicro = dmicro;
    if(dmicro > 999) gt500us++;
    microsum += dmicro;

  }
  Serial.printf("Buffer read times: average = %4.2f usec   Maximum = %lu usec    >999uSec: %lu\n",
                              microsum/BUFFSTOREAD, maxmicro, gt500us);
  Serial.printf("Average Read Rate = %6.3f MBytes/second)", (float)(10240 *4096)/(float)microsum);
  benchFile.close();
  Serial.println();

}

//------------------------------------------------------------------------------
/*
   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());
}
 
If read/write performance is an issue, you should be using Bill Greiman's SDFat 2.0b and the built-in SD card on the T3.6 or T4.1. I've found SDFat to be very stable and with MUCH higher performance when you read and write in blocks of 4KB. I think the speed increase is probably due to SDFat's use of multi-sector reads and writes.
Hi @mborgerson,
I am new to the Teensy world after picking up a 4.1 recently but have been around Ardu, linux, unix, etc. for a long time. I am interested in running the SD bench you listed, but am having some issues getting it to comple it with AurduinoIDE. Curious if that version is for a different board or if you used a different dev environment? I'm looking to do all sorts of good stuff that could never be done on the slow boards, so SD speed is definitely on my list of "yes, I need that".

Regards,
Karl
 
Last edited:
SD changes are coming in TeensyDuino 1.54 ... SdFAT-beta is being integrated to replace the prior SD system.

It is in the recent Beta release - not sure when the next beta release will show and if it has changes. But worth watching and working with perhaps.
 
SD changes are coming in TeensyDuino 1.54 ... SdFAT-beta is being integrated to replace the prior SD system.

It is in the recent Beta release - not sure when the next beta release will show and if it has changes. But worth watching and working with perhaps.

Good to know. Where are the beta releases stored?

EDIT: NVM Found them
 
Downloaded and installed 1.54 beta4, which fixed the compile errors on the SD test code above.

Quick test on an older Samsung Evo 32GB

Thanks for the tip!
Code:
...
230 MBytes
240 MBytes
250 MBytes

Writing 256MBytes took 13.16 seconds For an average rate of 19.454 Mbytes/second


Reading 10240 sequential buffers of 4096 bytes
Buffer read times: average = 179.96 usec   Maximum = 1232 usec    >500uSec: 15
Average Read Rate = 22.760 MBytes/second)


Reading 10240 random buffers of 4096 bytes
Buffer read times: average = 859.79 usec   Maximum = 3346 usec    >999uSec: 6
Average Read Rate =  4.764 MBytes/second)


2020-11-10 20:55  268435456 btest.dat
 
Hi @mborgerson,
I am new to the Teensy world after picking up a 4.1 recently but have been around Ardu, linux, unix, etc. for a long time. I am interested in running the SD bench you listed, but am having some issues getting it to comple it with AurduinoIDE. Curious if that version is for a different board or if you used a different dev environment?

Regards,
Karl

The test program does require that you have the SDFAT 2.0beta in your Arduino library folder. As others have pointed out, the functionality of the SDFat 2.0 is being integrated into SD in the new Teensyduino 1.54 beta. I just started working with that beta yesterday, so I've not yet tested my benchmark with it.
 
Status
Not open for further replies.
Back
Top