Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

532 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 = 3;
  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) {return ht < (QUEUE_DIM - 1) ? ht + 1 : 0;}
  126. //==============================================================================
  127. // Error messages stored in flash.
  128. #define error(msg) errorFlash(F(msg))
  129. //------------------------------------------------------------------------------
  130. void errorFlash(const __FlashStringHelper* msg) {
  131. sd.errorPrint(msg);
  132. fatalBlink();
  133. }
  134. //------------------------------------------------------------------------------
  135. //
  136. void fatalBlink() {
  137. while (true) {
  138. if (ERROR_LED_PIN >= 0) {
  139. digitalWrite(ERROR_LED_PIN, HIGH);
  140. delay(200);
  141. digitalWrite(ERROR_LED_PIN, LOW);
  142. delay(200);
  143. }
  144. }
  145. }
  146. //==============================================================================
  147. // Convert binary file to CSV file.
  148. void binaryToCsv() {
  149. uint8_t lastPct = 0;
  150. block_t block;
  151. uint32_t t0 = millis();
  152. uint32_t syncCluster = 0;
  153. SdFile csvFile;
  154. char csvName[13];
  155. if (!binFile.isOpen()) {
  156. Serial.println();
  157. Serial.println(F("No current binary file"));
  158. return;
  159. }
  160. binFile.rewind();
  161. // Create a new csvFile.
  162. strcpy(csvName, binName);
  163. strcpy_P(&csvName[BASE_NAME_SIZE + 3], PSTR("CSV"));
  164. if (!csvFile.open(csvName, O_WRITE | O_CREAT | O_TRUNC)) {
  165. error("open csvFile failed");
  166. }
  167. Serial.println();
  168. Serial.print(F("Writing: "));
  169. Serial.print(csvName);
  170. Serial.println(F(" - type any character to stop"));
  171. printHeader(&csvFile);
  172. uint32_t tPct = millis();
  173. while (!Serial.available() && binFile.read(&block, 512) == 512) {
  174. uint16_t i;
  175. if (block.count == 0) break;
  176. if (block.overrun) {
  177. csvFile.print(F("OVERRUN,"));
  178. csvFile.println(block.overrun);
  179. }
  180. for (i = 0; i < block.count; i++) {
  181. printData(&csvFile, &block.data[i]);
  182. }
  183. if (csvFile.curCluster() != syncCluster) {
  184. csvFile.sync();
  185. syncCluster = csvFile.curCluster();
  186. }
  187. if ((millis() - tPct) > 1000) {
  188. uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);
  189. if (pct != lastPct) {
  190. tPct = millis();
  191. lastPct = pct;
  192. Serial.print(pct, DEC);
  193. Serial.println('%');
  194. }
  195. }
  196. if (Serial.available()) break;
  197. }
  198. csvFile.close();
  199. Serial.print(F("Done: "));
  200. Serial.print(0.001*(millis() - t0));
  201. Serial.println(F(" Seconds"));
  202. }
  203. //------------------------------------------------------------------------------
  204. // read data file and check for overruns
  205. void checkOverrun() {
  206. bool headerPrinted = false;
  207. block_t block;
  208. uint32_t bgnBlock, endBlock;
  209. uint32_t bn = 0;
  210. if (!binFile.isOpen()) {
  211. Serial.println();
  212. Serial.println(F("No current binary file"));
  213. return;
  214. }
  215. if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
  216. error("contiguousRange failed");
  217. }
  218. binFile.rewind();
  219. Serial.println();
  220. Serial.println(F("Checking overrun errors - type any character to stop"));
  221. while (binFile.read(&block, 512) == 512) {
  222. if (block.count == 0) break;
  223. if (block.overrun) {
  224. if (!headerPrinted) {
  225. Serial.println();
  226. Serial.println(F("Overruns:"));
  227. Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));
  228. headerPrinted = true;
  229. }
  230. Serial.print(bn);
  231. Serial.print(',');
  232. Serial.print(bgnBlock + bn);
  233. Serial.print(',');
  234. Serial.println(block.overrun);
  235. }
  236. bn++;
  237. }
  238. if (!headerPrinted) {
  239. Serial.println(F("No errors found"));
  240. } else {
  241. Serial.println(F("Done"));
  242. }
  243. }
  244. //------------------------------------------------------------------------------
  245. // dump data file to Serial
  246. void dumpData() {
  247. block_t block;
  248. if (!binFile.isOpen()) {
  249. Serial.println();
  250. Serial.println(F("No current binary file"));
  251. return;
  252. }
  253. binFile.rewind();
  254. Serial.println();
  255. Serial.println(F("Type any character to stop"));
  256. delay(1000);
  257. printHeader(&Serial);
  258. while (!Serial.available() && binFile.read(&block , 512) == 512) {
  259. if (block.count == 0) break;
  260. if (block.overrun) {
  261. Serial.print(F("OVERRUN,"));
  262. Serial.println(block.overrun);
  263. }
  264. for (uint16_t i = 0; i < block.count; i++) {
  265. printData(&Serial, &block.data[i]);
  266. }
  267. }
  268. Serial.println(F("Done"));
  269. }
  270. //------------------------------------------------------------------------------
  271. // log data
  272. // max number of blocks to erase per erase call
  273. uint32_t const ERASE_SIZE = 262144L;
  274. void logData() {
  275. uint32_t bgnBlock, endBlock;
  276. // Allocate extra buffer space.
  277. block_t block[BUFFER_BLOCK_COUNT];
  278. block_t* curBlock = 0;
  279. Serial.println();
  280. // Find unused file name.
  281. if (BASE_NAME_SIZE > 6) {
  282. error("FILE_BASE_NAME too long");
  283. }
  284. while (sd.exists(binName)) {
  285. if (binName[BASE_NAME_SIZE + 1] != '9') {
  286. binName[BASE_NAME_SIZE + 1]++;
  287. } else {
  288. binName[BASE_NAME_SIZE + 1] = '0';
  289. if (binName[BASE_NAME_SIZE] == '9') {
  290. error("Can't create file name");
  291. }
  292. binName[BASE_NAME_SIZE]++;
  293. }
  294. }
  295. // Delete old tmp file.
  296. if (sd.exists(TMP_FILE_NAME)) {
  297. Serial.println(F("Deleting tmp file"));
  298. if (!sd.remove(TMP_FILE_NAME)) {
  299. error("Can't remove tmp file");
  300. }
  301. }
  302. // Create new file.
  303. Serial.println(F("Creating new file"));
  304. binFile.close();
  305. if (!binFile.createContiguous(sd.vwd(),
  306. TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
  307. error("createContiguous failed");
  308. }
  309. // Get the address of the file on the SD.
  310. if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
  311. error("contiguousRange failed");
  312. }
  313. // Use SdFat's internal buffer.
  314. uint8_t* cache = (uint8_t*)sd.vol()->cacheClear();
  315. if (cache == 0) error("cacheClear failed");
  316. // Flash erase all data in the file.
  317. Serial.println(F("Erasing all data"));
  318. uint32_t bgnErase = bgnBlock;
  319. uint32_t endErase;
  320. while (bgnErase < endBlock) {
  321. endErase = bgnErase + ERASE_SIZE;
  322. if (endErase > endBlock) endErase = endBlock;
  323. if (!sd.card()->erase(bgnErase, endErase)) {
  324. error("erase failed");
  325. }
  326. bgnErase = endErase + 1;
  327. }
  328. // Start a multiple block write.
  329. if (!sd.card()->writeStart(bgnBlock, FILE_BLOCK_COUNT)) {
  330. error("writeBegin failed");
  331. }
  332. // Initialize queues.
  333. emptyHead = emptyTail = 0;
  334. fullHead = fullTail = 0;
  335. // Use SdFat buffer for one block.
  336. emptyQueue[emptyHead] = (block_t*)cache;
  337. emptyHead = queueNext(emptyHead);
  338. // Put rest of buffers in the empty queue.
  339. for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) {
  340. emptyQueue[emptyHead] = &block[i];
  341. emptyHead = queueNext(emptyHead);
  342. }
  343. Serial.println(F("Logging - type any character to stop"));
  344. // Wait for Serial Idle.
  345. Serial.flush();
  346. delay(10);
  347. uint32_t bn = 0;
  348. uint32_t t0 = millis();
  349. uint32_t t1 = t0;
  350. uint32_t overrun = 0;
  351. uint32_t overrunTotal = 0;
  352. uint32_t count = 0;
  353. uint32_t maxLatency = 0;
  354. int32_t diff;
  355. // Start at a multiple of interval.
  356. uint32_t logTime = micros()/LOG_INTERVAL_USEC + 1;
  357. logTime *= LOG_INTERVAL_USEC;
  358. bool closeFile = false;
  359. while (1) {
  360. // Time for next data record.
  361. logTime += LOG_INTERVAL_USEC;
  362. if (Serial.available()) closeFile = true;
  363. if (closeFile) {
  364. if (curBlock != 0 && curBlock->count >= 0) {
  365. // Put buffer in full queue.
  366. fullQueue[fullHead] = curBlock;
  367. fullHead = queueNext(fullHead);
  368. curBlock = 0;
  369. }
  370. } else {
  371. if (curBlock == 0 && emptyTail != emptyHead) {
  372. curBlock = emptyQueue[emptyTail];
  373. emptyTail = queueNext(emptyTail);
  374. curBlock->count = 0;
  375. curBlock->overrun = overrun;
  376. overrun = 0;
  377. }
  378. do {
  379. diff = logTime - micros();
  380. } while(diff > 0);
  381. if (diff < -10) error("LOG_INTERVAL_USEC too small");
  382. if (curBlock == 0) {
  383. overrun++;
  384. } else {
  385. acquireData(&curBlock->data[curBlock->count++]);
  386. if (curBlock->count == DATA_DIM) {
  387. fullQueue[fullHead] = curBlock;
  388. fullHead = queueNext(fullHead);
  389. curBlock = 0;
  390. }
  391. }
  392. }
  393. if (fullHead == fullTail) {
  394. // Exit loop if done.
  395. if (closeFile) break;
  396. } else if (!sd.card()->isBusy()) {
  397. // Get address of block to write.
  398. block_t* pBlock = fullQueue[fullTail];
  399. fullTail = queueNext(fullTail);
  400. // Write block to SD.
  401. uint32_t usec = micros();
  402. if (!sd.card()->writeData((uint8_t*)pBlock)) {
  403. error("write data failed");
  404. }
  405. usec = micros() - usec;
  406. t1 = millis();
  407. if (usec > maxLatency) maxLatency = usec;
  408. count += pBlock->count;
  409. // Add overruns and possibly light LED.
  410. if (pBlock->overrun) {
  411. overrunTotal += pBlock->overrun;
  412. if (ERROR_LED_PIN >= 0) {
  413. digitalWrite(ERROR_LED_PIN, HIGH);
  414. }
  415. }
  416. // Move block to empty queue.
  417. emptyQueue[emptyHead] = pBlock;
  418. emptyHead = queueNext(emptyHead);
  419. bn++;
  420. if (bn == FILE_BLOCK_COUNT) {
  421. // File full so stop
  422. break;
  423. }
  424. }
  425. }
  426. if (!sd.card()->writeStop()) {
  427. error("writeStop failed");
  428. }
  429. // Truncate file if recording stopped early.
  430. if (bn != FILE_BLOCK_COUNT) {
  431. Serial.println(F("Truncating file"));
  432. if (!binFile.truncate(512L * bn)) {
  433. error("Can't truncate file");
  434. }
  435. }
  436. if (!binFile.rename(sd.vwd(), binName)) {
  437. error("Can't rename file");
  438. }
  439. Serial.print(F("File renamed: "));
  440. Serial.println(binName);
  441. Serial.print(F("Max block write usec: "));
  442. Serial.println(maxLatency);
  443. Serial.print(F("Record time sec: "));
  444. Serial.println(0.001*(t1 - t0), 3);
  445. Serial.print(F("Sample count: "));
  446. Serial.println(count);
  447. Serial.print(F("Samples/sec: "));
  448. Serial.println((1000.0)*count/(t1-t0));
  449. Serial.print(F("Overruns: "));
  450. Serial.println(overrunTotal);
  451. Serial.println(F("Done"));
  452. }
  453. //------------------------------------------------------------------------------
  454. void setup(void) {
  455. if (ERROR_LED_PIN >= 0) {
  456. pinMode(ERROR_LED_PIN, OUTPUT);
  457. }
  458. Serial.begin(9600);
  459. while (!Serial) {}
  460. Serial.print(F("FreeRam: "));
  461. Serial.println(FreeRam());
  462. Serial.print(F("Records/block: "));
  463. Serial.println(DATA_DIM);
  464. if (sizeof(block_t) != 512) error("Invalid block size");
  465. // initialize file system.
  466. if (!sd.begin(SD_CS_PIN, SPI_FULL_SPEED)) {
  467. sd.initErrorPrint();
  468. fatalBlink();
  469. }
  470. }
  471. //------------------------------------------------------------------------------
  472. void loop(void) {
  473. // discard any input
  474. while (Serial.read() >= 0) {}
  475. Serial.println();
  476. Serial.println(F("type:"));
  477. Serial.println(F("c - convert file to CSV"));
  478. Serial.println(F("d - dump data to Serial"));
  479. Serial.println(F("e - overrun error details"));
  480. Serial.println(F("r - record data"));
  481. while(!Serial.available()) {}
  482. char c = tolower(Serial.read());
  483. // Discard extra Serial data.
  484. do {
  485. delay(10);
  486. } while (Serial.read() >= 0);
  487. if (ERROR_LED_PIN >= 0) {
  488. digitalWrite(ERROR_LED_PIN, LOW);
  489. }
  490. if (c == 'c') {
  491. binaryToCsv();
  492. } else if (c == 'd') {
  493. dumpData();
  494. } else if (c == 'e') {
  495. checkOverrun();
  496. } else if (c == 'r') {
  497. logData();
  498. } else {
  499. Serial.println(F("Invalid entry"));
  500. }
  501. }