選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

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