Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

LowLatencyLoggerADXL345.ino 17KB

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