Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

583 lines
16KB

  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 13 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. //------------------------------------------------------------------------------
  20. // Set useSharedSpi true for use of an SPI sensor.
  21. const bool useSharedSpi = false;
  22. // File start time in micros.
  23. uint32_t startMicros;
  24. //------------------------------------------------------------------------------
  25. // User data functions. Modify these functions for your data items.
  26. #include "UserDataType.h" // Edit this include file to change data_t.
  27. // Acquire a data record.
  28. void acquireData(data_t* data) {
  29. data->time = micros();
  30. for (int i = 0; i < ADC_DIM; i++) {
  31. data->adc[i] = analogRead(i);
  32. }
  33. }
  34. // Print a data record.
  35. void printData(Print* pr, data_t* data) {
  36. pr->print(data->time - startMicros);
  37. for (int i = 0; i < ADC_DIM; i++) {
  38. pr->write(',');
  39. pr->print(data->adc[i]);
  40. }
  41. pr->println();
  42. }
  43. // Print data header.
  44. void printHeader(Print* pr) {
  45. pr->print(F("time"));
  46. for (int i = 0; i < ADC_DIM; i++) {
  47. pr->print(F(",adc"));
  48. pr->print(i);
  49. }
  50. pr->println();
  51. }
  52. //==============================================================================
  53. // Start of configuration constants.
  54. //==============================================================================
  55. //Interval between data records in microseconds.
  56. const uint32_t LOG_INTERVAL_USEC = 2000;
  57. //------------------------------------------------------------------------------
  58. // Pin definitions.
  59. //
  60. // SD chip select pin.
  61. const uint8_t SD_CS_PIN = SS;
  62. //
  63. // Digital pin to indicate an error, set to -1 if not used.
  64. // The led blinks for fatal errors. The led goes on solid for SD write
  65. // overrun errors and logging continues.
  66. const int8_t ERROR_LED_PIN = -1;
  67. //------------------------------------------------------------------------------
  68. // File definitions.
  69. //
  70. // Maximum file size in blocks.
  71. // The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.
  72. // This file is flash erased using special SD commands. The file will be
  73. // truncated if logging is stopped early.
  74. const uint32_t FILE_BLOCK_COUNT = 256000;
  75. // log file base name. Must be six characters or less.
  76. #define FILE_BASE_NAME "data"
  77. //------------------------------------------------------------------------------
  78. // Buffer definitions.
  79. //
  80. // The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional
  81. // buffers.
  82. //
  83. #ifndef RAMEND
  84. // Assume ARM. Use total of nine 512 byte buffers.
  85. const uint8_t BUFFER_BLOCK_COUNT = 8;
  86. //
  87. #elif RAMEND < 0X8FF
  88. #error Too little SRAM
  89. //
  90. #elif RAMEND < 0X10FF
  91. // Use total of two 512 byte buffers.
  92. const uint8_t BUFFER_BLOCK_COUNT = 1;
  93. //
  94. #elif RAMEND < 0X20FF
  95. // Use total of five 512 byte buffers.
  96. const uint8_t BUFFER_BLOCK_COUNT = 4;
  97. //
  98. #else // RAMEND
  99. // Use total of 13 512 byte buffers.
  100. const uint8_t BUFFER_BLOCK_COUNT = 12;
  101. #endif // RAMEND
  102. //==============================================================================
  103. // End of configuration constants.
  104. //==============================================================================
  105. // Temporary log file. Will be deleted if a reset or power failure occurs.
  106. #define TMP_FILE_NAME "tmp_log.bin"
  107. // Size of file base name. Must not be larger than six.
  108. const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
  109. SdFat sd;
  110. SdBaseFile binFile;
  111. char binName[13] = FILE_BASE_NAME "00.bin";
  112. // Number of data records in a block.
  113. const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t);
  114. //Compute fill so block size is 512 bytes. FILL_DIM may be zero.
  115. const uint16_t FILL_DIM = 512 - 4 - DATA_DIM*sizeof(data_t);
  116. struct block_t {
  117. uint16_t count;
  118. uint16_t overrun;
  119. data_t data[DATA_DIM];
  120. uint8_t fill[FILL_DIM];
  121. };
  122. const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 2;
  123. block_t* emptyQueue[QUEUE_DIM];
  124. uint8_t emptyHead;
  125. uint8_t emptyTail;
  126. block_t* fullQueue[QUEUE_DIM];
  127. uint8_t fullHead;
  128. uint8_t fullTail;
  129. // Advance queue index.
  130. inline uint8_t queueNext(uint8_t ht) {
  131. return ht < (QUEUE_DIM - 1) ? ht + 1 : 0;
  132. }
  133. //==============================================================================
  134. // Error messages stored in flash.
  135. #define error(msg) errorFlash(F(msg))
  136. //------------------------------------------------------------------------------
  137. void errorFlash(const __FlashStringHelper* msg) {
  138. sd.errorPrint(msg);
  139. fatalBlink();
  140. }
  141. //------------------------------------------------------------------------------
  142. //
  143. void fatalBlink() {
  144. while (true) {
  145. if (ERROR_LED_PIN >= 0) {
  146. digitalWrite(ERROR_LED_PIN, HIGH);
  147. delay(200);
  148. digitalWrite(ERROR_LED_PIN, LOW);
  149. delay(200);
  150. }
  151. }
  152. }
  153. //==============================================================================
  154. // Convert binary file to csv file.
  155. void binaryToCsv() {
  156. uint8_t lastPct = 0;
  157. block_t block;
  158. uint32_t t0 = millis();
  159. uint32_t syncCluster = 0;
  160. SdFile csvFile;
  161. char csvName[13];
  162. if (!binFile.isOpen()) {
  163. Serial.println();
  164. Serial.println(F("No current binary file"));
  165. return;
  166. }
  167. binFile.rewind();
  168. // Create a new csvFile.
  169. strcpy(csvName, binName);
  170. strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
  171. if (!csvFile.open(csvName, O_WRITE | O_CREAT | O_TRUNC)) {
  172. error("open csvFile failed");
  173. }
  174. Serial.println();
  175. Serial.print(F("Writing: "));
  176. Serial.print(csvName);
  177. Serial.println(F(" - type any character to stop"));
  178. printHeader(&csvFile);
  179. uint32_t tPct = millis();
  180. while (!Serial.available() && binFile.read(&block, 512) == 512) {
  181. uint16_t i;
  182. if (block.count == 0) {
  183. break;
  184. }
  185. if (block.overrun) {
  186. csvFile.print(F("OVERRUN,"));
  187. csvFile.println(block.overrun);
  188. }
  189. for (i = 0; i < block.count; i++) {
  190. printData(&csvFile, &block.data[i]);
  191. }
  192. if (csvFile.curCluster() != syncCluster) {
  193. csvFile.sync();
  194. syncCluster = csvFile.curCluster();
  195. }
  196. if ((millis() - tPct) > 1000) {
  197. uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
  198. if (pct != lastPct) {
  199. tPct = millis();
  200. lastPct = pct;
  201. Serial.print(pct, DEC);
  202. Serial.println('%');
  203. }
  204. }
  205. if (Serial.available()) {
  206. break;
  207. }
  208. }
  209. csvFile.close();
  210. Serial.print(F("Done: "));
  211. Serial.print(0.001*(millis() - t0));
  212. Serial.println(F(" Seconds"));
  213. }
  214. //------------------------------------------------------------------------------
  215. // read data file and check for overruns
  216. void checkOverrun() {
  217. bool headerPrinted = false;
  218. block_t block;
  219. uint32_t bgnBlock, endBlock;
  220. uint32_t bn = 0;
  221. if (!binFile.isOpen()) {
  222. Serial.println();
  223. Serial.println(F("No current binary file"));
  224. return;
  225. }
  226. if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
  227. error("contiguousRange failed");
  228. }
  229. binFile.rewind();
  230. Serial.println();
  231. Serial.println(F("Checking overrun errors - type any character to stop"));
  232. while (binFile.read(&block, 512) == 512) {
  233. if (block.count == 0) {
  234. break;
  235. }
  236. if (block.overrun) {
  237. if (!headerPrinted) {
  238. Serial.println();
  239. Serial.println(F("Overruns:"));
  240. Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
  241. headerPrinted = true;
  242. }
  243. Serial.print(bn);
  244. Serial.print(',');
  245. Serial.print(bgnBlock + bn);
  246. Serial.print(',');
  247. Serial.println(block.overrun);
  248. }
  249. bn++;
  250. }
  251. if (!headerPrinted) {
  252. Serial.println(F("No errors found"));
  253. } else {
  254. Serial.println(F("Done"));
  255. }
  256. }
  257. //------------------------------------------------------------------------------
  258. // dump data file to Serial
  259. void dumpData() {
  260. block_t block;
  261. if (!binFile.isOpen()) {
  262. Serial.println();
  263. Serial.println(F("No current binary file"));
  264. return;
  265. }
  266. binFile.rewind();
  267. Serial.println();
  268. Serial.println(F("Type any character to stop"));
  269. delay(1000);
  270. printHeader(&Serial);
  271. while (!Serial.available() && binFile.read(&block , 512) == 512) {
  272. if (block.count == 0) {
  273. break;
  274. }
  275. if (block.overrun) {
  276. Serial.print(F("OVERRUN,"));
  277. Serial.println(block.overrun);
  278. }
  279. for (uint16_t i = 0; i < block.count; i++) {
  280. printData(&Serial, &block.data[i]);
  281. }
  282. }
  283. Serial.println(F("Done"));
  284. }
  285. //------------------------------------------------------------------------------
  286. // log data
  287. // max number of blocks to erase per erase call
  288. uint32_t const ERASE_SIZE = 262144L;
  289. void logData() {
  290. uint32_t bgnBlock, endBlock;
  291. // Allocate extra buffer space.
  292. block_t block[BUFFER_BLOCK_COUNT];
  293. block_t* curBlock = 0;
  294. Serial.println();
  295. // Find unused file name.
  296. if (BASE_NAME_SIZE > 6) {
  297. error("FILE_BASE_NAME too long");
  298. }
  299. while (sd.exists(binName)) {
  300. if (binName[BASE_NAME_SIZE + 1] != '9') {
  301. binName[BASE_NAME_SIZE + 1]++;
  302. } else {
  303. binName[BASE_NAME_SIZE + 1] = '0';
  304. if (binName[BASE_NAME_SIZE] == '9') {
  305. error("Can't create file name");
  306. }
  307. binName[BASE_NAME_SIZE]++;
  308. }
  309. }
  310. // Delete old tmp file.
  311. if (sd.exists(TMP_FILE_NAME)) {
  312. Serial.println(F("Deleting tmp file"));
  313. if (!sd.remove(TMP_FILE_NAME)) {
  314. error("Can't remove tmp file");
  315. }
  316. }
  317. // Create new file.
  318. Serial.println(F("Creating new file"));
  319. binFile.close();
  320. if (!binFile.createContiguous(sd.vwd(),
  321. TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
  322. error("createContiguous failed");
  323. }
  324. // Get the address of the file on the SD.
  325. if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
  326. error("contiguousRange failed");
  327. }
  328. // Use SdFat's internal buffer.
  329. uint8_t* cache = (uint8_t*)sd.vol()->cacheClear();
  330. if (cache == 0) {
  331. error("cacheClear failed");
  332. }
  333. // Flash erase all data in the file.
  334. Serial.println(F("Erasing all data"));
  335. uint32_t bgnErase = bgnBlock;
  336. uint32_t endErase;
  337. while (bgnErase < endBlock) {
  338. endErase = bgnErase + ERASE_SIZE;
  339. if (endErase > endBlock) {
  340. endErase = endBlock;
  341. }
  342. if (!sd.card()->erase(bgnErase, endErase)) {
  343. error("erase failed");
  344. }
  345. bgnErase = endErase + 1;
  346. }
  347. // Start a multiple block write.
  348. if (!sd.card()->writeStart(bgnBlock, FILE_BLOCK_COUNT)) {
  349. error("writeBegin failed");
  350. }
  351. // Set chip select high if other devices use SPI.
  352. if (useSharedSpi) {
  353. sd.card()->chipSelectHigh();
  354. }
  355. // Initialize queues.
  356. emptyHead = emptyTail = 0;
  357. fullHead = fullTail = 0;
  358. // Use SdFat buffer for one block.
  359. emptyQueue[emptyHead] = (block_t*)cache;
  360. emptyHead = queueNext(emptyHead);
  361. // Put rest of buffers in the empty queue.
  362. for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) {
  363. emptyQueue[emptyHead] = &block[i];
  364. emptyHead = queueNext(emptyHead);
  365. }
  366. Serial.println(F("Logging - type any character to stop"));
  367. // Wait for Serial Idle.
  368. Serial.flush();
  369. delay(10);
  370. bool closeFile = false;
  371. uint32_t bn = 0;
  372. uint32_t t0 = millis();
  373. uint32_t t1 = t0;
  374. uint32_t overrun = 0;
  375. uint32_t overrunTotal = 0;
  376. uint32_t count = 0;
  377. uint32_t maxDelta = 0;
  378. uint32_t minDelta = 99999;
  379. uint32_t maxLatency = 0;
  380. uint32_t logTime = micros();
  381. // Set time for first record of file.
  382. startMicros = logTime + LOG_INTERVAL_USEC;
  383. while (1) {
  384. // Time for next data record.
  385. logTime += LOG_INTERVAL_USEC;
  386. if (Serial.available()) {
  387. closeFile = true;
  388. }
  389. if (closeFile) {
  390. if (curBlock != 0) {
  391. // Put buffer in full queue.
  392. fullQueue[fullHead] = curBlock;
  393. fullHead = queueNext(fullHead);
  394. curBlock = 0;
  395. }
  396. } else {
  397. if (curBlock == 0 && emptyTail != emptyHead) {
  398. curBlock = emptyQueue[emptyTail];
  399. emptyTail = queueNext(emptyTail);
  400. curBlock->count = 0;
  401. curBlock->overrun = overrun;
  402. overrun = 0;
  403. }
  404. if ((int32_t)(logTime - micros()) < 0) {
  405. error("Rate too fast");
  406. }
  407. int32_t delta;
  408. do {
  409. delta = micros() - logTime;
  410. } while (delta < 0);
  411. if (curBlock == 0) {
  412. overrun++;
  413. } else {
  414. acquireData(&curBlock->data[curBlock->count++]);
  415. if (curBlock->count == DATA_DIM) {
  416. fullQueue[fullHead] = curBlock;
  417. fullHead = queueNext(fullHead);
  418. curBlock = 0;
  419. }
  420. if ((uint32_t)delta > maxDelta) maxDelta = delta;
  421. if ((uint32_t)delta < minDelta) minDelta = delta;
  422. }
  423. }
  424. if (fullHead == fullTail) {
  425. // Exit loop if done.
  426. if (closeFile) {
  427. break;
  428. }
  429. } else if (!sd.card()->isBusy()) {
  430. // Get address of block to write.
  431. block_t* pBlock = fullQueue[fullTail];
  432. fullTail = queueNext(fullTail);
  433. // Write block to SD.
  434. uint32_t usec = micros();
  435. if (!sd.card()->writeData((uint8_t*)pBlock)) {
  436. error("write data failed");
  437. }
  438. usec = micros() - usec;
  439. t1 = millis();
  440. if (usec > maxLatency) {
  441. maxLatency = usec;
  442. }
  443. count += pBlock->count;
  444. // Add overruns and possibly light LED.
  445. if (pBlock->overrun) {
  446. overrunTotal += pBlock->overrun;
  447. if (ERROR_LED_PIN >= 0) {
  448. digitalWrite(ERROR_LED_PIN, HIGH);
  449. }
  450. }
  451. // Move block to empty queue.
  452. emptyQueue[emptyHead] = pBlock;
  453. emptyHead = queueNext(emptyHead);
  454. bn++;
  455. if (bn == FILE_BLOCK_COUNT) {
  456. // File full so stop
  457. break;
  458. }
  459. }
  460. }
  461. if (!sd.card()->writeStop()) {
  462. error("writeStop failed");
  463. }
  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. if (!binFile.rename(sd.vwd(), binName)) {
  472. error("Can't rename file");
  473. }
  474. Serial.print(F("File renamed: "));
  475. Serial.println(binName);
  476. Serial.print(F("Max block write usec: "));
  477. Serial.println(maxLatency);
  478. Serial.print(F("Record time sec: "));
  479. Serial.println(0.001*(t1 - t0), 3);
  480. Serial.print(minDelta);
  481. Serial.print(F(" <= jitter microseconds <= "));
  482. Serial.println(maxDelta);
  483. Serial.print(F("Sample count: "));
  484. Serial.println(count);
  485. Serial.print(F("Samples/sec: "));
  486. Serial.println((1000.0)*count/(t1-t0));
  487. Serial.print(F("Overruns: "));
  488. Serial.println(overrunTotal);
  489. Serial.println(F("Done"));
  490. }
  491. //------------------------------------------------------------------------------
  492. void setup(void) {
  493. if (ERROR_LED_PIN >= 0) {
  494. pinMode(ERROR_LED_PIN, OUTPUT);
  495. }
  496. Serial.begin(9600);
  497. // Wait for USB Serial
  498. while (!Serial) {
  499. SysCall::yield();
  500. }
  501. Serial.print(F("FreeStack: "));
  502. Serial.println(FreeStack());
  503. Serial.print(F("Records/block: "));
  504. Serial.println(DATA_DIM);
  505. if (sizeof(block_t) != 512) {
  506. error("Invalid block size");
  507. }
  508. // initialize file system.
  509. if (!sd.begin(SD_CS_PIN, SPI_FULL_SPEED)) {
  510. sd.initErrorPrint();
  511. fatalBlink();
  512. }
  513. }
  514. //------------------------------------------------------------------------------
  515. void loop(void) {
  516. // discard any input
  517. do {
  518. delay(10);
  519. } while (Serial.read() >= 0);
  520. Serial.println();
  521. Serial.println(F("type:"));
  522. Serial.println(F("c - convert file to csv"));
  523. Serial.println(F("d - dump data to Serial"));
  524. Serial.println(F("e - overrun error details"));
  525. Serial.println(F("r - record data"));
  526. while(!Serial.available()) {
  527. SysCall::yield();
  528. }
  529. char c = tolower(Serial.read());
  530. // Discard extra Serial data.
  531. do {
  532. delay(10);
  533. } while (Serial.read() >= 0);
  534. if (ERROR_LED_PIN >= 0) {
  535. digitalWrite(ERROR_LED_PIN, LOW);
  536. }
  537. if (c == 'c') {
  538. binaryToCsv();
  539. } else if (c == 'd') {
  540. dumpData();
  541. } else if (c == 'e') {
  542. checkOverrun();
  543. } else if (c == 'r') {
  544. logData();
  545. } else {
  546. Serial.println(F("Invalid entry"));
  547. }
  548. }