| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 | /** * This program logs data to a binary file.  Functions are included * to convert the binary file to a csv text file. * * Samples are logged at regular intervals.  The maximum logging rate * depends on the quality of your SD card and the time required to * read sensor data.  This example has been tested at 500 Hz with * good SD card on an Uno.  4000 HZ is possible on a Due. * * If your SD card has a long write latency, it may be necessary to use * slower sample rates.  Using a Mega Arduino helps overcome latency * problems since 12 512 byte buffers will be used. * * Data is written to the file using a SD multiple block write command. */#include <SPI.h>#include "SdFat.h"#include "FreeStack.h"#include "UserTypes.h"#ifdef __AVR_ATmega328P__#include "MinimumSerial.h"MinimumSerial MinSerial;#define Serial MinSerial#endif  // __AVR_ATmega328P__//==============================================================================// Start of configuration constants.//==============================================================================// Abort run on an overrun.  Data before the overrun will be saved.#define ABORT_ON_OVERRUN 1//------------------------------------------------------------------------------//Interval between data records in microseconds.const uint32_t LOG_INTERVAL_USEC = 2000;//------------------------------------------------------------------------------// Set USE_SHARED_SPI non-zero for use of an SPI sensor.// May not work for some cards.#ifndef USE_SHARED_SPI#define USE_SHARED_SPI 0#endif  // USE_SHARED_SPI//------------------------------------------------------------------------------// Pin definitions.//// SD chip select pin.const uint8_t SD_CS_PIN = SS;//// Digital pin to indicate an error, set to -1 if not used.// The led blinks for fatal errors. The led goes on solid for// overrun errors and logging continues unless ABORT_ON_OVERRUN// is non-zero.#ifdef ERROR_LED_PIN#undef ERROR_LED_PIN#endif  // ERROR_LED_PINconst int8_t ERROR_LED_PIN = -1;//------------------------------------------------------------------------------// File definitions.//// Maximum file size in blocks.// The program creates a contiguous file with FILE_BLOCK_COUNT 512 byte blocks.// This file is flash erased using special SD commands.  The file will be// truncated if logging is stopped early.const uint32_t FILE_BLOCK_COUNT = 256000;//// log file base name if not defined in UserTypes.h#ifndef FILE_BASE_NAME#define FILE_BASE_NAME "data"#endif  // FILE_BASE_NAME//------------------------------------------------------------------------------// Buffer definitions.//// The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT-1 additional// buffers.//#ifndef RAMEND// Assume ARM. Use total of ten 512 byte buffers.const uint8_t BUFFER_BLOCK_COUNT = 10;//#elif RAMEND < 0X8FF#error Too little SRAM//#elif RAMEND < 0X10FF// Use total of two 512 byte buffers.const uint8_t BUFFER_BLOCK_COUNT = 2;//#elif RAMEND < 0X20FF// Use total of four 512 byte buffers.const uint8_t BUFFER_BLOCK_COUNT = 4;//#else  // RAMEND// Use total of 12 512 byte buffers.const uint8_t BUFFER_BLOCK_COUNT = 12;#endif  // RAMEND//==============================================================================// End of configuration constants.//==============================================================================// Temporary log file.  Will be deleted if a reset or power failure occurs.#define TMP_FILE_NAME FILE_BASE_NAME "##.bin"// Size of file base name.const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;const uint8_t FILE_NAME_DIM  = BASE_NAME_SIZE + 7;char binName[FILE_NAME_DIM] = FILE_BASE_NAME "00.bin";SdFat sd;SdBaseFile binFile;// Number of data records in a block.const uint16_t DATA_DIM = (512 - 4)/sizeof(data_t);//Compute fill so block size is 512 bytes.  FILL_DIM may be zero.const uint16_t FILL_DIM = 512 - 4 - DATA_DIM*sizeof(data_t);struct block_t {  uint16_t count;  uint16_t overrun;  data_t data[DATA_DIM];  uint8_t fill[FILL_DIM];};//==============================================================================// Error messages stored in flash.#define error(msg) {sd.errorPrint(&Serial, F(msg));fatalBlink();}//------------------------------------------------------------------------------//void fatalBlink() {  while (true) {    yield();    if (ERROR_LED_PIN >= 0) {      digitalWrite(ERROR_LED_PIN, HIGH);      delay(200);      digitalWrite(ERROR_LED_PIN, LOW);      delay(200);    }  }}//------------------------------------------------------------------------------// read data file and check for overrunsvoid checkOverrun() {  bool headerPrinted = false;  block_t block;  uint32_t bn = 0;  if (!binFile.isOpen()) {    Serial.println();    Serial.println(F("No current binary file"));    return;  }  binFile.rewind();  Serial.println();  Serial.print(F("FreeStack: "));  Serial.println(FreeStack());  Serial.println(F("Checking overrun errors - type any character to stop"));  while (binFile.read(&block, 512) == 512) {    if (block.count == 0) {      break;    }    if (block.overrun) {      if (!headerPrinted) {        Serial.println();        Serial.println(F("Overruns:"));        Serial.println(F("fileBlockNumber,sdBlockNumber,overrunCount"));        headerPrinted = true;      }      Serial.print(bn);      Serial.print(',');      Serial.print(binFile.firstBlock() + bn);      Serial.print(',');      Serial.println(block.overrun);    }    bn++;  }  if (!headerPrinted) {    Serial.println(F("No errors found"));  } else {    Serial.println(F("Done"));  }}//-----------------------------------------------------------------------------// Convert binary file to csv file.void binaryToCsv() {  uint8_t lastPct = 0;  block_t block;  uint32_t t0 = millis();  uint32_t syncCluster = 0;  SdFile csvFile;  char csvName[FILE_NAME_DIM];  if (!binFile.isOpen()) {    Serial.println();    Serial.println(F("No current binary file"));    return;  }  Serial.println();  Serial.print(F("FreeStack: "));  Serial.println(FreeStack());  // Create a new csvFile.  strcpy(csvName, binName);  strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");  if (!csvFile.open(csvName, O_WRONLY | O_CREAT | O_TRUNC)) {    error("open csvFile failed");  }  binFile.rewind();  Serial.print(F("Writing: "));  Serial.print(csvName);  Serial.println(F(" - type any character to stop"));  printHeader(&csvFile);  uint32_t tPct = millis();  while (!Serial.available() && binFile.read(&block, 512) == 512) {    uint16_t i;    if (block.count == 0 || block.count > DATA_DIM) {      break;    }    if (block.overrun) {      csvFile.print(F("OVERRUN,"));      csvFile.println(block.overrun);    }    for (i = 0; i < block.count; i++) {      printData(&csvFile, &block.data[i]);    }    if (csvFile.curCluster() != syncCluster) {      csvFile.sync();      syncCluster = csvFile.curCluster();    }    if ((millis() - tPct) > 1000) {      uint8_t pct = binFile.curPosition()/(binFile.fileSize()/100);      if (pct != lastPct) {        tPct = millis();        lastPct = pct;        Serial.print(pct, DEC);        Serial.println('%');      }    }    if (Serial.available()) {      break;    }  }  csvFile.close();  Serial.print(F("Done: "));  Serial.print(0.001*(millis() - t0));  Serial.println(F(" Seconds"));}//-----------------------------------------------------------------------------void createBinFile() {  // max number of blocks to erase per erase call  const uint32_t ERASE_SIZE = 262144L;  uint32_t bgnBlock, endBlock;  // Delete old tmp file.  if (sd.exists(TMP_FILE_NAME)) {    Serial.println(F("Deleting tmp file " TMP_FILE_NAME));    if (!sd.remove(TMP_FILE_NAME)) {      error("Can't remove tmp file");    }  }  // Create new file.  Serial.println(F("\nCreating new file"));  binFile.close();  if (!binFile.createContiguous(TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT)) {    error("createContiguous failed");  }  // Get the address of the file on the SD.  if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {    error("contiguousRange failed");  }  // Flash erase all data in the file.  Serial.println(F("Erasing all data"));  uint32_t bgnErase = bgnBlock;  uint32_t endErase;  while (bgnErase < endBlock) {    endErase = bgnErase + ERASE_SIZE;    if (endErase > endBlock) {      endErase = endBlock;    }    if (!sd.card()->erase(bgnErase, endErase)) {      error("erase failed");    }    bgnErase = endErase + 1;  }}//------------------------------------------------------------------------------// dump data file to Serialvoid dumpData() {  block_t block;  if (!binFile.isOpen()) {    Serial.println();    Serial.println(F("No current binary file"));    return;  }  binFile.rewind();  Serial.println();  Serial.println(F("Type any character to stop"));  delay(1000);  printHeader(&Serial);  while (!Serial.available() && binFile.read(&block , 512) == 512) {    if (block.count == 0) {      break;    }    if (block.overrun) {      Serial.print(F("OVERRUN,"));      Serial.println(block.overrun);    }    for (uint16_t i = 0; i < block.count; i++) {      printData(&Serial, &block.data[i]);    }  }  Serial.println(F("Done"));}//------------------------------------------------------------------------------// log datavoid logData() {  createBinFile();  recordBinFile();  renameBinFile();}//------------------------------------------------------------------------------void openBinFile() {  char name[FILE_NAME_DIM];  strcpy(name, binName);  Serial.println(F("\nEnter two digit version"));  Serial.write(name, BASE_NAME_SIZE);  for (int i = 0; i < 2; i++) {    while (!Serial.available()) {     yield();    }    char c = Serial.read();    Serial.write(c);    if (c < '0' || c > '9') {      Serial.println(F("\nInvalid digit"));      return;    }    name[BASE_NAME_SIZE + i] = c;  }  Serial.println(&name[BASE_NAME_SIZE+2]);  if (!sd.exists(name)) {    Serial.println(F("File does not exist"));    return;  }  binFile.close();  strcpy(binName, name);  if (!binFile.open(binName, O_RDONLY)) {    Serial.println(F("open failed"));    return;  }  Serial.println(F("File opened"));}//------------------------------------------------------------------------------void recordBinFile() {  const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 1;  // Index of last queue location.  const uint8_t QUEUE_LAST = QUEUE_DIM - 1;  // Allocate extra buffer space.  block_t block[BUFFER_BLOCK_COUNT - 1];  block_t* curBlock = 0;  block_t* emptyStack[BUFFER_BLOCK_COUNT];  uint8_t emptyTop;  uint8_t minTop;  block_t* fullQueue[QUEUE_DIM];  uint8_t fullHead = 0;  uint8_t fullTail = 0;  // Use SdFat's internal buffer.  emptyStack[0] = (block_t*)sd.vol()->cacheClear();  if (emptyStack[0] == 0) {    error("cacheClear failed");  }  // Put rest of buffers on the empty stack.  for (int i = 1; i < BUFFER_BLOCK_COUNT; i++) {    emptyStack[i] = &block[i - 1];  }  emptyTop = BUFFER_BLOCK_COUNT;  minTop = BUFFER_BLOCK_COUNT;  // Start a multiple block write.  if (!sd.card()->writeStart(binFile.firstBlock())) {    error("writeStart failed");  }  Serial.print(F("FreeStack: "));  Serial.println(FreeStack());  Serial.println(F("Logging - type any character to stop"));  bool closeFile = false;  uint32_t bn = 0;  uint32_t maxLatency = 0;  uint32_t overrun = 0;  uint32_t overrunTotal = 0;  uint32_t logTime = micros();  while(1) {     // Time for next data record.    logTime += LOG_INTERVAL_USEC;    if (Serial.available()) {      closeFile = true;    }    if (closeFile) {      if (curBlock != 0) {        // Put buffer in full queue.        fullQueue[fullHead] = curBlock;        fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;        curBlock = 0;      }    } else {      if (curBlock == 0 && emptyTop != 0) {        curBlock = emptyStack[--emptyTop];        if (emptyTop < minTop) {          minTop = emptyTop;        }        curBlock->count = 0;        curBlock->overrun = overrun;        overrun = 0;      }      if ((int32_t)(logTime - micros()) < 0) {        error("Rate too fast");      }      int32_t delta;      do {        delta = micros() - logTime;      } while (delta < 0);      if (curBlock == 0) {        overrun++;        overrunTotal++;        if (ERROR_LED_PIN >= 0) {          digitalWrite(ERROR_LED_PIN, HIGH);        }#if ABORT_ON_OVERRUN        Serial.println(F("Overrun abort"));        break; #endif  // ABORT_ON_OVERRUN      } else {#if USE_SHARED_SPI        sd.card()->spiStop();#endif  // USE_SHARED_SPI        acquireData(&curBlock->data[curBlock->count++]);#if USE_SHARED_SPI        sd.card()->spiStart();#endif  // USE_SHARED_SPI        if (curBlock->count == DATA_DIM) {          fullQueue[fullHead] = curBlock;          fullHead = fullHead < QUEUE_LAST ? fullHead + 1 : 0;          curBlock = 0;        }      }    }    if (fullHead == fullTail) {      // Exit loop if done.      if (closeFile) {        break;      }    } else if (!sd.card()->isBusy()) {      // Get address of block to write.      block_t* pBlock = fullQueue[fullTail];      fullTail = fullTail < QUEUE_LAST ? fullTail + 1 : 0;      // Write block to SD.      uint32_t usec = micros();      if (!sd.card()->writeData((uint8_t*)pBlock)) {        error("write data failed");      }      usec = micros() - usec;      if (usec > maxLatency) {        maxLatency = usec;      }      // Move block to empty queue.      emptyStack[emptyTop++] = pBlock;      bn++;      if (bn == FILE_BLOCK_COUNT) {        // File full so stop        break;      }    }  }  if (!sd.card()->writeStop()) {    error("writeStop failed");  }  Serial.print(F("Min Free buffers: "));  Serial.println(minTop);  Serial.print(F("Max block write usec: "));  Serial.println(maxLatency);  Serial.print(F("Overruns: "));  Serial.println(overrunTotal);  // Truncate file if recording stopped early.  if (bn != FILE_BLOCK_COUNT) {    Serial.println(F("Truncating file"));    if (!binFile.truncate(512L * bn)) {      error("Can't truncate file");    }  }}//------------------------------------------------------------------------------void recoverTmpFile() {  uint16_t count;  if (!binFile.open(TMP_FILE_NAME, O_RDWR)) {    return;  }  if (binFile.read(&count, 2) != 2 || count != DATA_DIM) {    error("Please delete existing " TMP_FILE_NAME);  }  Serial.println(F("\nRecovering data in tmp file " TMP_FILE_NAME));  uint32_t bgnBlock = 0;  uint32_t endBlock = binFile.fileSize()/512 - 1;  // find last used block.  while (bgnBlock < endBlock) {    uint32_t midBlock = (bgnBlock + endBlock + 1)/2;    binFile.seekSet(512*midBlock);    if (binFile.read(&count, 2) != 2) error("read");    if (count == 0 || count > DATA_DIM) {      endBlock = midBlock - 1;    } else {      bgnBlock = midBlock;    }  }  // truncate after last used block.  if (!binFile.truncate(512*(bgnBlock + 1))) {    error("Truncate " TMP_FILE_NAME " failed");  }  renameBinFile();}//-----------------------------------------------------------------------------void renameBinFile() {  while (sd.exists(binName)) {    if (binName[BASE_NAME_SIZE + 1] != '9') {      binName[BASE_NAME_SIZE + 1]++;    } else {      binName[BASE_NAME_SIZE + 1] = '0';      if (binName[BASE_NAME_SIZE] == '9') {        error("Can't create file name");      }      binName[BASE_NAME_SIZE]++;    }  }  if (!binFile.rename(binName)) {    error("Can't rename file");    }  Serial.print(F("File renamed: "));  Serial.println(binName);  Serial.print(F("File size: "));  Serial.print(binFile.fileSize()/512);  Serial.println(F(" blocks"));}//------------------------------------------------------------------------------void testSensor() {  const uint32_t interval = 200000;  int32_t diff;  data_t data;  Serial.println(F("\nTesting - type any character to stop\n"));  // Wait for Serial Idle.  delay(1000);  printHeader(&Serial);  uint32_t m = micros();  while (!Serial.available()) {    m += interval;    do {      diff = m - micros();    } while (diff > 0);    acquireData(&data);    printData(&Serial, &data);  }}//------------------------------------------------------------------------------void setup(void) {  if (ERROR_LED_PIN >= 0) {    pinMode(ERROR_LED_PIN, OUTPUT);  }  Serial.begin(9600);  // Wait for USB Serial  while (!Serial) {    yield();  }  Serial.print(F("\nFreeStack: "));  Serial.println(FreeStack());  Serial.print(F("Records/block: "));  Serial.println(DATA_DIM);  if (sizeof(block_t) != 512) {    error("Invalid block size");  }  // Allow userSetup access to SPI bus.  pinMode(SD_CS_PIN, OUTPUT);  digitalWrite(SD_CS_PIN, HIGH);  // Setup sensors.  userSetup();  // Initialize at the highest speed supported by the board that is  // not over 50 MHz. Try a lower speed if SPI errors occur.  if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {    sd.initErrorPrint(&Serial);    fatalBlink();  }  // recover existing tmp file.  if (sd.exists(TMP_FILE_NAME)) {    Serial.println(F("\nType 'Y' to recover existing tmp file " TMP_FILE_NAME));    while (!Serial.available()) {      yield();    }    if (Serial.read() == 'Y') {      recoverTmpFile();    } else {      error("'Y' not typed, please manually delete " TMP_FILE_NAME);    }  }}//------------------------------------------------------------------------------void loop(void) {  // Read any Serial data.  do {    delay(10);  } while (Serial.available() && Serial.read() >= 0);  Serial.println();  Serial.println(F("type:"));  Serial.println(F("b - open existing bin file"));  Serial.println(F("c - convert file to csv"));  Serial.println(F("d - dump data to Serial"));  Serial.println(F("e - overrun error details"));  Serial.println(F("l - list files"));  Serial.println(F("r - record data"));  Serial.println(F("t - test without logging"));  while(!Serial.available()) {    yield();  }#if WDT_YIELD_TIME_MICROS  Serial.println(F("LowLatencyLogger can not run with watchdog timer"));  while (true) {}#endif  char c = tolower(Serial.read());  // Discard extra Serial data.  do {    delay(10);  } while (Serial.available() && Serial.read() >= 0);  if (ERROR_LED_PIN >= 0) {    digitalWrite(ERROR_LED_PIN, LOW);  }  if (c == 'b') {    openBinFile();  } else if (c == 'c') {    binaryToCsv();  } else if (c == 'd') {    dumpData();  } else if (c == 'e') {    checkOverrun();  } else if (c == 'l') {    Serial.println(F("\nls:"));    sd.ls(&Serial, LS_SIZE);  } else if (c == 'r') {    logData();  } else if (c == 't') {    testSensor();  } else {    Serial.println(F("Invalid entry"));  }}
 |