ExFatLogger.ino 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. // Example to demonstrate write latency for preallocated exFAT files.
  2. // I suggest you write a PC program to convert very large bin files.
  3. //
  4. // The maximum data rate will depend on the quality of your SD,
  5. // the size of the FIFO, and using dedicated SPI.
  6. #include "ExFatLogger.h"
  7. #include "FreeStack.h"
  8. #include "SdFat.h"
  9. //------------------------------------------------------------------------------
  10. // This example was designed for exFAT but will support FAT16/FAT32.
  11. // Note: Uno will not support SD_FAT_TYPE = 3.
  12. // SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
  13. // 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
  14. #define SD_FAT_TYPE 2
  15. //------------------------------------------------------------------------------
  16. // Interval between data records in microseconds.
  17. // Try 250 with Teensy 3.6, Due, or STM32.
  18. // Try 2000 with AVR boards.
  19. // Try 4000 with SAMD Zero boards.
  20. const uint32_t LOG_INTERVAL_USEC = 2000;
  21. // Set USE_RTC nonzero for file timestamps.
  22. // RAM use will be marginal on Uno with RTClib.
  23. // 0 - RTC not used
  24. // 1 - DS1307
  25. // 2 - DS3231
  26. // 3 - PCF8523
  27. #define USE_RTC 0
  28. #if USE_RTC
  29. #include "RTClib.h"
  30. #endif // USE_RTC
  31. // LED to light if overruns occur.
  32. #define ERROR_LED_PIN -1
  33. /*
  34. Change the value of SD_CS_PIN if you are using SPI and
  35. your hardware does not use the default value, SS.
  36. Common values are:
  37. Arduino Ethernet shield: pin 4
  38. Sparkfun SD shield: pin 8
  39. Adafruit SD shields and modules: pin 10
  40. */
  41. // SDCARD_SS_PIN is defined for the built-in SD on some boards.
  42. #ifndef SDCARD_SS_PIN
  43. const uint8_t SD_CS_PIN = SS;
  44. #else // SDCARD_SS_PIN
  45. // Assume built-in SD is used.
  46. const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
  47. #endif // SDCARD_SS_PIN
  48. // FIFO SIZE - 512 byte sectors. Modify for your board.
  49. #ifdef __AVR_ATmega328P__
  50. // Use 512 bytes for 328 boards.
  51. #define FIFO_SIZE_SECTORS 1
  52. #elif defined(__AVR__)
  53. // Use 2 KiB for other AVR boards.
  54. #define FIFO_SIZE_SECTORS 4
  55. #else // __AVR_ATmega328P__
  56. // Use 8 KiB for non-AVR boards.
  57. #define FIFO_SIZE_SECTORS 16
  58. #endif // __AVR_ATmega328P__
  59. // Preallocate 1GiB file.
  60. const uint32_t PREALLOCATE_SIZE_MiB = 1024UL;
  61. // Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
  62. #define SPI_CLOCK SD_SCK_MHZ(50)
  63. // Try to select the best SD card configuration.
  64. #if HAS_SDIO_CLASS
  65. #define SD_CONFIG SdioConfig(FIFO_SDIO)
  66. #elif ENABLE_DEDICATED_SPI
  67. #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
  68. #else // HAS_SDIO_CLASS
  69. #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
  70. #endif // HAS_SDIO_CLASS
  71. // Save SRAM if 328.
  72. #ifdef __AVR_ATmega328P__
  73. #include "MinimumSerial.h"
  74. MinimumSerial MinSerial;
  75. #define Serial MinSerial
  76. #endif // __AVR_ATmega328P__
  77. //==============================================================================
  78. // Replace logRecord(), printRecord(), and ExFatLogger.h for your sensors.
  79. void logRecord(data_t* data, uint16_t overrun) {
  80. if (overrun) {
  81. // Add one since this record has no adc data. Could add overrun field.
  82. overrun++;
  83. data->adc[0] = 0X8000 | overrun;
  84. } else {
  85. for (size_t i = 0; i < ADC_COUNT; i++) {
  86. data->adc[i] = analogRead(i);
  87. }
  88. }
  89. }
  90. //------------------------------------------------------------------------------
  91. void printRecord(Print* pr, data_t* data) {
  92. static uint32_t nr = 0;
  93. if (!data) {
  94. pr->print(F("LOG_INTERVAL_USEC,"));
  95. pr->println(LOG_INTERVAL_USEC);
  96. pr->print(F("rec#"));
  97. for (size_t i = 0; i < ADC_COUNT; i++) {
  98. pr->print(F(",adc"));
  99. pr->print(i);
  100. }
  101. pr->println();
  102. nr = 0;
  103. return;
  104. }
  105. if (data->adc[0] & 0X8000) {
  106. uint16_t n = data->adc[0] & 0X7FFF;
  107. nr += n;
  108. pr->print(F("-1,"));
  109. pr->print(n);
  110. pr->println(F(",overuns"));
  111. } else {
  112. pr->print(nr++);
  113. for (size_t i = 0; i < ADC_COUNT; i++) {
  114. pr->write(',');
  115. pr->print(data->adc[i]);
  116. }
  117. pr->println();
  118. }
  119. }
  120. //==============================================================================
  121. const uint64_t PREALLOCATE_SIZE = (uint64_t)PREALLOCATE_SIZE_MiB << 20;
  122. // Max length of file name including zero byte.
  123. #define FILE_NAME_DIM 40
  124. // Max number of records to buffer while SD is busy.
  125. const size_t FIFO_DIM = 512 * FIFO_SIZE_SECTORS / sizeof(data_t);
  126. #if SD_FAT_TYPE == 0
  127. typedef SdFat sd_t;
  128. typedef File file_t;
  129. #elif SD_FAT_TYPE == 1
  130. typedef SdFat32 sd_t;
  131. typedef File32 file_t;
  132. #elif SD_FAT_TYPE == 2
  133. typedef SdExFat sd_t;
  134. typedef ExFile file_t;
  135. #elif SD_FAT_TYPE == 3
  136. typedef SdFs sd_t;
  137. typedef FsFile file_t;
  138. #else // SD_FAT_TYPE
  139. #error Invalid SD_FAT_TYPE
  140. #endif // SD_FAT_TYPE
  141. sd_t sd;
  142. file_t binFile;
  143. file_t csvFile;
  144. // You may modify the filename. Digits before the dot are file versions.
  145. char binName[] = "ExFatLogger00.bin";
  146. //------------------------------------------------------------------------------
  147. #if USE_RTC
  148. #if USE_RTC == 1
  149. RTC_DS1307 rtc;
  150. #elif USE_RTC == 2
  151. RTC_DS3231 rtc;
  152. #elif USE_RTC == 3
  153. RTC_PCF8523 rtc;
  154. #else // USE_RTC == type
  155. #error USE_RTC type not implemented.
  156. #endif // USE_RTC == type
  157. // Call back for file timestamps. Only called for file create and sync().
  158. void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
  159. DateTime now = rtc.now();
  160. // Return date using FS_DATE macro to format fields.
  161. *date = FS_DATE(now.year(), now.month(), now.day());
  162. // Return time using FS_TIME macro to format fields.
  163. *time = FS_TIME(now.hour(), now.minute(), now.second());
  164. // Return low time bits in units of 10 ms.
  165. *ms10 = now.second() & 1 ? 100 : 0;
  166. }
  167. #endif // USE_RTC
  168. //------------------------------------------------------------------------------
  169. #define error(s) sd.errorHalt(&Serial, F(s))
  170. #define dbgAssert(e) ((e) ? (void)0 : error("assert " #e))
  171. //-----------------------------------------------------------------------------
  172. // Convert binary file to csv file.
  173. void binaryToCsv() {
  174. uint8_t lastPct = 0;
  175. uint32_t t0 = millis();
  176. data_t binData[FIFO_DIM];
  177. if (!binFile.seekSet(512)) {
  178. error("binFile.seek failed");
  179. }
  180. uint32_t tPct = millis();
  181. printRecord(&csvFile, nullptr);
  182. while (!Serial.available() && binFile.available()) {
  183. int nb = binFile.read(binData, sizeof(binData));
  184. if (nb <= 0) {
  185. error("read binFile failed");
  186. }
  187. size_t nr = nb / sizeof(data_t);
  188. for (size_t i = 0; i < nr; i++) {
  189. printRecord(&csvFile, &binData[i]);
  190. }
  191. if ((millis() - tPct) > 1000) {
  192. uint8_t pct = binFile.curPosition() / (binFile.fileSize() / 100);
  193. if (pct != lastPct) {
  194. tPct = millis();
  195. lastPct = pct;
  196. Serial.print(pct, DEC);
  197. Serial.println('%');
  198. csvFile.sync();
  199. }
  200. }
  201. if (Serial.available()) {
  202. break;
  203. }
  204. }
  205. csvFile.close();
  206. Serial.print(F("Done: "));
  207. Serial.print(0.001 * (millis() - t0));
  208. Serial.println(F(" Seconds"));
  209. }
  210. //------------------------------------------------------------------------------
  211. void clearSerialInput() {
  212. uint32_t m = micros();
  213. do {
  214. if (Serial.read() >= 0) {
  215. m = micros();
  216. }
  217. } while (micros() - m < 10000);
  218. }
  219. //-------------------------------------------------------------------------------
  220. void createBinFile() {
  221. binFile.close();
  222. while (sd.exists(binName)) {
  223. char* p = strchr(binName, '.');
  224. if (!p) {
  225. error("no dot in filename");
  226. }
  227. while (true) {
  228. p--;
  229. if (p < binName || *p < '0' || *p > '9') {
  230. error("Can't create file name");
  231. }
  232. if (p[0] != '9') {
  233. p[0]++;
  234. break;
  235. }
  236. p[0] = '0';
  237. }
  238. }
  239. if (!binFile.open(binName, O_RDWR | O_CREAT)) {
  240. error("open binName failed");
  241. }
  242. Serial.println(binName);
  243. if (!binFile.preAllocate(PREALLOCATE_SIZE)) {
  244. error("preAllocate failed");
  245. }
  246. Serial.print(F("preAllocated: "));
  247. Serial.print(PREALLOCATE_SIZE_MiB);
  248. Serial.println(F(" MiB"));
  249. }
  250. //-------------------------------------------------------------------------------
  251. bool createCsvFile() {
  252. char csvName[FILE_NAME_DIM];
  253. if (!binFile.isOpen()) {
  254. Serial.println(F("No current binary file"));
  255. return false;
  256. }
  257. // Create a new csvFile.
  258. binFile.getName(csvName, sizeof(csvName));
  259. char* dot = strchr(csvName, '.');
  260. if (!dot) {
  261. error("no dot in filename");
  262. }
  263. strcpy(dot + 1, "csv");
  264. if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {
  265. error("open csvFile failed");
  266. }
  267. clearSerialInput();
  268. Serial.print(F("Writing: "));
  269. Serial.print(csvName);
  270. Serial.println(F(" - type any character to stop"));
  271. return true;
  272. }
  273. //-------------------------------------------------------------------------------
  274. void logData() {
  275. int32_t delta; // Jitter in log time.
  276. int32_t maxDelta = 0;
  277. uint32_t maxLogMicros = 0;
  278. uint32_t maxWriteMicros = 0;
  279. size_t maxFifoUse = 0;
  280. size_t fifoCount = 0;
  281. size_t fifoHead = 0;
  282. size_t fifoTail = 0;
  283. uint16_t overrun = 0;
  284. uint16_t maxOverrun = 0;
  285. uint32_t totalOverrun = 0;
  286. uint32_t fifoBuf[128 * FIFO_SIZE_SECTORS];
  287. data_t* fifoData = (data_t*)fifoBuf;
  288. // Write dummy sector to start multi-block write.
  289. dbgAssert(sizeof(fifoBuf) >= 512);
  290. memset(fifoBuf, 0, sizeof(fifoBuf));
  291. if (binFile.write(fifoBuf, 512) != 512) {
  292. error("write first sector failed");
  293. }
  294. clearSerialInput();
  295. Serial.println(F("Type any character to stop"));
  296. // Wait until SD is not busy.
  297. while (sd.card()->isBusy()) {
  298. }
  299. // Start time for log file.
  300. uint32_t m = millis();
  301. // Time to log next record.
  302. uint32_t logTime = micros();
  303. while (true) {
  304. // Time for next data record.
  305. logTime += LOG_INTERVAL_USEC;
  306. // Wait until time to log data.
  307. delta = micros() - logTime;
  308. if (delta > 0) {
  309. Serial.print(F("delta: "));
  310. Serial.println(delta);
  311. error("Rate too fast");
  312. }
  313. while (delta < 0) {
  314. delta = micros() - logTime;
  315. }
  316. if (fifoCount < FIFO_DIM) {
  317. uint32_t m = micros();
  318. logRecord(fifoData + fifoHead, overrun);
  319. m = micros() - m;
  320. if (m > maxLogMicros) {
  321. maxLogMicros = m;
  322. }
  323. fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
  324. fifoCount++;
  325. if (overrun) {
  326. if (overrun > maxOverrun) {
  327. maxOverrun = overrun;
  328. }
  329. overrun = 0;
  330. }
  331. } else {
  332. totalOverrun++;
  333. overrun++;
  334. if (overrun > 0XFFF) {
  335. error("too many overruns");
  336. }
  337. if (ERROR_LED_PIN >= 0) {
  338. digitalWrite(ERROR_LED_PIN, HIGH);
  339. }
  340. }
  341. // Save max jitter.
  342. if (delta > maxDelta) {
  343. maxDelta = delta;
  344. }
  345. // Write data if SD is not busy.
  346. if (!sd.card()->isBusy()) {
  347. size_t nw = fifoHead > fifoTail ? fifoCount : FIFO_DIM - fifoTail;
  348. // Limit write time by not writing more than 512 bytes.
  349. const size_t MAX_WRITE = 512 / sizeof(data_t);
  350. if (nw > MAX_WRITE) nw = MAX_WRITE;
  351. size_t nb = nw * sizeof(data_t);
  352. uint32_t usec = micros();
  353. if (nb != binFile.write(fifoData + fifoTail, nb)) {
  354. error("write binFile failed");
  355. }
  356. usec = micros() - usec;
  357. if (usec > maxWriteMicros) {
  358. maxWriteMicros = usec;
  359. }
  360. fifoTail = (fifoTail + nw) < FIFO_DIM ? fifoTail + nw : 0;
  361. if (fifoCount > maxFifoUse) {
  362. maxFifoUse = fifoCount;
  363. }
  364. fifoCount -= nw;
  365. if (Serial.available()) {
  366. break;
  367. }
  368. }
  369. }
  370. Serial.print(F("\nLog time: "));
  371. Serial.print(0.001 * (millis() - m));
  372. Serial.println(F(" Seconds"));
  373. binFile.truncate();
  374. binFile.sync();
  375. Serial.print(("File size: "));
  376. // Warning cast used for print since fileSize is uint64_t.
  377. Serial.print((uint32_t)binFile.fileSize());
  378. Serial.println(F(" bytes"));
  379. Serial.print(F("totalOverrun: "));
  380. Serial.println(totalOverrun);
  381. Serial.print(F("FIFO_DIM: "));
  382. Serial.println(FIFO_DIM);
  383. Serial.print(F("maxFifoUse: "));
  384. Serial.println(maxFifoUse);
  385. Serial.print(F("maxLogMicros: "));
  386. Serial.println(maxLogMicros);
  387. Serial.print(F("maxWriteMicros: "));
  388. Serial.println(maxWriteMicros);
  389. Serial.print(F("Log interval: "));
  390. Serial.print(LOG_INTERVAL_USEC);
  391. Serial.print(F(" micros\nmaxDelta: "));
  392. Serial.print(maxDelta);
  393. Serial.println(F(" micros"));
  394. }
  395. //------------------------------------------------------------------------------
  396. void openBinFile() {
  397. char name[FILE_NAME_DIM];
  398. clearSerialInput();
  399. Serial.println(F("Enter file name"));
  400. if (!serialReadLine(name, sizeof(name))) {
  401. return;
  402. }
  403. if (!sd.exists(name)) {
  404. Serial.println(name);
  405. Serial.println(F("File does not exist"));
  406. return;
  407. }
  408. binFile.close();
  409. if (!binFile.open(name, O_RDONLY)) {
  410. Serial.println(name);
  411. Serial.println(F("open failed"));
  412. return;
  413. }
  414. Serial.println(F("File opened"));
  415. }
  416. //-----------------------------------------------------------------------------
  417. void printData() {
  418. if (!binFile.isOpen()) {
  419. Serial.println(F("No current binary file"));
  420. return;
  421. }
  422. // Skip first dummy sector.
  423. if (!binFile.seekSet(512)) {
  424. error("seek failed");
  425. }
  426. clearSerialInput();
  427. Serial.println(F("type any character to stop\n"));
  428. delay(1000);
  429. printRecord(&Serial, nullptr);
  430. while (binFile.available() && !Serial.available()) {
  431. data_t record;
  432. if (binFile.read(&record, sizeof(data_t)) != sizeof(data_t)) {
  433. error("read binFile failed");
  434. }
  435. printRecord(&Serial, &record);
  436. }
  437. }
  438. //------------------------------------------------------------------------------
  439. void printUnusedStack() {
  440. #if HAS_UNUSED_STACK
  441. Serial.print(F("\nUnused stack: "));
  442. Serial.println(UnusedStack());
  443. #endif // HAS_UNUSED_STACK
  444. }
  445. //------------------------------------------------------------------------------
  446. bool serialReadLine(char* str, size_t size) {
  447. size_t n = 0;
  448. while (!Serial.available()) {
  449. yield();
  450. }
  451. while (true) {
  452. int c = Serial.read();
  453. if (c < ' ') break;
  454. str[n++] = c;
  455. if (n >= size) {
  456. Serial.println(F("input too long"));
  457. return false;
  458. }
  459. uint32_t m = millis();
  460. while (!Serial.available() && (millis() - m) < 100) {
  461. }
  462. if (!Serial.available()) break;
  463. }
  464. str[n] = 0;
  465. return true;
  466. }
  467. //------------------------------------------------------------------------------
  468. void testSensor() {
  469. const uint32_t interval = 200000;
  470. int32_t diff;
  471. data_t data;
  472. clearSerialInput();
  473. Serial.println(F("\nTesting - type any character to stop\n"));
  474. delay(1000);
  475. printRecord(&Serial, nullptr);
  476. uint32_t m = micros();
  477. while (!Serial.available()) {
  478. m += interval;
  479. do {
  480. diff = m - micros();
  481. } while (diff > 0);
  482. logRecord(&data, 0);
  483. printRecord(&Serial, &data);
  484. }
  485. }
  486. //------------------------------------------------------------------------------
  487. void setup() {
  488. if (ERROR_LED_PIN >= 0) {
  489. pinMode(ERROR_LED_PIN, OUTPUT);
  490. digitalWrite(ERROR_LED_PIN, HIGH);
  491. }
  492. Serial.begin(9600);
  493. // Wait for USB Serial
  494. while (!Serial) {
  495. yield();
  496. }
  497. delay(1000);
  498. Serial.println(F("Type any character to begin"));
  499. while (!Serial.available()) {
  500. yield();
  501. }
  502. FillStack();
  503. #if !ENABLE_DEDICATED_SPI
  504. Serial.println(
  505. F("\nFor best performance edit SdFatConfig.h\n"
  506. "and set ENABLE_DEDICATED_SPI nonzero"));
  507. #endif // !ENABLE_DEDICATED_SPI
  508. Serial.print(FIFO_DIM);
  509. Serial.println(F(" FIFO entries will be used."));
  510. // Initialize SD.
  511. if (!sd.begin(SD_CONFIG)) {
  512. sd.initErrorHalt(&Serial);
  513. }
  514. #if USE_RTC
  515. if (!rtc.begin()) {
  516. error("rtc.begin failed");
  517. }
  518. if (!rtc.isrunning()) {
  519. // Set RTC to sketch compile date & time.
  520. // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  521. error("RTC is NOT running!");
  522. }
  523. // Set callback
  524. FsDateTime::setCallback(dateTime);
  525. #endif // USE_RTC
  526. }
  527. //------------------------------------------------------------------------------
  528. void loop() {
  529. printUnusedStack();
  530. // Read any Serial data.
  531. clearSerialInput();
  532. if (ERROR_LED_PIN >= 0) {
  533. digitalWrite(ERROR_LED_PIN, LOW);
  534. }
  535. Serial.println();
  536. Serial.println(F("type: "));
  537. Serial.println(F("b - open existing bin file"));
  538. Serial.println(F("c - convert file to csv"));
  539. Serial.println(F("l - list files"));
  540. Serial.println(F("p - print data to Serial"));
  541. Serial.println(F("r - record data"));
  542. Serial.println(F("t - test without logging"));
  543. while (!Serial.available()) {
  544. yield();
  545. }
  546. char c = tolower(Serial.read());
  547. Serial.println();
  548. if (c == 'b') {
  549. openBinFile();
  550. } else if (c == 'c') {
  551. if (createCsvFile()) {
  552. binaryToCsv();
  553. }
  554. } else if (c == 'l') {
  555. Serial.println(F("ls:"));
  556. sd.ls(&Serial, LS_DATE | LS_SIZE);
  557. } else if (c == 'p') {
  558. printData();
  559. } else if (c == 'r') {
  560. createBinFile();
  561. logData();
  562. } else if (c == 't') {
  563. testSensor();
  564. } else {
  565. Serial.println(F("Invalid entry"));
  566. }
  567. }