ExFatLogger.ino 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  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 "SdFat.h"
  7. #include "FreeStack.h"
  8. #include "ExFatLogger.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. // Start time for log file.
  299. uint32_t m = millis();
  300. // Time to log next record.
  301. uint32_t logTime = micros();
  302. while (true) {
  303. // Time for next data record.
  304. logTime += LOG_INTERVAL_USEC;
  305. // Wait until time to log data.
  306. delta = micros() - logTime;
  307. if (delta > 0) {
  308. Serial.print(F("delta: "));
  309. Serial.println(delta);
  310. error("Rate too fast");
  311. }
  312. while (delta < 0) {
  313. delta = micros() - logTime;
  314. }
  315. if (fifoCount < FIFO_DIM) {
  316. uint32_t m = micros();
  317. logRecord(fifoData + fifoHead, overrun);
  318. m = micros() - m;
  319. if (m > maxLogMicros) {
  320. maxLogMicros = m;
  321. }
  322. fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
  323. fifoCount++;
  324. if (overrun) {
  325. if (overrun > maxOverrun) {
  326. maxOverrun = overrun;
  327. }
  328. overrun = 0;
  329. }
  330. } else {
  331. totalOverrun++;
  332. overrun++;
  333. if (overrun > 0XFFF) {
  334. error("too many overruns");
  335. }
  336. if (ERROR_LED_PIN >= 0) {
  337. digitalWrite(ERROR_LED_PIN, HIGH);
  338. }
  339. }
  340. // Save max jitter.
  341. if (delta > maxDelta) {
  342. maxDelta = delta;
  343. }
  344. // Write data if SD is not busy.
  345. if (!sd.card()->isBusy()) {
  346. size_t nw = fifoHead > fifoTail ? fifoCount : FIFO_DIM - fifoTail;
  347. // Limit write time by not writing more than 512 bytes.
  348. const size_t MAX_WRITE = 512/sizeof(data_t);
  349. if (nw > MAX_WRITE) nw = MAX_WRITE;
  350. size_t nb = nw*sizeof(data_t);
  351. uint32_t usec = micros();
  352. if (nb != binFile.write(fifoData + fifoTail, nb)) {
  353. error("write binFile failed");
  354. }
  355. usec = micros() - usec;
  356. if (usec > maxWriteMicros) {
  357. maxWriteMicros = usec;
  358. }
  359. fifoTail = (fifoTail + nw) < FIFO_DIM ? fifoTail + nw : 0;
  360. if (fifoCount > maxFifoUse) {
  361. maxFifoUse = fifoCount;
  362. }
  363. fifoCount -= nw;
  364. if (Serial.available()) {
  365. break;
  366. }
  367. }
  368. }
  369. Serial.print(F("\nLog time: "));
  370. Serial.print(0.001*(millis() - m));
  371. Serial.println(F(" Seconds"));
  372. binFile.truncate();
  373. binFile.sync();
  374. Serial.print(("File size: "));
  375. // Warning cast used for print since fileSize is uint64_t.
  376. Serial.print((uint32_t)binFile.fileSize());
  377. Serial.println(F(" bytes"));
  378. Serial.print(F("totalOverrun: "));
  379. Serial.println(totalOverrun);
  380. Serial.print(F("FIFO_DIM: "));
  381. Serial.println(FIFO_DIM);
  382. Serial.print(F("maxFifoUse: "));
  383. Serial.println(maxFifoUse);
  384. Serial.print(F("maxLogMicros: "));
  385. Serial.println(maxLogMicros);
  386. Serial.print(F("maxWriteMicros: "));
  387. Serial.println(maxWriteMicros);
  388. Serial.print(F("Log interval: "));
  389. Serial.print(LOG_INTERVAL_USEC);
  390. Serial.print(F(" micros\nmaxDelta: "));
  391. Serial.print(maxDelta);
  392. Serial.println(F(" micros"));
  393. }
  394. //------------------------------------------------------------------------------
  395. void openBinFile() {
  396. char name[FILE_NAME_DIM];
  397. clearSerialInput();
  398. Serial.println(F("Enter file name"));
  399. if (!serialReadLine(name, sizeof(name))) {
  400. return;
  401. }
  402. if (!sd.exists(name)) {
  403. Serial.println(name);
  404. Serial.println(F("File does not exist"));
  405. return;
  406. }
  407. binFile.close();
  408. if (!binFile.open(name, O_RDONLY)) {
  409. Serial.println(name);
  410. Serial.println(F("open failed"));
  411. return;
  412. }
  413. Serial.println(F("File opened"));
  414. }
  415. //-----------------------------------------------------------------------------
  416. void printData() {
  417. if (!binFile.isOpen()) {
  418. Serial.println(F("No current binary file"));
  419. return;
  420. }
  421. // Skip first dummy sector.
  422. if (!binFile.seekSet(512)) {
  423. error("seek failed");
  424. }
  425. clearSerialInput();
  426. Serial.println(F("type any character to stop\n"));
  427. delay(1000);
  428. printRecord(&Serial, nullptr);
  429. while (binFile.available() && !Serial.available()) {
  430. data_t record;
  431. if (binFile.read(&record, sizeof(data_t)) != sizeof(data_t)) {
  432. error("read binFile failed");
  433. }
  434. printRecord(&Serial, &record);
  435. }
  436. }
  437. //------------------------------------------------------------------------------
  438. void printUnusedStack() {
  439. #if HAS_UNUSED_STACK
  440. Serial.print(F("\nUnused stack: "));
  441. Serial.println(UnusedStack());
  442. #endif // HAS_UNUSED_STACK
  443. }
  444. //------------------------------------------------------------------------------
  445. bool serialReadLine(char* str, size_t size) {
  446. size_t n = 0;
  447. while(!Serial.available()) {
  448. yield();
  449. }
  450. while (true) {
  451. int c = Serial.read();
  452. if (c < ' ') break;
  453. str[n++] = c;
  454. if (n >= size) {
  455. Serial.println(F("input too long"));
  456. return false;
  457. }
  458. uint32_t m = millis();
  459. while (!Serial.available() && (millis() - m) < 100){}
  460. if (!Serial.available()) break;
  461. }
  462. str[n] = 0;
  463. return true;
  464. }
  465. //------------------------------------------------------------------------------
  466. void testSensor() {
  467. const uint32_t interval = 200000;
  468. int32_t diff;
  469. data_t data;
  470. clearSerialInput();
  471. Serial.println(F("\nTesting - type any character to stop\n"));
  472. delay(1000);
  473. printRecord(&Serial, nullptr);
  474. uint32_t m = micros();
  475. while (!Serial.available()) {
  476. m += interval;
  477. do {
  478. diff = m - micros();
  479. } while (diff > 0);
  480. logRecord(&data, 0);
  481. printRecord(&Serial, &data);
  482. }
  483. }
  484. //------------------------------------------------------------------------------
  485. void setup() {
  486. if (ERROR_LED_PIN >= 0) {
  487. pinMode(ERROR_LED_PIN, OUTPUT);
  488. digitalWrite(ERROR_LED_PIN, HIGH);
  489. }
  490. Serial.begin(9600);
  491. // Wait for USB Serial
  492. while (!Serial) {
  493. yield();
  494. }
  495. delay(1000);
  496. Serial.println(F("Type any character to begin"));
  497. while (!Serial.available()) {
  498. yield();
  499. }
  500. FillStack();
  501. #if !ENABLE_DEDICATED_SPI
  502. Serial.println(F(
  503. "\nFor best performance edit SdFatConfig.h\n"
  504. "and set ENABLE_DEDICATED_SPI nonzero"));
  505. #endif // !ENABLE_DEDICATED_SPI
  506. Serial.print(FIFO_DIM);
  507. Serial.println(F(" FIFO entries will be used."));
  508. // Initialize SD.
  509. if (!sd.begin(SD_CONFIG)) {
  510. sd.initErrorHalt(&Serial);
  511. }
  512. #if USE_RTC
  513. if (!rtc.begin()) {
  514. error("rtc.begin failed");
  515. }
  516. if (!rtc.isrunning()) {
  517. // Set RTC to sketch compile date & time.
  518. // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  519. error("RTC is NOT running!");
  520. }
  521. // Set callback
  522. FsDateTime::setCallback(dateTime);
  523. #endif // USE_RTC
  524. }
  525. //------------------------------------------------------------------------------
  526. void loop() {
  527. printUnusedStack();
  528. // Read any Serial data.
  529. clearSerialInput();
  530. if (ERROR_LED_PIN >= 0) {
  531. digitalWrite(ERROR_LED_PIN, LOW);
  532. }
  533. Serial.println();
  534. Serial.println(F("type: "));
  535. Serial.println(F("b - open existing bin file"));
  536. Serial.println(F("c - convert file to csv"));
  537. Serial.println(F("l - list files"));
  538. Serial.println(F("p - print data to Serial"));
  539. Serial.println(F("r - record data"));
  540. Serial.println(F("t - test without logging"));
  541. while(!Serial.available()) {
  542. yield();
  543. }
  544. char c = tolower(Serial.read());
  545. Serial.println();
  546. if (c == 'b') {
  547. openBinFile();
  548. } else if (c == 'c') {
  549. if (createCsvFile()) {
  550. binaryToCsv();
  551. }
  552. } else if (c == 'l') {
  553. Serial.println(F("ls:"));
  554. sd.ls(&Serial, LS_DATE | LS_SIZE);
  555. } else if (c == 'p') {
  556. printData();
  557. } else if (c == 'r') {
  558. createBinFile();
  559. logData();
  560. } else if (c == 't') {
  561. testSensor();
  562. } else {
  563. Serial.println(F("Invalid entry"));
  564. }
  565. }