2

I'm building a data logging device with BLE capability using RFduino.

Sensors are read using timer interrupt to a buffer. When the buffer is full, then the whole buffer is written to SD card.

At the same time, I have BLE_Receive to set time of the RTC clock on the device. Also BLE_send that transmit the current time of the necklace, and the freespace available (heartBeat or HB). Calculation of free space in particular might take a long time since it's IO operation.

Of course, reading sensors and writing data to SD card has highest priority, then transmitting heart beat only when the processor is free.

#include <Wire.h>
#include <Adafruit_VCNL4010.h>
#include <SPI.h>
#include <SdFat.h>
#include <TimeLib.h>
#include <RFduinoBLE.h>
#include "PCF8563RTC.h"


#define number_of_ms 100
#define NUM_SAMPLE_BUF    3        // number of data samples in buffer
#define SAMPLESIZEINCHAR 24        // verify at line 104, include null character
#define BUFLEN NUM_SAMPLE_BUF*SAMPLESIZEINCHAR + 1

volatile boolean connectFlag = false;
volatile boolean readSensorFlag = false;
volatile boolean heartBeatReadFlag = false;

// RTC variables
PCF8563RTC rtc;
tmElements_t tm;
time_t epoch;


// SD card variables
#define CHIPSELECT 2
SdFat SD;
File myFile;


// Proximity sensor
Adafruit_VCNL4010 vcnl;
uint16_t proximity;
uint16_t ambientLight;


// Double buffers to transfer data between sensors and SD card
char buffer[2][BUFLEN];
volatile uint16_t bufferCounter;
volatile int inBuf = 0;
volatile bool outBufHasData = false;


void timer_config(void) {
  NRF_TIMER2->TASKS_STOP = 1;  // Stop timer
  NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;  // taken from Nordic dev zone
  NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit;
  NRF_TIMER2->PRESCALER = 9;  // 32us resolution
  NRF_TIMER2->TASKS_CLEAR = 1;  // Clear timer

  // With 32 us ticks, we need to multiply by 31.25 to get milliseconds
  NRF_TIMER2->CC[0] = number_of_ms * 31;
  NRF_TIMER2->CC[0] += number_of_ms / 4;

  NRF_TIMER2->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
  NRF_TIMER2->SHORTS = (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos) 
                        & TIMER_SHORTS_COMPARE0_CLEAR_Msk;

  attachInterrupt(TIMER2_IRQn, TIMER2_Interrupt);
  NRF_TIMER2->TASKS_START = 1;  // Start TIMER
}


void setup() {
  bufferCounter = 0;
  inBuf = 0;
  outBufHasData = false;

  // BLE stack
  RFduinoBLE.deviceName = "andrey";
  RFduinoBLE.begin();

  rtc.init();
  setSyncProvider(rtc.get);
  setSyncInterval(3600);

  // start SD card
  pinMode(CHIPSELECT, OUTPUT);
  if (!SD.begin(CHIPSELECT)) {
    return;
  }

  // Start sensors
  if (!vcnl.begin()) {
    return;
  }

  timer_config();
}


void loop() {
  // Reading data from sensors to a buffer
  if (readSensorFlag) {
    readSensorFlag = false;
    heartBeatReadFlag = true;

    if (bufferCounter < BUFLEN - SAMPLESIZEINCHAR + 1) {
      epoch = now();
      proximity = vcnl.readProximity();
      ambientLight = vcnl.readAmbient();

      // format to be fixed length as opposed to variable length
      // sacrifice overhead in written data with simpler and more robust code
      // since we know exactly how much to increment after each write
      int written = snprintf(&buffer[inBuf][bufferCounter], SAMPLESIZEINCHAR,
            "%10lu,%5d,%5d\n",
            epoch, proximity, ambientLight);

      if (written > 0) {
        bufferCounter += written;
      }
    } else {
      // only switch buffer if out buffer is written to SD card
      if (!outBufHasData) {
        inBuf = inBuf? 0:1;    
        bufferCounter = 0;
        outBufHasData = true;
      }
    }
  }

  // write buffer to SD card
  if (outBufHasData) {
    // create folder if not exist
    char folderPath[3];
    snprintf(folderPath, sizeof(folderPath), "%02d", day());
    if (!SD.exists(folderPath)) {
      SD.mkdir(folderPath);
    }

    // Write buffer to SD card
    char filePath[19];
    snprintf(filePath, sizeof(filePath), "%02d/%02d%02d%02d%02d.csv",
        day(), month(), day(), hour(), minute());
    myFile = SD.open(filePath,  FILE_WRITE);
    if (myFile) {
      myFile.print(buffer[1 - inBuf]);
      myFile.close();
    }
    outBufHasData = false;
  } else {
    if (connectFlag && heartBeatReadFlag) {
        heartBeatReadFlag = false;
        transmitHeartBeat();
    }
  }
}

void transmitHeartBeat(void) {
  uint32_t heartBeat[8];
  heartBeat[0] = rtc.get();
  heartBeat[1] = SD.vol()->freeClusterCount() * SD.vol()->blocksPerCluster();
  RFduinoBLE.send((char*)&heartBeat, 8); 

}

// read sensors periodically at 20Hz
void TIMER2_Interrupt(void) {
  if (NRF_TIMER2->EVENTS_COMPARE[0] != 0) {
    NRF_TIMER2->EVENTS_COMPARE[0] = 0;
    readSensorFlag = true;
  }
}

void RFduinoBLE_onConnect() {
  connectFlag = true;
}

void RFduinoBLE_onDisconnect() {
  connectFlag = false;
}

void RFduinoBLE_onReceive(char* data, int len) {
  memcpy(&epoch, data, sizeof(time_t));
  rtc.set(epoch);
}

Currently my solution is as following: reading happens at the beginning of every interval of 100ms. Every 3 interval, the buffer is full and is written to SD card. During other 2 intervals, the processor is free thus I can get HB and transmit it.

Is there a better way? Could I have transmit HB on demand, i.e. transmit HB only when there's a request for it? How could I represent a request in Bluetooth LE? By setting a bit of BLE_receive?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.