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_PIN
- const 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 overruns
- void 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 Serial
- void 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 data
- void 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"));
- }
- }
|