// Example to demonstrate write latency for preallocated exFAT files. // I suggest you write a PC program to convert very large bin files. // // If an exFAT SD is required, the ExFatFormatter example will format // smaller cards with an exFAT file system. // // The maximum data rate will depend on the quality of your SD, // the size of the FIFO, and using dedicated SPI. #include "SdFat.h" #include "FreeStack.h" #include "ExFatLogger.h" //------------------------------------------------------------------------------ // This example was designed for exFAT but will support FAT16/FAT32. // Note: Uno will not support SD_FAT_TYPE = 3. // SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, // 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. #define SD_FAT_TYPE 2 //------------------------------------------------------------------------------ // Interval between data records in microseconds. // Try 250 with Teensy 3.6, Due, or STM32. // Try 2000 with AVR boards. // Try 4000 with SAMD Zero boards. const uint32_t LOG_INTERVAL_USEC = 2000; // Set USE_RTC nonzero for file timestamps. // RAM use will be marginal on Uno with RTClib. #define USE_RTC 0 #if USE_RTC #include "RTClib.h" #endif // USE_RTC // LED to light if overruns occur. #define ERROR_LED_PIN -1 /* Change the value of SD_CS_PIN if you are using SPI and your hardware does not use the default value, SS. Common values are: Arduino Ethernet shield: pin 4 Sparkfun SD shield: pin 8 Adafruit SD shields and modules: pin 10 */ // 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 // FIFO SIZE - 512 byte sectors. Modify for your board. #ifdef __AVR_ATmega328P__ // Use 512 bytes for 328 boards. #define FIFO_SIZE_SECTORS 1 #elif defined(__AVR__) // Use 2 KiB for other AVR boards. #define FIFO_SIZE_SECTORS 4 #else // __AVR_ATmega328P__ // Use 8 KiB for non-AVR boards. #define FIFO_SIZE_SECTORS 16 #endif // __AVR_ATmega328P__ // Preallocate 1GiB file. const uint32_t PREALLOCATE_SIZE_MiB = 1024UL; // Select the fastest interface. Assumes no other SPI devices. #if ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI) #else // ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI) #endif // ENABLE_DEDICATED_SPI // Save SRAM if 328. #ifdef __AVR_ATmega328P__ #include "MinimumSerial.h" MinimumSerial MinSerial; #define Serial MinSerial #endif // __AVR_ATmega328P__ //============================================================================== // Replace logRecord(), printRecord(), and ExFatLogger.h for your sensors. void logRecord(data_t* data, uint16_t overrun) { if (overrun) { // Add one since this record has no adc data. Could add overrun field. overrun++; data->adc[0] = 0X8000 | overrun; } else { for (size_t i = 0; i < ADC_COUNT; i++) { data->adc[i] = analogRead(i); } } } //------------------------------------------------------------------------------ void printRecord(Print* pr, data_t* data) { static uint32_t nr = 0; if (!data) { pr->print(F("LOG_INTERVAL_USEC,")); pr->println(LOG_INTERVAL_USEC); pr->print(F("rec#")); for (size_t i = 0; i < ADC_COUNT; i++) { pr->print(F(",adc")); pr->print(i); } pr->println(); nr = 0; return; } if (data->adc[0] & 0X8000) { uint16_t n = data->adc[0] & 0X7FFF; nr += n; pr->print(F("-1,")); pr->print(n); pr->println(F(",overuns")); } else { pr->print(nr++); for (size_t i = 0; i < ADC_COUNT; i++) { pr->write(','); pr->print(data->adc[i]); } pr->println(); } } //============================================================================== const uint64_t PREALLOCATE_SIZE = (uint64_t)PREALLOCATE_SIZE_MiB << 20; // Max length of file name including zero byte. #define FILE_NAME_DIM 40 // Max number of records to buffer while SD is busy. const size_t FIFO_DIM = 512*FIFO_SIZE_SECTORS/sizeof(data_t); #if SD_FAT_TYPE == 0 typedef SdFat sd_t; typedef File file_t; #elif SD_FAT_TYPE == 1 typedef SdFat32 sd_t; typedef File32 file_t; #elif SD_FAT_TYPE == 2 typedef SdExFat sd_t; typedef ExFile file_t; #elif SD_FAT_TYPE == 3 typedef SdFs sd_t; typedef FsFile file_t; #else // SD_FAT_TYPE #error Invalid SD_FAT_TYPE #endif // SD_FAT_TYPE sd_t sd; file_t binFile; file_t csvFile; // You may modify the filename. Digits before the dot are file versions. char binName[] = "ExFatLogger00.bin"; //------------------------------------------------------------------------------ #if USE_RTC RTC_DS1307 rtc; // Call back for file timestamps. Only called for file create and sync(). void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) { DateTime now = rtc.now(); // Return date using FS_DATE macro to format fields. *date = FS_DATE(now.year(), now.month(), now.day()); // Return time using FS_TIME macro to format fields. *time = FS_TIME(now.hour(), now.minute(), now.second()); // Return low time bits in units of 10 ms. *ms10 = now.second() & 1 ? 100 : 0; } #endif // USE_RTC //------------------------------------------------------------------------------ #define error(s) sd.errorHalt(&Serial, F(s)) #define dbgAssert(e) ((e) ? (void)0 : error("assert " #e)) //----------------------------------------------------------------------------- // Convert binary file to csv file. void binaryToCsv() { uint8_t lastPct = 0; uint32_t t0 = millis(); data_t binData[FIFO_DIM]; if (!binFile.seekSet(512)) { error("binFile.seek faile"); } uint32_t tPct = millis(); printRecord(&csvFile, nullptr); while (!Serial.available() && binFile.available()) { int nb = binFile.read(binData, sizeof(binData)); if (nb <= 0 ) { error("read binFile failed"); } size_t nr = nb/sizeof(data_t); for (size_t i = 0; i < nr; i++) { printRecord(&csvFile, &binData[i]); } if ((millis() - tPct) > 1000) { uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100); if (pct != lastPct) { tPct = millis(); lastPct = pct; Serial.print(pct, DEC); Serial.println('%'); csvFile.sync(); } } if (Serial.available()) { break; } } csvFile.close(); Serial.print(F("Done: ")); Serial.print(0.001*(millis() - t0)); Serial.println(F(" Seconds")); } //------------------------------------------------------------------------------- void createBinFile() { binFile.close(); while (sd.exists(binName)) { char* p = strchr(binName, '.'); if (!p) { error("no dot in filename"); } while (true) { p--; if (p < binName || *p < '0' || *p > '9') { error("Can't create file name"); } if (p[0] != '9') { p[0]++; break; } p[0] = '0'; } } if (!binFile.open(binName, O_RDWR | O_CREAT)) { error("open binName failed"); } Serial.println(binName); if (!binFile.preAllocate(PREALLOCATE_SIZE)) { error("preAllocate failed"); } Serial.print(F("preAllocated: ")); Serial.print(PREALLOCATE_SIZE_MiB); Serial.println(F(" MiB")); } //------------------------------------------------------------------------------- bool createCsvFile() { char csvName[FILE_NAME_DIM]; if (!binFile.isOpen()) { Serial.println(F("No current binary file")); return false; } // Create a new csvFile. binFile.getName(csvName, sizeof(csvName)); char* dot = strchr(csvName, '.'); if (!dot) { error("no dot in filename"); } strcpy(dot + 1, "csv"); if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) { error("open csvFile failed"); } serialClearInput(); Serial.print(F("Writing: ")); Serial.print(csvName); Serial.println(F(" - type any character to stop")); return true; } //------------------------------------------------------------------------------- void logData() { int32_t delta; // Jitter in log time. int32_t maxDelta = 0; uint32_t maxLogMicros = 0; uint32_t maxWriteMicros = 0; size_t maxFifoUse = 0; size_t fifoCount = 0; size_t fifoHead = 0; size_t fifoTail = 0; uint16_t overrun = 0; uint16_t maxOverrun = 0; uint32_t totalOverrun = 0; uint32_t fifoBuf[128*FIFO_SIZE_SECTORS]; data_t* fifoData = (data_t*)fifoBuf; // Write dummy sector to start multi-block write. dbgAssert(sizeof(fifoBuf) >= 512); memset(fifoBuf, 0, sizeof(fifoBuf)); if (binFile.write(fifoBuf, 512) != 512) { error("write first sector failed"); } serialClearInput(); Serial.println(F("Type any character to stop")); // Wait until SD is not busy. while (sd.card()->isBusy()) {} // Start time for log file. uint32_t m = millis(); // Time to log next record. uint32_t logTime = micros(); while (true) { // Time for next data record. logTime += LOG_INTERVAL_USEC; // Wait until time to log data. delta = micros() - logTime; if (delta > 0) { Serial.print(F("delta: ")); Serial.println(delta); error("Rate too fast"); } while (delta < 0) { delta = micros() - logTime; } if (fifoCount < FIFO_DIM) { uint32_t m = micros(); logRecord(fifoData + fifoHead, overrun); m = micros() - m; if (m > maxLogMicros) { maxLogMicros = m; } fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0; fifoCount++; if (overrun) { if (overrun > maxOverrun) { maxOverrun = overrun; } overrun = 0; } } else { totalOverrun++; overrun++; if (overrun > 0XFFF) { error("too many overruns"); } if (ERROR_LED_PIN >= 0) { digitalWrite(ERROR_LED_PIN, HIGH); } } // Save max jitter. if (delta > maxDelta) { maxDelta = delta; } // Write data if SD is not busy. if (!sd.card()->isBusy()) { size_t nw = fifoHead > fifoTail ? fifoCount : FIFO_DIM - fifoTail; // Limit write time by not writing more than 512 bytes. const size_t MAX_WRITE = 512/sizeof(data_t); if (nw > MAX_WRITE) nw = MAX_WRITE; size_t nb = nw*sizeof(data_t); uint32_t usec = micros(); if (nb != binFile.write(fifoData + fifoTail, nb)) { error("write binFile failed"); } usec = micros() - usec; if (usec > maxWriteMicros) { maxWriteMicros = usec; } fifoTail = (fifoTail + nw) < FIFO_DIM ? fifoTail + nw : 0; if (fifoCount > maxFifoUse) { maxFifoUse = fifoCount; } fifoCount -= nw; if (Serial.available()) { break; } } } Serial.print(F("\nLog time: ")); Serial.print(0.001*(millis() - m)); Serial.println(F(" Seconds")); binFile.truncate(); binFile.sync(); Serial.print(("File size: ")); // Warning cast used for print since fileSize is uint64_t. Serial.print((uint32_t)binFile.fileSize()); Serial.println(F(" bytes")); Serial.print(F("totalOverrun: ")); Serial.println(totalOverrun); Serial.print(F("FIFO_DIM: ")); Serial.println(FIFO_DIM); Serial.print(F("maxFifoUse: ")); Serial.println(maxFifoUse); Serial.print(F("maxLogMicros: ")); Serial.println(maxLogMicros); Serial.print(F("maxWriteMicros: ")); Serial.println(maxWriteMicros); Serial.print(F("Log interval: ")); Serial.print(LOG_INTERVAL_USEC); Serial.print(F(" micros\nmaxDelta: ")); Serial.print(maxDelta); Serial.println(F(" micros")); } //------------------------------------------------------------------------------ void openBinFile() { char name[FILE_NAME_DIM]; serialClearInput(); Serial.println(F("Enter file name")); if (!serialReadLine(name, sizeof(name))) { return; } if (!sd.exists(name)) { Serial.println(name); Serial.println(F("File does not exist")); return; } binFile.close(); if (!binFile.open(name, O_RDONLY)) { Serial.println(name); Serial.println(F("open failed")); return; } Serial.println(F("File opened")); } //----------------------------------------------------------------------------- void printData() { if (!binFile.isOpen()) { Serial.println(F("No current binary file")); return; } // Skip first dummy sector. if (!binFile.seekSet(512)) { error("seek failed"); } serialClearInput(); Serial.println(F("type any character to stop\n")); delay(1000); printRecord(&Serial, nullptr); while (binFile.available() && !Serial.available()) { data_t record; if (binFile.read(&record, sizeof(data_t)) != sizeof(data_t)) { error("read binFile failed"); } printRecord(&Serial, &record); } } //------------------------------------------------------------------------------ void printUnusedStack() { #if HAS_UNUSED_STACK Serial.print(F("\nUnused stack: ")); Serial.println(UnusedStack()); #endif // HAS_UNUSED_STACK } //------------------------------------------------------------------------------ void serialClearInput() { do { delay(10); } while (Serial.read() >= 0); } //------------------------------------------------------------------------------ bool serialReadLine(char* str, size_t size) { size_t n = 0; while(!Serial.available()) { yield(); } while (true) { int c = Serial.read(); if (c < ' ') break; str[n++] = c; if (n >= size) { Serial.println(F("input too long")); return false; } uint32_t m = millis(); while (!Serial.available() && (millis() - m) < 100){} if (!Serial.available()) break; } str[n] = 0; return true; } //------------------------------------------------------------------------------ void testSensor() { const uint32_t interval = 200000; int32_t diff; data_t data; serialClearInput(); Serial.println(F("\nTesting - type any character to stop\n")); delay(1000); printRecord(&Serial, nullptr); uint32_t m = micros(); while (!Serial.available()) { m += interval; do { diff = m - micros(); } while (diff > 0); logRecord(&data, 0); printRecord(&Serial, &data); } } //------------------------------------------------------------------------------ void setup() { if (ERROR_LED_PIN >= 0) { pinMode(ERROR_LED_PIN, OUTPUT); digitalWrite(ERROR_LED_PIN, HIGH); } Serial.begin(9600); // Wait for USB Serial while (!Serial) { SysCall::yield(); } delay(1000); Serial.println(F("Type any character to begin")); while (!Serial.available()) { yield(); } FillStack(); #if !ENABLE_DEDICATED_SPI Serial.println(F( "\nFor best performance edit SdFatConfig.h\n" "and set ENABLE_DEDICATED_SPI nonzero")); #endif // !ENABLE_DEDICATED_SPI Serial.print(FIFO_DIM); Serial.println(F(" FIFO entries will be used.")); // Initialize SD. if (!sd.begin(SD_CONFIG)) { sd.initErrorHalt(&Serial); } #if USE_RTC if (!rtc.begin()) { error("rtc.begin failed"); } if (!rtc.isrunning()) { // Set RTC to sketch compile date & time. // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); error("RTC is NOT running!"); } // Set callback FsDateTime::setCallback(dateTime); #endif // USE_RTC } //------------------------------------------------------------------------------ void loop() { printUnusedStack(); // Read any Serial data. serialClearInput(); if (ERROR_LED_PIN >= 0) { digitalWrite(ERROR_LED_PIN, LOW); } Serial.println(); Serial.println(F("type: ")); Serial.println(F("b - open existing bin file")); Serial.println(F("c - convert file to csv")); Serial.println(F("l - list files")); Serial.println(F("p - print data to Serial")); Serial.println(F("r - record data")); Serial.println(F("t - test without logging")); while(!Serial.available()) { SysCall::yield(); } char c = tolower(Serial.read()); Serial.println(); if (c == 'b') { openBinFile(); } else if (c == 'c') { if (createCsvFile()) { binaryToCsv(); } } else if (c == 'l') { Serial.println(F("ls:")); sd.ls(&Serial, LS_DATE | LS_SIZE); } else if (c == 'p') { printData(); } else if (c == 'r') { createBinFile(); logData(); } else if (c == 't') { testSensor(); } else { Serial.println(F("Invalid entry")); } }