You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ExFatLogger.ino 16KB

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