Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

556 lines
15KB

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