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?