// Test Teensy SDIO with write busy in a data logger demo.
//
// The driver writes to the uSDHC controller's FIFO then returns while the
// controller writes the data to the SD. The first sector puts the controller
// in write mode and takes about 11 usec on a Teensy 4.1. About 5 usec is
// required to write a sector when the controller is in write mode.
#include <SdFat.h>
#include <RingBuf.h>
//******************************************************************************
// global variables
//******************************************************************************
#define SD_CONFIG (SdioConfig(FIFO_SDIO)) // use Teensy SDIO
#define LOGGING_TIME_S (10) // s
#define DATA_RATE_BPS ((2)*(1024*1024)) // bytes/s
#define LOG_FILE_SIZE (DATA_RATE_BPS * LOGGING_TIME_S)// total bytes
#define RING_BUF_SIZE (DATA_RATE_BPS / 20) // 50-ms buffer at BPS
#define BUFLEN (256) // bytes per write
#define C2US(c) ((c)*(1E6/F_CPU)) // CPU cycles to us
//******************************************************************************
// global variables
//******************************************************************************
IntervalTimer dataTimer; // IntervalTimer for ISR-level writes
RingBuf<FsFile,RING_BUF_SIZE> rb; // ISR --> RingBuf --> loop --> SD file
SdFs sd; // SdFat type
FsFile file; // SdFat file type
size_t rbMaxUsed = 0; // RingBuf max bytes (useful diagnostic)
char buf[BUFLEN]; // test buffer
uint32_t error; // RingBuf/file error code
//******************************************************************************
// IntervalTimer callback -- write BUFLEN bytes to RingBuf
//******************************************************************************
void dataTimerCallback( void )
{
#if (SD_FAT_VERSION == 20102) // #if SdFat 2.1.1
rb.memcpyIn( buf, BUFLEN ); // write to RingBuf via rb.memcpyIn()
#elif (SD_FAT_VERSION >= 20202) // #elif SdFat >= 2.2.0
rb.beginISR(); // begin interrupt access
rb.write( buf, BUFLEN ); // write to RingBuf via rb.write()
rb.endISR(); // end interrupt access
#endif // #endif
if (rb.getWriteError()) // if write error occurred
error = 1; // set global error code
}
//******************************************************************************
// setup()
//******************************************************************************
void setup()
{
Serial.begin(9600);
while (!Serial && millis() < 3000) {}
Serial.printf( "%s %s\n", __DATE__, __TIME__ );
Serial.printf( "Teensy %s\n",
#if defined(ARDUINO_TEENSY35)
"3.5" );
#elif defined(ARDUINO_TEENSY41)
"4.1" );
#endif
Serial.printf( "Teensyduino version %1lu\n", TEENSYDUINO );
Serial.printf( "SdFat version %s\n", SD_FAT_VERSION_STR );
// Initialize the SD.
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
// these 2 lines are necessary to enable cycle counting on T3.x
#if (defined(KINETISL) || defined(KINETISK)) // if Teensy LC or 3.x
ARM_DEMCR |= ARM_DEMCR_TRCENA; // enable debug/trace
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; // enable cycle counter
#endif
}
//******************************************************************************
// loop() open file, preAllocate, init RingBuf, log data, print results/stats
//******************************************************************************
void loop()
{
while (Serial.available()) { Serial.read(); }
Serial.println( "Type any character to begin" );
while (!Serial.available()) {}
Serial.printf( "Log for %1lu seconds at %1.2f MB/s in %1lu byte chunks\n",
LOGGING_TIME_S, (float)(DATA_RATE_BPS/(1024*1024.0)), BUFLEN );
Serial.printf( "Pre-allocated file %1lu bytes\n", LOG_FILE_SIZE );
Serial.printf( "RingBuf %1lu bytes\n", RING_BUF_SIZE );
// Open or create file - truncate existing file.
if (!file.open( "logfile.txt", O_RDWR | O_CREAT | O_TRUNC )) {
Serial.println( "open failed\n" );
}
// File must be pre-allocated to avoid huge delays searching for free clusters.
else if (!file.preAllocate( LOG_FILE_SIZE )) {
Serial.println( "preAllocate failed\n" );
file.close();
}
// init the file and RingBuf
else {
rb.begin(&file);
}
// Init data buffer with random data
randomSeed( micros() );
for (int i=0; i<BUFLEN; i++)
buf[i] = 0x30+random( 10 );
buf[BUFLEN-1] = '\n';
uint32_t timer_period_us = 1E6 * BUFLEN / DATA_RATE_BPS;
Serial.printf( "Start dataTimer (period = %1lu us)\n", timer_period_us );
dataTimer.begin( dataTimerCallback, timer_period_us );
uint32_t count = 0, start_ms = millis(), start_busy = 0;
bool busy = false;
error = 0;
elapsedMillis ms = 0;
uint32_t sum_busy=0, min_busy=0xFFFFFFFF, max_busy=0;
uint32_t sum_write=0, min_write=0xFFFFFFFF, max_write=0;
while (error == 0 && millis() - start_ms < LOGGING_TIME_S*1000) {
if (ms >= 1000) { Serial.print( "." ); ms -= 1000; }
// number of bytes in RingBuf
size_t n = rb.bytesUsed();
if (n > rbMaxUsed) {
rbMaxUsed = n;
}
// bytes in RingBuf now will fit, but any more will exceed file size
if ((n + file.curPosition()) > (LOG_FILE_SIZE - BUFLEN/*_MAX*/)) {
error = 2; // file full
}
// write one sector (512 bytes) from RingBuf to file
// Not busy only allows one sector before possible busy wait
if (file.isBusy()) {
if (!busy) {
busy = true;
start_busy = ARM_DWT_CYCCNT;
}
}
else if (busy) {
busy = false;
uint32_t busy_cyc = ARM_DWT_CYCCNT - start_busy;
sum_busy += busy_cyc;
if (busy_cyc < min_busy) min_busy = busy_cyc;
if (busy_cyc > max_busy) max_busy = busy_cyc;
}
if (n >= 512 && !busy) {
uint32_t start_write = ARM_DWT_CYCCNT;
if (512 != rb.writeOut(512)) {
error = 1; // write error
}
uint32_t write_cyc = ARM_DWT_CYCCNT - start_write;
sum_write += write_cyc;
if (count > 0) {
if (write_cyc < min_write) min_write = write_cyc;
if (write_cyc > max_write) max_write = write_cyc;
}
count++;
}
}
uint32_t duration_ms = millis() - start_ms;
Serial.printf( "\nStop dataTimer\n" );
dataTimer.end();
double duration_s = duration_ms/1000.0;
double write_s = C2US(sum_write)/1E6;
double write_percent = 100*(write_s/duration_s);
Serial.printf( "%1lu writes in %1.3lf s (%1.3lf s writing to file = %1.3lf %c)\n",
count, duration_s, write_s, write_percent, '%' );
switch (error) {
case 0: Serial.printf( "No error\n" ); break;
case 1: Serial.printf( "Not enough space in RingBuf\n" ); break;
case 2: Serial.printf( "File is full\n" ); break;
case 3: Serial.printf( "Write from RingBuf failed" ); break;
default: Serial.printf( "Undefined error %1lu\n", error ); break;
}
// write any remaining RingBuf data to file
Serial.printf( "fileSize = %10lu before sync()\n", (uint32_t)file.fileSize() );
Serial.printf( "rb.bytesUsed = %10lu before sync()\n", (uint32_t)rb.bytesUsed() );
rb.sync();
// file and buffer stats
Serial.printf( "fileSize = %10lu after sync()\n", (uint32_t)file.fileSize() );
Serial.printf( "rb.bytesUsed = %10lu after sync()\n", (uint32_t)rb.bytesUsed() );
Serial.printf( "rbMaxUsed = %10lu\n", (uint32_t)rbMaxUsed );
Serial.printf( "min write us = %10.2lf\n", C2US(min_write) );
Serial.printf( "max write us = %10.2lf\n", C2US(max_write) );
Serial.printf( "min busy us = %10.0lf\n", C2US(min_busy) );
Serial.printf( "max busy us = %10.0lf\n", C2US(max_busy) );
// print first N line(s) of file.
int lines=0;
if (lines > 0) {
Serial.printf( "First %1d line(s) of file\n", lines );
file.truncate();
file.rewind();
}
for (int n=0; n < lines && file.available();) {
int c = file.read();
if (c < 0) break;
Serial.write(c);
if (c == '\n') n++;
}
// close file
file.close();
Serial.printf( "file.close()\n\n" );
}