|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- /**
- * 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 13 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"
- //------------------------------------------------------------------------------
- // User data functions. Modify these functions for your data items.
- #include "UserDataType.h" // Edit this include file to change data_t.
-
- // Set useSharedSpi true for use of an SPI sensor.
- const bool useSharedSpi = true;
-
- const uint8_t ADXL345_CS = 9;
-
- const uint8_t POWER_CTL = 0x2D; //Power Control Register
- const uint8_t DATA_FORMAT = 0x31;
- const uint8_t DATAX0 = 0x32; //X-Axis Data 0
- const uint8_t DATAX1 = 0x33; //X-Axis Data 1
- const uint8_t DATAY0 = 0x34; //Y-Axis Data 0
- const uint8_t DATAY1 = 0x35; //Y-Axis Data 1
- const uint8_t DATAZ0 = 0x36; //Z-Axis Data 0
- const uint8_t DATAZ1 = 0x37; //Z-Axis Data 1
-
- void writeADXL345Register(const uint8_t registerAddress, const uint8_t value) {
- SPI.setDataMode(SPI_MODE3);
- digitalWrite(ADXL345_CS, LOW);
- SPI.transfer(registerAddress);
- SPI.transfer(value);
- digitalWrite(ADXL345_CS, HIGH);
- }
-
- void setupADXL345() {
- SPI.begin();
- pinMode(ADXL345_CS, OUTPUT);
- digitalWrite(ADXL345_CS, HIGH);
- //Put the ADXL345 into +/- 4G range by writing the value 0x01 to the DATA_FORMAT register.
- writeADXL345Register(DATA_FORMAT, 0x01);
- //Put the ADXL345 into Measurement Mode by writing 0x08 to the POWER_CTL register.
- writeADXL345Register(POWER_CTL, 0x08); //Measurement mode
- }
-
- // Acquire a data record.
- void acquireData(data_t* data) {
- data->time = micros();
- SPI.setDataMode(SPI_MODE3);
- digitalWrite(ADXL345_CS, LOW);
- // Read multiple bytes so or 0XC0 with address.
- SPI.transfer(DATAX0 | 0XC0);
- data->accel[0] = SPI.transfer(0) | (SPI.transfer(0) << 8);
- data->accel[1] = SPI.transfer(0) | (SPI.transfer(0) << 8);
- data->accel[2] = SPI.transfer(0) | (SPI.transfer(0) << 8);
- digitalWrite(ADXL345_CS, HIGH);
- }
-
- // Print a data record.
- void printData(Print* pr, data_t* data) {
- pr->print(data->time);
- for (int i = 0; i < ACCEL_DIM; i++) {
- pr->write(',');
- pr->print(data->accel[i]);
- }
- pr->println();
- }
-
- // Print data header.
- void printHeader(Print* pr) {
- pr->println(F("time,ax,ay,az"));
- }
- //==============================================================================
- // Start of configuration constants.
- //==============================================================================
- //Interval between data records in microseconds.
- const uint32_t LOG_INTERVAL_USEC = 10000;
- //------------------------------------------------------------------------------
- // 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 SD write
- // overrun errors and logging continues.
- 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. Must be six characters or less.
- #define FILE_BASE_NAME "data"
- //------------------------------------------------------------------------------
- // Buffer definitions.
- //
- // The logger will use SdFat's buffer plus BUFFER_BLOCK_COUNT additional
- // buffers.
- //
- #ifndef RAMEND
- // Assume ARM. Use total of nine 512 byte buffers.
- const uint8_t BUFFER_BLOCK_COUNT = 8;
- //
- #elif RAMEND < 0X8FF
- #error Too little SRAM
- //
- #elif RAMEND < 0X10FF
- // Use total of two 512 byte buffers.
- const uint8_t BUFFER_BLOCK_COUNT = 1;
- //
- #elif RAMEND < 0X20FF
- // Use total of five 512 byte buffers.
- const uint8_t BUFFER_BLOCK_COUNT = 4;
- //
- #else // RAMEND
- // Use total of 13 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 "tmp_log.bin"
-
- // Size of file base name. Must not be larger than six.
- const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
-
- SdFat sd;
-
- SdBaseFile binFile;
-
- char binName[13] = FILE_BASE_NAME "00.bin";
-
- // 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];
- };
-
- const uint8_t QUEUE_DIM = BUFFER_BLOCK_COUNT + 2;
-
- block_t* emptyQueue[QUEUE_DIM];
- uint8_t emptyHead;
- uint8_t emptyTail;
-
- block_t* fullQueue[QUEUE_DIM];
- uint8_t fullHead;
- uint8_t fullTail;
-
- // Advance queue index.
- inline uint8_t queueNext(uint8_t ht) {
- return ht < (QUEUE_DIM - 1) ? ht + 1 : 0;
- }
- //==============================================================================
- // Error messages stored in flash.
- #define error(msg) errorFlash(F(msg))
- //------------------------------------------------------------------------------
- void errorFlash(const __FlashStringHelper* msg) {
- sd.errorPrint(msg);
- fatalBlink();
- }
- //------------------------------------------------------------------------------
- //
- void fatalBlink() {
- while (true) {
- if (ERROR_LED_PIN >= 0) {
- digitalWrite(ERROR_LED_PIN, HIGH);
- delay(200);
- digitalWrite(ERROR_LED_PIN, LOW);
- delay(200);
- }
- }
- }
- //==============================================================================
- // 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[13];
-
- if (!binFile.isOpen()) {
- Serial.println();
- Serial.println(F("No current binary file"));
- return;
- }
- binFile.rewind();
- // Create a new csvFile.
- strcpy(csvName, binName);
- strcpy(&csvName[BASE_NAME_SIZE + 3], "csv");
-
- if (!csvFile.open(csvName, O_WRITE | O_CREAT | O_TRUNC)) {
- error("open csvFile failed");
- }
- Serial.println();
- 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) {
- 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"));
- }
- //------------------------------------------------------------------------------
- // read data file and check for overruns
- void checkOverrun() {
- bool headerPrinted = false;
- block_t block;
- uint32_t bgnBlock, endBlock;
- uint32_t bn = 0;
-
- if (!binFile.isOpen()) {
- Serial.println();
- Serial.println(F("No current binary file"));
- return;
- }
- if (!binFile.contiguousRange(&bgnBlock, &endBlock)) {
- error("contiguousRange failed");
- }
- binFile.rewind();
- Serial.println();
- 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(bgnBlock + bn);
- Serial.print(',');
- Serial.println(block.overrun);
- }
- bn++;
- }
- if (!headerPrinted) {
- Serial.println(F("No errors found"));
- } else {
- Serial.println(F("Done"));
- }
- }
- //------------------------------------------------------------------------------
- // 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
- // max number of blocks to erase per erase call
- uint32_t const ERASE_SIZE = 262144L;
- void logData() {
- uint32_t bgnBlock, endBlock;
-
- // Allocate extra buffer space.
- block_t block[BUFFER_BLOCK_COUNT];
- block_t* curBlock = 0;
- Serial.println();
-
- // Find unused file name.
- if (BASE_NAME_SIZE > 6) {
- error("FILE_BASE_NAME too long");
- }
- 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]++;
- }
- }
- // Delete old tmp file.
- if (sd.exists(TMP_FILE_NAME)) {
- Serial.println(F("Deleting tmp file"));
- if (!sd.remove(TMP_FILE_NAME)) {
- error("Can't remove tmp file");
- }
- }
- // Create new file.
- Serial.println(F("Creating new file"));
- binFile.close();
- if (!binFile.createContiguous(sd.vwd(),
- 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");
- }
- // Use SdFat's internal buffer.
- uint8_t* cache = (uint8_t*)sd.vol()->cacheClear();
- if (cache == 0) {
- error("cacheClear 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;
- }
- // Start a multiple block write.
- if (!sd.card()->writeStart(bgnBlock, FILE_BLOCK_COUNT)) {
- error("writeBegin failed");
- }
- // Set chip select high if other devices use SPI.
- if (useSharedSpi) {
- sd.card()->chipSelectHigh();
- }
- // Initialize queues.
- emptyHead = emptyTail = 0;
- fullHead = fullTail = 0;
-
- // Use SdFat buffer for one block.
- emptyQueue[emptyHead] = (block_t*)cache;
- emptyHead = queueNext(emptyHead);
-
- // Put rest of buffers in the empty queue.
- for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) {
- emptyQueue[emptyHead] = &block[i];
- emptyHead = queueNext(emptyHead);
- }
- Serial.println(F("Logging - type any character to stop"));
- // Wait for Serial Idle.
- Serial.flush();
- delay(10);
- uint32_t bn = 0;
- uint32_t t0 = millis();
- uint32_t t1 = t0;
- uint32_t overrun = 0;
- uint32_t overrunTotal = 0;
- uint32_t count = 0;
- uint32_t maxDelta = 0;
- uint32_t minDelta = 99999;
- uint32_t maxLatency = 0;
-
- // Start at a multiple of interval.
- uint32_t logTime = micros()/LOG_INTERVAL_USEC + 1;
- logTime *= LOG_INTERVAL_USEC;
- bool closeFile = false;
- 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 = queueNext(fullHead);
- curBlock = 0;
- }
- } else {
- if (curBlock == 0 && emptyTail != emptyHead) {
- curBlock = emptyQueue[emptyTail];
- emptyTail = queueNext(emptyTail);
- 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++;
- } else {
- acquireData(&curBlock->data[curBlock->count++]);
- if (curBlock->count == DATA_DIM) {
- fullQueue[fullHead] = curBlock;
- fullHead = queueNext(fullHead);
- curBlock = 0;
- }
- if ((uint32_t)delta > maxDelta) maxDelta = delta;
- if ((uint32_t)delta < minDelta) minDelta = delta;
- }
- }
-
- 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 = queueNext(fullTail);
- // Write block to SD.
- uint32_t usec = micros();
- if (!sd.card()->writeData((uint8_t*)pBlock)) {
- error("write data failed");
- }
- usec = micros() - usec;
- t1 = millis();
- if (usec > maxLatency) {
- maxLatency = usec;
- }
- count += pBlock->count;
-
- // Add overruns and possibly light LED.
- if (pBlock->overrun) {
- overrunTotal += pBlock->overrun;
- if (ERROR_LED_PIN >= 0) {
- digitalWrite(ERROR_LED_PIN, HIGH);
- }
- }
- // Move block to empty queue.
- emptyQueue[emptyHead] = pBlock;
- emptyHead = queueNext(emptyHead);
- bn++;
- if (bn == FILE_BLOCK_COUNT) {
- // File full so stop
- break;
- }
- }
- }
- if (!sd.card()->writeStop()) {
- error("writeStop failed");
- }
- // 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");
- }
- }
- if (!binFile.rename(sd.vwd(), binName)) {
- error("Can't rename file");
- }
- Serial.print(F("File renamed: "));
- Serial.println(binName);
- Serial.print(F("Max block write usec: "));
- Serial.println(maxLatency);
- Serial.print(F("Record time sec: "));
- Serial.println(0.001*(t1 - t0), 3);
- Serial.print(minDelta);
- Serial.print(F(" <= jitter microseconds <= "));
- Serial.println(maxDelta);
- Serial.print(F("Sample count: "));
- Serial.println(count);
- Serial.print(F("Samples/sec: "));
- Serial.println((1000.0)*count/(t1-t0));
- Serial.print(F("Overruns: "));
- Serial.println(overrunTotal);
- Serial.println(F("Done"));
- }
- //------------------------------------------------------------------------------
- void setup(void) {
- if (ERROR_LED_PIN >= 0) {
- pinMode(ERROR_LED_PIN, OUTPUT);
- }
- Serial.begin(9600);
-
- // Wait for USB Serial
- while (!Serial) {
- SysCall::yield();
- }
-
- Serial.print(F("FreeStack: "));
- Serial.println(FreeStack());
- Serial.print(F("Records/block: "));
- Serial.println(DATA_DIM);
- if (sizeof(block_t) != 512) {
- error("Invalid block size");
- }
- // initialize file system.
- if (!sd.begin(SD_CS_PIN, SPI_FULL_SPEED)) {
- sd.initErrorPrint();
- fatalBlink();
- }
- setupADXL345();
- }
- //------------------------------------------------------------------------------
- void loop(void) {
- // discard any input
- do {
- delay(10);
- } while (Serial.read() >= 0);
- Serial.println();
- Serial.println(F("type:"));
- 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("r - record data"));
-
- while(!Serial.available()) {
- SysCall::yield();
- }
- char c = tolower(Serial.read());
-
- // Discard extra Serial data.
- do {
- delay(10);
- } while (Serial.read() >= 0);
-
- if (ERROR_LED_PIN >= 0) {
- digitalWrite(ERROR_LED_PIN, LOW);
- }
- if (c == 'c') {
- binaryToCsv();
- } else if (c == 'd') {
- dumpData();
- } else if (c == 'e') {
- checkOverrun();
- } else if (c == 'r') {
- logData();
- } else {
- Serial.println(F("Invalid entry"));
- }
- }
|