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.

613 lines
17KB

  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 = 3;
  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 "tmp_log.bin"
  96. // Size of file base name. Must not be larger than six.
  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) {
  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. Serial.println();
  238. while (sd.exists(binName)) {
  239. if (binName[BASE_NAME_SIZE + 1] != '9') {
  240. binName[BASE_NAME_SIZE + 1]++;
  241. } else {
  242. binName[BASE_NAME_SIZE + 1] = '0';
  243. if (binName[BASE_NAME_SIZE] == '9') {
  244. error("Can't create file name");
  245. }
  246. binName[BASE_NAME_SIZE]++;
  247. }
  248. }
  249. // Delete old tmp file.
  250. if (sd.exists(TMP_FILE_NAME)) {
  251. Serial.println(F("Deleting tmp file"));
  252. if (!sd.remove(TMP_FILE_NAME)) {
  253. error("Can't remove tmp file");
  254. }
  255. }
  256. // Create new file.
  257. Serial.println(F("Creating new file"));
  258. binFile.close();
  259. if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
  260. error("createContiguous failed");
  261. }
  262. // Get the address of the file on the SD.
  263. if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
  264. error("contiguousRange failed");
  265. }
  266. // Flash erase all data in the file.
  267. Serial.println(F("Erasing all data"));
  268. uint32_t bgnErase = bgnBlock;
  269. uint32_t endErase;
  270. while (bgnErase < endBlock) {
  271. endErase = bgnErase + ERASE_SIZE;
  272. if (endErase > endBlock) {
  273. endErase = endBlock;
  274. }
  275. if (!sd.card()->erase(bgnErase, endErase)) {
  276. error("erase failed");
  277. }
  278. bgnErase = endErase + 1;
  279. }
  280. }
  281. //------------------------------------------------------------------------------
  282. // dump data file to Serial
  283. void dumpData() {
  284. block_t block;
  285. if (!binFile.isOpen()) {
  286. Serial.println();
  287. Serial.println(F("No current binary file"));
  288. return;
  289. }
  290. binFile.rewind();
  291. Serial.println();
  292. Serial.println(F("Type any character to stop"));
  293. delay(1000);
  294. printHeader(&Serial);
  295. while (!Serial.available() && binFile.read(&block , 512) == 512) {
  296. if (block.count == 0) {
  297. break;
  298. }
  299. if (block.overrun) {
  300. Serial.print(F("OVERRUN,"));
  301. Serial.println(block.overrun);
  302. }
  303. for (uint16_t i = 0; i < block.count; i++) {
  304. printData(&Serial, &block.data[i]);
  305. }
  306. }
  307. Serial.println(F("Done"));
  308. }
  309. //------------------------------------------------------------------------------
  310. // log data
  311. void logData() {
  312. createBinFile();
  313. recordBinFile();
  314. renameBinFile();
  315. }
  316. //------------------------------------------------------------------------------
  317. void openBinFile() {
  318. char name[FILE_NAME_DIM];
  319. strcpy(name, binName);
  320. Serial.println(F("\nEnter two digit version"));
  321. Serial.write(name, BASE_NAME_SIZE);
  322. for (int i = 0; i < 2; i++) {
  323. while (!Serial.available()) {
  324. SysCall::yield();
  325. }
  326. char c = Serial.read();
  327. Serial.write(c);
  328. if (c < '0' || c > '9') {
  329. Serial.println("\nInvalid digit");
  330. return;
  331. }
  332. name[BASE_NAME_SIZE + i] = c;
  333. }
  334. Serial.println(&name[BASE_NAME_SIZE+2]);
  335. if (!sd.exists(name)) {
  336. Serial.println(F("File does not exist"));
  337. return;
  338. }
  339. binFile.close();
  340. strcpy(binName, name);
  341. if (!binFile.open(binName, O_READ)) {
  342. Serial.println(F("open failed"));
  343. return;
  344. }
  345. Serial.println(F("File opened"));
  346. }
  347. //------------------------------------------------------------------------------
  348. void recordBinFile() {
  349. const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 1;
  350. // Index of last queue location.
  351. const uint8_t QUEUE_LAST = QUEUE_DIM - 1;
  352. // Allocate extra buffer space.
  353. block_t block[BUFFER_BLOCK_COUNT - 1];
  354. block_t* curBlock = 0;
  355. block_t* emptyStack[BUFFER_BLOCK_COUNT];
  356. uint8_t emptyTop;
  357. uint8_t minTop;
  358. block_t* fullQueue[QUEUE_DIM];
  359. uint8_t fullHead = 0;
  360. uint8_t fullTail = 0;
  361. // Use SdFat's internal buffer.
  362. emptyStack[0] = (block_t*)sd.vol()->cacheClear();
  363. if (emptyStack[0] == 0) {
  364. error("cacheClear failed");
  365. }
  366. // Put rest of buffers on the empty stack.
  367. for (int i = 1; i < BUFFER_BLOCK_COUNT; i++) {
  368. emptyStack[i] = &block[i - 1];
  369. }
  370. emptyTop = BUFFER_BLOCK_COUNT;
  371. minTop = BUFFER_BLOCK_COUNT;
  372. // Start a multiple block write.
  373. if (!sd.card()->writeStart(binFile.firstBlock())) {
  374. error("writeBStart failed");
  375. }
  376. Serial.print(F("FreeStack: "));
  377. Serial.println(FreeStack());
  378. Serial.println(F("Logging - type any character to stop"));
  379. bool closeFile = false;
  380. uint32_t bn = 0;
  381. uint32_t maxLatency = 0;
  382. uint32_t overrun = 0;
  383. uint32_t overrunTotal = 0;
  384. uint32_t logTime = micros();
  385. while(1) {
  386. // Time for next data record.
  387. logTime += LOG_INTERVAL_USEC;
  388. if (Serial.available()) {
  389. closeFile = true;
  390. }
  391. if (closeFile) {
  392. if (curBlock != 0) {
  393. // Put buffer in full queue.
  394. fullQueue[fullHead] = curBlock;
  395. fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
  396. curBlock = 0;
  397. }
  398. } else {
  399. if (curBlock == 0 && emptyTop != 0) {
  400. curBlock = emptyStack[--emptyTop];
  401. if (emptyTop < minTop) {
  402. minTop = emptyTop;
  403. }
  404. curBlock->count = 0;
  405. curBlock->overrun = overrun;
  406. overrun = 0;
  407. }
  408. if ((int32_t)(logTime - micros()) < 0) {
  409. error("Rate too fast");
  410. }
  411. int32_t delta;
  412. do {
  413. delta = micros() - logTime;
  414. } while (delta < 0);
  415. if (curBlock == 0) {
  416. overrun++;
  417. overrunTotal++;
  418. if (ERROR_LED_PIN >= 0) {
  419. digitalWrite(ERROR_LED_PIN, HIGH);
  420. }
  421. #if ABORT_ON_OVERRUN
  422. Serial.println(F("Overrun abort"));
  423. break;
  424. #endif // ABORT_ON_OVERRUN
  425. } else {
  426. #if USE_SHARED_SPI
  427. sd.card()->spiStop();
  428. #endif // USE_SHARED_SPI
  429. acquireData(&curBlock->data[curBlock->count++]);
  430. #if USE_SHARED_SPI
  431. sd.card()->spiStart();
  432. #endif // USE_SHARED_SPI
  433. if (curBlock->count == DATA_DIM) {
  434. fullQueue[fullHead] = curBlock;
  435. fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;
  436. curBlock = 0;
  437. }
  438. }
  439. }
  440. if (fullHead == fullTail) {
  441. // Exit loop if done.
  442. if (closeFile) {
  443. break;
  444. }
  445. } else if (!sd.card()->isBusy()) {
  446. // Get address of block to write.
  447. block_t* pBlock = fullQueue[fullTail];
  448. fullTail = fullTail < QUEUE_LAST ? fullTail + 1 : 0;
  449. // Write block to SD.
  450. uint32_t usec = micros();
  451. if (!sd.card()->writeData((uint8_t*)pBlock)) {
  452. error("write data failed");
  453. }
  454. usec = micros() - usec;
  455. if (usec > maxLatency) {
  456. maxLatency = usec;
  457. }
  458. // Move block to empty queue.
  459. emptyStack[emptyTop++] = pBlock;
  460. bn++;
  461. if (bn == FILE_BLOCK_COUNT) {
  462. // File full so stop
  463. break;
  464. }
  465. }
  466. }
  467. if (!sd.card()->writeStop()) {
  468. error("writeStop failed");
  469. }
  470. Serial.print(F("Min Free buffers: "));
  471. Serial.println(minTop);
  472. Serial.print(F("Max block write usec: "));
  473. Serial.println(maxLatency);
  474. Serial.print(F("Overruns: "));
  475. Serial.println(overrunTotal);
  476. // Truncate file if recording stopped early.
  477. if (bn != FILE_BLOCK_COUNT) {
  478. Serial.println(F("Truncating file"));
  479. if (!binFile.truncate(512L * bn)) {
  480. error("Can't truncate file");
  481. }
  482. }
  483. }
  484. //-----------------------------------------------------------------------------
  485. void renameBinFile() {
  486. if (!binFile.rename(sd.vwd(), binName)) {
  487. error("Can't rename file");
  488. }
  489. Serial.print(F("File renamed: "));
  490. Serial.println(binName);
  491. Serial.print("File size: ");
  492. Serial.print(binFile.fileSize()/512);
  493. Serial.println(F(" blocks"));
  494. Serial.print(F("FreeStack: "));
  495. Serial.println(FreeStack());
  496. }
  497. //------------------------------------------------------------------------------
  498. void testSensor() {
  499. const uint32_t interval = 200000;
  500. int32_t diff;
  501. data_t data;
  502. Serial.println(F("\nTesting - type any character to stop\n"));
  503. // Wait for Serial Idle.
  504. delay(1000);
  505. printHeader(&Serial);
  506. uint32_t m = micros();
  507. while (!Serial.available()) {
  508. m += interval;
  509. do {
  510. diff = m - micros();
  511. } while (diff > 0);
  512. acquireData(&data);
  513. printData(&Serial, &data);
  514. }
  515. }
  516. //------------------------------------------------------------------------------
  517. void setup(void) {
  518. if (ERROR_LED_PIN >= 0) {
  519. pinMode(ERROR_LED_PIN, OUTPUT);
  520. }
  521. Serial.begin(9600);
  522. // Wait for USB Serial
  523. while (!Serial) {
  524. SysCall::yield();
  525. }
  526. Serial.print(F("FreeStack: "));
  527. Serial.println(FreeStack());
  528. Serial.print(F("Records/block: "));
  529. Serial.println(DATA_DIM);
  530. if (sizeof(block_t) != 512) {
  531. error("Invalid block size");
  532. }
  533. // Initialize at the highest speed supported by the board that is
  534. // not over 50 MHz. Try a lower speed if SPI errors occur.
  535. if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {
  536. sd.initErrorPrint(&Serial);
  537. fatalBlink();
  538. }
  539. userSetup();
  540. }
  541. //------------------------------------------------------------------------------
  542. void loop(void) {
  543. // Read any Serial data.
  544. do {
  545. delay(10);
  546. } while (Serial.available() && Serial.read() >= 0);
  547. Serial.println();
  548. Serial.println(F("type:"));
  549. Serial.println(F("b - open existing bin file"));
  550. Serial.println(F("c - convert file to csv"));
  551. Serial.println(F("d - dump data to Serial"));
  552. Serial.println(F("e - overrun error details"));
  553. Serial.println(F("l - list files"));
  554. Serial.println(F("r - record data"));
  555. Serial.println(F("t - test without logging"));
  556. while(!Serial.available()) {
  557. SysCall::yield();
  558. }
  559. #if WDT_YIELD_TIME_MICROS
  560. Serial.println(F("LowLatencyLogger can not run with watchdog timer"));
  561. SysCall::halt();
  562. #endif
  563. char c = tolower(Serial.read());
  564. // Discard extra Serial data.
  565. do {
  566. delay(10);
  567. } while (Serial.available() && Serial.read() >= 0);
  568. if (ERROR_LED_PIN >= 0) {
  569. digitalWrite(ERROR_LED_PIN, LOW);
  570. }
  571. if (c == 'b') {
  572. openBinFile();
  573. } else if (c == 'c') {
  574. binaryToCsv();
  575. } else if (c == 'd') {
  576. dumpData();
  577. } else if (c == 'e') {
  578. checkOverrun();
  579. } else if (c == 'l') {
  580. Serial.println(F("\nls:"));
  581. sd.ls(&Serial, LS_SIZE);
  582. } else if (c == 'r') {
  583. logData();
  584. } else if (c == 't') {
  585. testSensor();
  586. } else {
  587. Serial.println(F("Invalid entry"));
  588. }
  589. }