// Teensy 4.1 SDIO logger with SdFat RingBuf
#include <SdFat.h>
#include <RingBuf.h>
//******************************************************************************
// configuration
//******************************************************************************
#define SD_CONFIG (SdioConfig(FIFO_SDIO)) // use Teensy SDIO
#define LOG_TIME_S (60) // s
#define LOG_FREQ_HZ (50000) // Hz
#define ADC_N_CHAN (12) // number of channels
#define RECORD_SIZE (12 + ADC_N_CHAN*6) // bytes/record
#define BYTES_PER_S (RECORD_SIZE * LOG_FREQ_HZ) // bytes/sec
#define LOG_FILE_SIZE (BYTES_PER_S * LOG_TIME_S) // total bytes
// RingBuf size = 50 ms of data for BYTES_PER_S data rate (minimum 1024)
#define BYTES_PER_50MS (BYTES_PER_S/20) // 50-ms buffer size
#define RB_MIN (1024) // minimum buffer_size
#define RINGBUF_SIZE (BYTES_PER_50MS < RB_MIN ? RB_MIN : BYTES_PER_50MS)
//******************************************************************************
// SdFat RingBuf template class <file type, buffer size>
//******************************************************************************
RingBuf<FsFile,RINGBUF_SIZE> rb; // ISR --> RingBuf --> loop --> SD file
//******************************************************************************
// global variables
//******************************************************************************
IntervalTimer timer; // IntervalTimer for ISR-level writes
SdFs sd; // SdFat type
FsFile file; // SdFat file type
size_t rbMaxUsed = 0; // RingBuf max bytes (useful diagnostic)
uint32_t error; // RingBuf/file error code
uint32_t timer_count=0; // timer interrupt executions
uint32_t timer_bytes=0; // timer interrupt bytes written
char format[128]; // sprintf format string
//******************************************************************************
// IntervalTimer handler -- write simulated ADC data to RingBuf
//******************************************************************************
void timer_handler( void )
{
static char s[RECORD_SIZE];
static uint32_t start_us;
uint32_t now_us = micros();
if (timer_count == 0)
start_us = now_us;
// write up to 16 values to str (as determined by format)
uint16_t value = now_us % 4096;
int n = sprintf( s, format, (now_us - start_us)/1E6,
value, value, value, value, value, value, value, value,
value, value, value, value, value, value, value, value
);
// write n bytes from string s to RingBuf
if (rb.memcpyIn( s, n ) < n) // if write NOT complete
error = 1; // set global error variable
timer_count++; // increment count
timer_bytes += n; // update bytes
}
//******************************************************************************
// setup()
//******************************************************************************
void setup()
{
Serial.begin(9600);
while (!Serial && millis() < 3000) {}
Serial.printf( "Teensyduino version %1lu\n", TEENSYDUINO );
Serial.printf( "SdFat version %s\n", SD_FAT_VERSION_STR );
// Initialize the SD, print message and stop if error
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
// create format string
int n = sprintf( format, "%s", "%1.6lf" );
for (int i=0; i<ADC_N_CHAN; i++) {
n += sprintf( &format[n], "%s", ",%1hu" );
}
n += sprintf( &format[n], "%s", "\n" );
Serial.print( format );
}
//******************************************************************************
// open file, preAllocate, init RingBuf, start timer, log data, print results
//******************************************************************************
void loop()
{
// clear input buffer
while (Serial.available()) { Serial.read(); }
// List files in SD root.
Serial.println( "Files in root directory:" );
sd.ls(LS_DATE | LS_SIZE);
Serial.println( "Type any character to begin" );
while (!Serial.available()) {}
Serial.printf( "Log %1lu channels for %1lu seconds at %1lu Hz\n",
ADC_N_CHAN, LOG_TIME_S, LOG_FREQ_HZ );
Serial.printf( "Pre-allocate file %1lu bytes\n", LOG_FILE_SIZE );
Serial.printf( "RingBuf %1lu bytes\n", RINGBUF_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);
}
uint32_t timer_period_us = 1E6 / LOG_FREQ_HZ;
Serial.printf( "Start timer (period = %1lu us)\n", timer_period_us );
timer_count = 0;
timer_bytes = 0;
timer.begin( timer_handler, timer_period_us );
elapsedMillis ms = 0;
while (ms/1000 < LOG_TIME_S) {
// number of bytes in RingBuf
size_t n = rb.bytesUsed();
if (n > rbMaxUsed) {
rbMaxUsed = n;
}
// if RingBuf >= 512 (one sector), write 512 bytes to file
if (n >= 512 && !file.isBusy()) {
if (512 != rb.writeOut(512)) {
error = 1; // write error
}
}
}
// stop timer, write any remaining RingBuf data to file, truncate file
timer.end();
rb.sync();
file.truncate();
Serial.printf("fileSize: ");
Serial.println((uint32_t)file.fileSize());
Serial.printf( "Runtime %1.3lf s\n", ms/1E3 );
Serial.printf( "%1lu timer interrupts (%1lu bytes)\n", timer_count, timer_bytes );
Serial.printf( "RingBuf max %1lu bytes\n", (uint32_t)rbMaxUsed );
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;
}
file.rewind();
// Print first 10 lines of file
for (uint8_t n = 0; n < 10 && file.available();) {
int c = file.read();
if (c < 0) {
break;
}
Serial.write(c);
if (c == '\n') n++;
}
// close file
file.close();
}