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.

655 satır
19KB

  1. /**
  2. * This program logs data to a binary file. Functions are included
  3. * to convert the binary file to a csv text file.
  4. *
  5. * Samples are logged at regular intervals. The maximum logging rate
  6. * depends on the quality of your SD card and the time required to
  7. * read sensor data. This example has been tested at 500 Hz with
  8. * good SD card on an Uno. 4000 HZ is possible on a Due.
  9. *
  10. * If your SD card has a long write latency, it may be necessary to use
  11. * slower sample rates. Using a Mega Arduino helps overcome latency
  12. * problems since 12 512 byte buffers will be used.
  13. *
  14. * Data is written to the file using a SD multiple block write command.
  15. */
  16. #include <SPI.h>
  17. #include "SdFat.h"
  18. #include "FreeStack.h"
  19. #include "UserTypes.h"
  20. #ifdef __AVR_ATmega328P__
  21. #include "MinimumSerial.h"
  22. MinimumSerial MinSerial;
  23. #define Serial MinSerial
  24. #endif // __AVR_ATmega328P__
  25. //==============================================================================
  26. // Start of configuration constants.
  27. //==============================================================================
  28. // Abort run on an overrun. Data before the overrun will be saved.
  29. #define ABORT_ON_OVERRUN 1
  30. //------------------------------------------------------------------------------
  31. //Interval between data records in microseconds.
  32. const uint32_t LOG_INTERVAL_USEC = 2000;
  33. //------------------------------------------------------------------------------
  34. // Set USE_SHARED_SPI non-zero for use of an SPI sensor.
  35. // May not work for some cards.
  36. #ifndef USE_SHARED_SPI
  37. #define USE_SHARED_SPI 0
  38. #endif // USE_SHARED_SPI
  39. //------------------------------------------------------------------------------
  40. // Pin definitions.
  41. //
  42. // SD chip select pin.
  43. const uint8_t SD_CS_PIN = SS;
  44. //
  45. // Digital pin to indicate an error, set to -1 if not used.
  46. // The led blinks for fatal errors. The led goes on solid for
  47. // overrun errors and logging continues unless ABORT_ON_OVERRUN
  48. // is non-zero.
  49. #ifdef ERROR_LED_PIN
  50. #undef ERROR_LED_PIN
  51. #endif // ERROR_LED_PIN
  52. const int8_t ERROR_LED_PIN = -1;
  53. //------------------------------------------------------------------------------
  54. // File definitions.
  55. //
  56. // Maximum file size in blocks.
  57. // The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.
  58. // This file is flash erased using special SD commands. The file will be
  59. // truncated if logging is stopped early.
  60. const uint32_t FILE_BLOCK_COUNT = 256000;
  61. //
  62. // log file base name if not defined in UserTypes.h
  63. #ifndef FILE_BASE_NAME
  64. #define FILE_BASE_NAME "data"
  65. #endif // FILE_BASE_NAME
  66. //------------------------------------------------------------------------------
  67. // Buffer definitions.
  68. //
  69. // The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT-1 additional
  70. // buffers.
  71. //
  72. #ifndef RAMEND
  73. // Assume ARM. Use total of ten 512 byte buffers.
  74. const uint8_t BUFFER_BLOCK_COUNT = 10;
  75. //
  76. #elif RAMEND < 0X8FF
  77. #error Too little SRAM
  78. //
  79. #elif RAMEND < 0X10FF
  80. // Use total of two 512 byte buffers.
  81. const uint8_t BUFFER_BLOCK_COUNT = 2;
  82. //
  83. #elif RAMEND < 0X20FF
  84. // Use total of four 512 byte buffers.
  85. const uint8_t BUFFER_BLOCK_COUNT = 4;
  86. //
  87. #else // RAMEND
  88. // Use total of 12 512 byte buffers.
  89. const uint8_t BUFFER_BLOCK_COUNT = 12;
  90. #endif // RAMEND
  91. //==============================================================================
  92. // End of configuration constants.
  93. //==============================================================================
  94. // Temporary log file. Will be deleted if a reset or power failure occurs.
  95. #define TMP_FILE_NAME FILE_BASE_NAME "##.bin"
  96. // Size of file base name.
  97. const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
  98. const uint8_t FILE_NAME_DIM = BASE_NAME_SIZE + 7;
  99. char binName[FILE_NAME_DIM] = FILE_BASE_NAME "00.bin";
  100. SdFat sd;
  101. SdBaseFile binFile;
  102. // Number of data records in a block.
  103. const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t);
  104. //Compute fill so block size is 512 bytes. FILL_DIM may be zero.
  105. const uint16_t FILL_DIM = 512 - 4 - DATA_DIM*sizeof(data_t);
  106. struct block_t {
  107. uint16_t count;
  108. uint16_t overrun;
  109. data_t data[DATA_DIM];
  110. uint8_t fill[FILL_DIM];
  111. };
  112. //==============================================================================
  113. // Error messages stored in flash.
  114. #define error(msg) {sd.errorPrint(&Serial, F(msg));fatalBlink();}
  115. //------------------------------------------------------------------------------
  116. //
  117. void fatalBlink() {
  118. while (true) {
  119. SysCall::yield();
  120. if (ERROR_LED_PIN >= 0) {
  121. digitalWrite(ERROR_LED_PIN, HIGH);
  122. delay(200);
  123. digitalWrite(ERROR_LED_PIN, LOW);
  124. delay(200);
  125. }
  126. }
  127. }
  128. //------------------------------------------------------------------------------
  129. // read data file and check for overruns
  130. void checkOverrun() {
  131. bool headerPrinted = false;
  132. block_t block;
  133. uint32_t bn = 0;
  134. if (!binFile.isOpen()) {
  135. Serial.println();
  136. Serial.println(F("No current binary file"));
  137. return;
  138. }
  139. binFile.rewind();
  140. Serial.println();
  141. Serial.print(F("FreeStack: "));
  142. Serial.println(FreeStack());
  143. Serial.println(F("Checking overrun errors - type any character to stop"));
  144. while (binFile.read(&block, 512) == 512) {
  145. if (block.count == 0) {
  146. break;
  147. }
  148. if (block.overrun) {
  149. if (!headerPrinted) {
  150. Serial.println();
  151. Serial.println(F("Overruns:"));
  152. Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
  153. headerPrinted = true;
  154. }
  155. Serial.print(bn);
  156. Serial.print(',');
  157. Serial.print(binFile.firstBlock() + bn);
  158. Serial.print(',');
  159. Serial.println(block.overrun);
  160. }
  161. bn++;
  162. }
  163. if (!headerPrinted) {
  164. Serial.println(F("No errors found"));
  165. } else {
  166. Serial.println(F("Done"));
  167. }
  168. }
  169. //-----------------------------------------------------------------------------
  170. // Convert binary file to csv file.
  171. void binaryToCsv() {
  172. uint8_t lastPct = 0;
  173. block_t block;
  174. uint32_t t0 = millis();
  175. uint32_t syncCluster = 0;
  176. SdFile csvFile;
  177. char csvName[FILE_NAME_DIM];
  178. if (!binFile.isOpen()) {
  179. Serial.println();
  180. Serial.println(F("No current binary file"));
  181. return;
  182. }
  183. Serial.println();
  184. Serial.print(F("FreeStack: "));
  185. Serial.println(FreeStack());
  186. // Create a new csvFile.
  187. strcpy(csvName, binName);
  188. strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
  189. if (!csvFile.open(csvName, O_WRITE | O_CREAT | O_TRUNC)) {
  190. error("open csvFile failed");
  191. }
  192. binFile.rewind();
  193. Serial.print(F("Writing: "));
  194. Serial.print(csvName);
  195. Serial.println(F(" - type any character to stop"));
  196. printHeader(&csvFile);
  197. uint32_t tPct = millis();
  198. while (!Serial.available() && binFile.read(&block, 512) == 512) {
  199. uint16_t i;
  200. if (block.count == 0 || block.count > DATA_DIM) {
  201. break;
  202. }
  203. if (block.overrun) {
  204. csvFile.print(F("OVERRUN,"));
  205. csvFile.println(block.overrun);
  206. }
  207. for (i = 0; i < block.count; i++) {
  208. printData(&csvFile, &block.data[i]);
  209. }
  210. if (csvFile.curCluster() != syncCluster) {
  211. csvFile.sync();
  212. syncCluster = csvFile.curCluster();
  213. }
  214. if ((millis() - tPct) > 1000) {
  215. uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
  216. if (pct != lastPct) {
  217. tPct = millis();
  218. lastPct = pct;
  219. Serial.print(pct, DEC);
  220. Serial.println('%');
  221. }
  222. }
  223. if (Serial.available()) {
  224. break;
  225. }
  226. }
  227. csvFile.close();
  228. Serial.print(F("Done: "));
  229. Serial.print(0.001*(millis() - t0));
  230. Serial.println(F(" Seconds"));
  231. }
  232. //-----------------------------------------------------------------------------
  233. void createBinFile() {
  234. // max number of blocks to erase per erase call
  235. const uint32_t ERASE_SIZE = 262144L;
  236. uint32_t bgnBlock, endBlock;
  237. // Delete old tmp file.
  238. if (sd.exists(TMP_FILE_NAME)) {
  239. Serial.println(F("Deleting tmp file " TMP_FILE_NAME));
  240. if (!sd.remove(TMP_FILE_NAME)) {
  241. error("Can't remove tmp file");
  242. }
  243. }
  244. // Create new file.
  245. Serial.println(F("\nCreating new file"));
  246. binFile.close();
  247. if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
  248. error("createContiguous failed");
  249. }
  250. // Get the address of the file on the SD.
  251. if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
  252. error("contiguousRange failed");
  253. }
  254. // Flash erase all data in the file.
  255. Serial.println(F("Erasing all data"));
  256. uint32_t bgnErase = bgnBlock;
  257. uint32_t endErase;
  258. while (bgnErase < endBlock) {
  259. endErase = bgnErase + ERASE_SIZE;
  260. if (endErase > endBlock) {
  261. endErase = endBlock;
  262. }
  263. if (!sd.card()->erase(bgnErase, endErase)) {
  264. error("erase failed");
  265. }
  266. bgnErase = endErase + 1;
  267. }
  268. }
  269. //------------------------------------------------------------------------------
  270. // dump data file to Serial
  271. void dumpData() {
  272. block_t block;
  273. if (!binFile.isOpen()) {
  274. Serial.println();
  275. Serial.println(F("No current binary file"));
  276. return;
  277. }
  278. binFile.rewind();
  279. Serial.println();
  280. Serial.println(F("Type any character to stop"));
  281. delay(1000);
  282. printHeader(&Serial);
  283. while (!Serial.available() && binFile.read(&block , 512) == 512) {
  284. if (block.count == 0) {
  285. break;
  286. }
  287. if (block.overrun) {
  288. Serial.print(F("OVERRUN,"));
  289. Serial.println(block.overrun);
  290. }
  291. for (uint16_t i = 0; i < block.count; i++) {
  292. printData(&Serial, &block.data[i]);
  293. }
  294. }
  295. Serial.println(F("Done"));
  296. }
  297. //------------------------------------------------------------------------------
  298. // log data
  299. void logData() {
  300. createBinFile();
  301. recordBinFile();
  302. renameBinFile();
  303. }
  304. //------------------------------------------------------------------------------
  305. void openBinFile() {
  306. char name[FILE_NAME_DIM];
  307. strcpy(name, binName);
  308. Serial.println(F("\nEnter two digit version"));
  309. Serial.write(name, BASE_NAME_SIZE);
  310. for (int i = 0; i < 2; i++) {
  311. while (!Serial.available()) {
  312. SysCall::yield();
  313. }
  314. char c = Serial.read();
  315. Serial.write(c);
  316. if (c < '0' || c > '9') {
  317. Serial.println(F("\nInvalid digit"));
  318. return;
  319. }
  320. name[BASE_NAME_SIZE + i] = c;
  321. }
  322. Serial.println(&name[BASE_NAME_SIZE+2]);
  323. if (!sd.exists(name)) {
  324. Serial.println(F("File does not exist"));
  325. return;
  326. }
  327. binFile.close();
  328. strcpy(binName, name);
  329. if (!binFile.open(binName, O_READ)) {
  330. Serial.println(F("open failed"));
  331. return;
  332. }
  333. Serial.println(F("File opened"));
  334. }
  335. //------------------------------------------------------------------------------
  336. void recordBinFile() {
  337. const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 1;
  338. // Index of last queue location.
  339. const uint8_t QUEUE_LAST = QUEUE_DIM - 1;
  340. // Allocate extra buffer space.
  341. block_t block[BUFFER_BLOCK_COUNT - 1];
  342. block_t* curBlock = 0;
  343. block_t* emptyStack[BUFFER_BLOCK_COUNT];
  344. uint8_t emptyTop;
  345. uint8_t minTop;
  346. block_t* fullQueue[QUEUE_DIM];
  347. uint8_t fullHead = 0;
  348. uint8_t fullTail = 0;
  349. // Use SdFat's internal buffer.
  350. emptyStack[0] = (block_t*)sd.vol()->cacheClear();
  351. if (emptyStack[0] == 0) {
  352. error("cacheClear failed");
  353. }
  354. // Put rest of buffers on the empty stack.
  355. for (int i = 1; i < BUFFER_BLOCK_COUNT; i++) {
  356. emptyStack[i] = &block[i - 1];
  357. }
  358. emptyTop = BUFFER_BLOCK_COUNT;
  359. minTop = BUFFER_BLOCK_COUNT;
  360. // Start a multiple block write.
  361. if (!sd.card()->writeStart(binFile.firstBlock())) {
  362. error("writeBStart failed");
  363. }
  364. Serial.print(F("FreeStack: "));
  365. Serial.println(FreeStack());
  366. Serial.println(F("Logging - type any character to stop"));
  367. bool closeFile = false;
  368. uint32_t bn = 0;
  369. uint32_t maxLatency = 0;
  370. uint32_t overrun = 0;
  371. uint32_t overrunTotal = 0;
  372. uint32_t logTime = micros();
  373. while(1) {
  374. // Time for next data record.
  375. logTime += LOG_INTERVAL_USEC;
  376. if (Serial.available()) {
  377. closeFile = true;
  378. }
  379. if (closeFile) {
  380. if (curBlock != 0) {
  381. // Put buffer in full queue.
  382. fullQueue[fullHead] = curBlock;
  383. fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
  384. curBlock = 0;
  385. }
  386. } else {
  387. if (curBlock == 0 && emptyTop != 0) {
  388. curBlock = emptyStack[--emptyTop];
  389. if (emptyTop < minTop) {
  390. minTop = emptyTop;
  391. }
  392. curBlock->count = 0;
  393. curBlock->overrun = overrun;
  394. overrun = 0;
  395. }
  396. if ((int32_t)(logTime - micros()) < 0) {
  397. error("Rate too fast");
  398. }
  399. int32_t delta;
  400. do {
  401. delta = micros() - logTime;
  402. } while (delta < 0);
  403. if (curBlock == 0) {
  404. overrun++;
  405. overrunTotal++;
  406. if (ERROR_LED_PIN >= 0) {
  407. digitalWrite(ERROR_LED_PIN, HIGH);
  408. }
  409. #if ABORT_ON_OVERRUN
  410. Serial.println(F("Overrun abort"));
  411. break;
  412. #endif // ABORT_ON_OVERRUN
  413. } else {
  414. #if USE_SHARED_SPI
  415. sd.card()->spiStop();
  416. #endif // USE_SHARED_SPI
  417. acquireData(&curBlock->data[curBlock->count++]);
  418. #if USE_SHARED_SPI
  419. sd.card()->spiStart();
  420. #endif // USE_SHARED_SPI
  421. if (curBlock->count == DATA_DIM) {
  422. fullQueue[fullHead] = curBlock;
  423. fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
  424. curBlock = 0;
  425. }
  426. }
  427. }
  428. if (fullHead == fullTail) {
  429. // Exit loop if done.
  430. if (closeFile) {
  431. break;
  432. }
  433. } else if (!sd.card()->isBusy()) {
  434. // Get address of block to write.
  435. block_t* pBlock = fullQueue[fullTail];
  436. fullTail = fullTail < QUEUE_LAST ? fullTail + 1 : 0;
  437. // Write block to SD.
  438. uint32_t usec = micros();
  439. if (!sd.card()->writeData((uint8_t*)pBlock)) {
  440. error("write data failed");
  441. }
  442. usec = micros() - usec;
  443. if (usec > maxLatency) {
  444. maxLatency = usec;
  445. }
  446. // Move block to empty queue.
  447. emptyStack[emptyTop++] = pBlock;
  448. bn++;
  449. if (bn == FILE_BLOCK_COUNT) {
  450. // File full so stop
  451. break;
  452. }
  453. }
  454. }
  455. if (!sd.card()->writeStop()) {
  456. error("writeStop failed");
  457. }
  458. Serial.print(F("Min Free buffers: "));
  459. Serial.println(minTop);
  460. Serial.print(F("Max block write usec: "));
  461. Serial.println(maxLatency);
  462. Serial.print(F("Overruns: "));
  463. Serial.println(overrunTotal);
  464. // Truncate file if recording stopped early.
  465. if (bn != FILE_BLOCK_COUNT) {
  466. Serial.println(F("Truncating file"));
  467. if (!binFile.truncate(512L * bn)) {
  468. error("Can't truncate file");
  469. }
  470. }
  471. }
  472. //------------------------------------------------------------------------------
  473. void recoverTmpFile() {
  474. uint16_t count;
  475. if (!binFile.open(TMP_FILE_NAME, O_RDWR)) {
  476. return;
  477. }
  478. if (binFile.read(&count, 2) != 2 || count != DATA_DIM) {
  479. error("Please delete existing " TMP_FILE_NAME);
  480. }
  481. Serial.println(F("\nRecovering data in tmp file " TMP_FILE_NAME));
  482. uint32_t bgnBlock = 0;
  483. uint32_t endBlock = binFile.fileSize()/512 - 1;
  484. // find last used block.
  485. while (bgnBlock < endBlock) {
  486. uint32_t midBlock = (bgnBlock + endBlock + 1)/2;
  487. binFile.seekSet(512*midBlock);
  488. if (binFile.read(&count, 2) != 2) error("read");
  489. if (count == 0 || count > DATA_DIM) {
  490. endBlock = midBlock - 1;
  491. } else {
  492. bgnBlock = midBlock;
  493. }
  494. }
  495. // truncate after last used block.
  496. if (!binFile.truncate(512*(bgnBlock + 1))) {
  497. error("Truncate " TMP_FILE_NAME " failed");
  498. }
  499. renameBinFile();
  500. }
  501. //-----------------------------------------------------------------------------
  502. void renameBinFile() {
  503. while (sd.exists(binName)) {
  504. if (binName[BASE_NAME_SIZE + 1] != '9') {
  505. binName[BASE_NAME_SIZE + 1]++;
  506. } else {
  507. binName[BASE_NAME_SIZE + 1] = '0';
  508. if (binName[BASE_NAME_SIZE] == '9') {
  509. error("Can't create file name");
  510. }
  511. binName[BASE_NAME_SIZE]++;
  512. }
  513. }
  514. if (!binFile.rename(sd.vwd(), binName)) {
  515. error("Can't rename file");
  516. }
  517. Serial.print(F("File renamed: "));
  518. Serial.println(binName);
  519. Serial.print(F("File size: "));
  520. Serial.print(binFile.fileSize()/512);
  521. Serial.println(F(" blocks"));
  522. }
  523. //------------------------------------------------------------------------------
  524. void testSensor() {
  525. const uint32_t interval = 200000;
  526. int32_t diff;
  527. data_t data;
  528. Serial.println(F("\nTesting - type any character to stop\n"));
  529. // Wait for Serial Idle.
  530. delay(1000);
  531. printHeader(&Serial);
  532. uint32_t m = micros();
  533. while (!Serial.available()) {
  534. m += interval;
  535. do {
  536. diff = m - micros();
  537. } while (diff > 0);
  538. acquireData(&data);
  539. printData(&Serial, &data);
  540. }
  541. }
  542. //------------------------------------------------------------------------------
  543. void setup(void) {
  544. if (ERROR_LED_PIN >= 0) {
  545. pinMode(ERROR_LED_PIN, OUTPUT);
  546. }
  547. Serial.begin(9600);
  548. // Wait for USB Serial
  549. while (!Serial) {
  550. SysCall::yield();
  551. }
  552. Serial.print(F("\nFreeStack: "));
  553. Serial.println(FreeStack());
  554. Serial.print(F("Records/block: "));
  555. Serial.println(DATA_DIM);
  556. if (sizeof(block_t) != 512) {
  557. error("Invalid block size");
  558. }
  559. // Allow userSetup access to SPI bus.
  560. pinMode(SD_CS_PIN, OUTPUT);
  561. digitalWrite(SD_CS_PIN, HIGH);
  562. // Setup sensors.
  563. userSetup();
  564. // Initialize at the highest speed supported by the board that is
  565. // not over 50 MHz. Try a lower speed if SPI errors occur.
  566. if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
  567. sd.initErrorPrint(&Serial);
  568. fatalBlink();
  569. }
  570. // recover existing tmp file.
  571. if (sd.exists(TMP_FILE_NAME)) {
  572. Serial.println(F("\nType 'Y' to recover existing tmp file " TMP_FILE_NAME));
  573. while (!Serial.available()) {
  574. SysCall::yield();
  575. }
  576. if (Serial.read() == 'Y') {
  577. recoverTmpFile();
  578. } else {
  579. error("'Y' not typed, please manually delete " TMP_FILE_NAME);
  580. }
  581. }
  582. }
  583. //------------------------------------------------------------------------------
  584. void loop(void) {
  585. // Read any Serial data.
  586. do {
  587. delay(10);
  588. } while (Serial.available() && Serial.read() >= 0);
  589. Serial.println();
  590. Serial.println(F("type:"));
  591. Serial.println(F("b - open existing bin file"));
  592. Serial.println(F("c - convert file to csv"));
  593. Serial.println(F("d - dump data to Serial"));
  594. Serial.println(F("e - overrun error details"));
  595. Serial.println(F("l - list files"));
  596. Serial.println(F("r - record data"));
  597. Serial.println(F("t - test without logging"));
  598. while(!Serial.available()) {
  599. SysCall::yield();
  600. }
  601. #if WDT_YIELD_TIME_MICROS
  602. Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
  603. SysCall::halt();
  604. #endif
  605. char c = tolower(Serial.read());
  606. // Discard extra Serial data.
  607. do {
  608. delay(10);
  609. } while (Serial.available() && Serial.read() >= 0);
  610. if (ERROR_LED_PIN >= 0) {
  611. digitalWrite(ERROR_LED_PIN, LOW);
  612. }
  613. if (c == 'b') {
  614. openBinFile();
  615. } else if (c == 'c') {
  616. binaryToCsv();
  617. } else if (c == 'd') {
  618. dumpData();
  619. } else if (c == 'e') {
  620. checkOverrun();
  621. } else if (c == 'l') {
  622. Serial.println(F("\nls:"));
  623. sd.ls(&Serial, LS_SIZE);
  624. } else if (c == 'r') {
  625. logData();
  626. } else if (c == 't') {
  627. testSensor();
  628. } else {
  629. Serial.println(F("Invalid entry"));
  630. }
  631. }