您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

LowLatencyLogger.ino 16KB

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