2

I'm using a Mega 2560 to communicate with a mixture of analog (i.e. measured via analogRead()) and I2C based sensors. Sampled data is placed in a 16 byte array, then written to an SD card once a 512 byte buffer is completely filled. The problem I'm running into is that when I collect data from a MAX30102 pulse oximeter using

measure.pox = pulseOx.getIR();

from my code below, the cycle time of my collectHR() loop drops to about 20 mS. If I preallocate this spot by using the line directly below it (storing a constant instead of reading a new uint32_t each loop), my cycle time is about half a millisecond. What's confusing to me is that if I cast the uint32_t into a string using dtostrf:

dtostrf(pulseOx.getIR(),3,0,pOxBuf); // read pulseOx, convert it into a 3 byte string

it only takes about 1.2 mS to actually read a sample from the MAX30102. It seems to me that by favoring a struct over a string (from an earlier iteration of this code that wrote all data as a txt file instead of a bin file) so that I can write binary to my SD card, I'm absolutely throttling my speed. Shouldn't working bytewise be more efficient than working with a string? What is happening to the other 18 mS that occurs between reading the unsigned long and placing it into by byte buffer, buffer1? Implementing this code using string arrays instead of a struct, I was able to run at approximately 125 Hz. Now I'm at roughly 50 Hz. I'd appreciate any insight here. Relevant code shown below:

#include <Wire.h>
#include "MAX30105.h"
#include <SPI.h>
#include <SdFat.h>
#define moisture0 A0 // Upper back sensor analog pin
#define moisture1 A7 // Trunk sensor analog pin
#define ECGpin A3 // ECG analog pin

SdFat SD; // replace SD with SDfat.
File sdLog; // placeholder to create datalogger


struct meas // a 16 byte structure to hold all of our measurements
{
  unsigned long mils;
  int ECG;
  uint32_t pox;
  int tempInt;
  int m1;
  int m2;
};
struct meas measure; // create an instance of the above structure

byte buffer1[512];
byte *measureLoc = (byte *) &measure; // to access measure structure as bytes
char fileName[] = "WIHMSlog.bin";

void setup {
    Serial.begin(230400);
}


void loop() {
sdLog = SD.open(fileName, O_WRITE | O_CREAT | O_AT_END); // Create file for the datalogging and verify its existance
collectHR();
sdlog.close()
}


void collectHR() {
  unsigned int loopCount = 0;
  int buffLen = 0; // starting position in buffer
  int buffSize = 16;

  while (loopCount < 3200) { // some multiple of 32 (since 512 byte/sd block divided by 16 bytes per loop cycle = 32 cycles per sd write cycle

    measure.ECG = analogRead(ECGpin); // read ECG and stick the int into measures
    measure.pox = pulseOx.getIR();
    //measure.pox = 5555; // Placeholder value
    measure.mils = micros();
    measure.m1 = analogRead(moisture0);
    measure.m2 = loopCount; // just a test to ensure this is actually iterating in the card

    if (buffLen == 512) { // SD buffer is full, time to write!
      sdLog.write(buffer1, 512);
      buffLen = 0;
    }
    memcpy(buffer1 + buffLen, measureLoc, buffSize); // place the 16 byte data structure into the buffer
    buffLen = buffLen + buffSize; // increase the index size in the array
    loopCount++;
    Serial.println(micros());
  }

}
  • The sequence of operations looks a bit strange. Consider what happens at the very start: buffLen is initially 0, then you immediately increase it to 16 and then you copy the data to that buffer at offset 16. But what about the first 16 bytes? Why did you leave them unused? Also, it is not clear why you even need to constantly pump bytes from measure to buffer1, i.e. from memory to memory. Why don't you just declare an array of 32 struct measure elements and use it directly? – AnT Dec 20 '19 at 1:47
  • Thanks for your input! You're absolutely right that the first 16 bytes remain empty. I had initially thought it would make it easier to determine when each collectHR() loop ended in the file by searching for 16 0's, but now that you point it out it seems like a total waste of memory. I have moved the increment to below. the memcpy call to account for this. I hadn't thought of casting an array of structs; I'll give that a go. Do you have any insight as to why specifically the memcpy of the pulseOx.getIR() call is so much slower than the time it takes to retrieve those 4 bytes from the sensor? – Blake Hewgill Dec 20 '19 at 2:08
  • 1
    use unsigned int for buffLen, because 15*3200 is more than int can take – Juraj Dec 20 '19 at 9:45
  • Are you sure to have 512 byte of free RAM? The SDfat library holds a 512 byte buffer anyway, and another one for the file system itself. It physically writes to SD only full blocks anyway. – DataFiddler Dec 20 '19 at 12:35
  • 1
    Juraj Good catch, thanks! I've amended this above. @DataFiddler I allegedly have about 5800 bytes left to play with. I'm aware SDfat utilizes a 512 byte buffer, but does that mean that every write() call pushes 512 bytes to the card, or that nothing is sent to the card at all until 512 bytes worth of write() are amassed? Sorry millisSet() offended you, I've removed it. I tried to post just the relevant portions of the code but seem to have missed that variable in the initial post. Garbage is removed... – Blake Hewgill Dec 20 '19 at 20:45

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.