Sample Rate Issues Using The delay() Function

I have a major issue regarding my set sample rate and my actual output on two of my data logging projects. Both systems log each sample to an SD card in the form of .csv One system is logging data every 1000 milliseconds (1Hz) and the other system is logging data at 16 milliseconds (60Hz).

After perusing both .csv files after a 10 minute experiment, the number of samples is nowhere near what it should be. For example, the system sampling at 1Hz has a total of 367 samples, which when you divide by 60 I get ~6.12 minutes. But whats more worrying is my other system that was sampling at 60Hz has a total of 8630 samples, and when you do the maths ((8630 -1)/60) I get ~143.82 seconds which is barely 3 mins!

The strange thing is that when graphed using matlab, the actual data does correlate to a degree to what I was doing and where.

If code is needed I can post, but both sketches are fairly large.

You say you're using delay()...?

There's your problem right there.

You delay for 1 second and then do all your work. Does all that work take no time at all? I think not.

Check the BlinkWithoutDelay example to see how to replace delay() with millis(), which will vastly improve the situation.

Phew!

Yes I'm using delay(1000); and delay(17);

At least its something I've done wrong and not a major hardware issue.

Ok, will check it out asap!

Can you post your code?

I've had a look at the blinking and LED without delay but I'm unsure about how to implement it to my code.

Heres one out of the two sketches that uses the delay function at the end of the loop:

#include <LiquidCrystal.h>
#include <SD.h>
#include "Wire.h"
#include "DHT.h"
#include "RTClib.h"
#include <LSM303.h>

int CS_pin = 10;  //  SD chip select
long unsigned int id = 1;  //  Set the ID for the beginning of .csv file.

LiquidCrystal lcd(8, 7, 6, 5, 4, 3);

RTC_DS1307 RTC;

LSM303 compass;

const int xInput = A0;
const int yInput = A1;
const int zInput = A2;
const int buttonPin = 2;

// Raw Ranges:
// initialize to mid-range and allow calibration to
// find the minimum and maximum for each axis
int xRawMin = 401;
int xRawMax = 607;

int yRawMin = 401;
int yRawMax = 616;

int zRawMin = 419;
int zRawMax = 623;

// Take multiple samples to reduce noise
const int sampleSize = 10;

void setup() 
{
  lcd.begin(16, 2);
  analogReference(EXTERNAL);
  Serial.begin(9600);
  Wire.begin();  // Start I2c
  RTC.begin();  // Start RTC
  compass.init();
  compass.enableDefault();

  // Calibration values. Use the Calibrate example program to get the values for
  // your compass.
  compass.m_min.x = -533; 
  compass.m_min.y = -623; 
  compass.m_min.z = -452;
  compass.m_max.x =  603; 
  compass.m_max.y = 493; 
  compass.m_max.z = 508;

  //  Check if card is ready
  if(!SD.begin(CS_pin)) {
    Serial.println("Card Failed");
    Serial.println();
    return;
  }
  Serial.println("Card Ready");
  Serial.println();
}

void loop() 
{
  int xRaw = ReadAxis(xInput);
  int yRaw = ReadAxis(yInput);
  int zRaw = ReadAxis(zInput);

  if (digitalRead(buttonPin) == LOW)
  {
    AutoCalibrate(xRaw, yRaw, zRaw);
  }
  else
  {
    //Serial.print("Raw Ranges: X: ");
    //Serial.print(xRawMin);
    //Serial.print("-");
    //Serial.print(xRawMax);

    //Serial.print(", Y: ");
    //Serial.print(yRawMin);
    //Serial.print("-");
    //Serial.print(yRawMax);

    //Serial.print(", Z: ");
    //Serial.print(zRawMin);
    //Serial.print("-");
    //Serial.print(zRawMax);
    //Serial.println();
    //Serial.print(xRaw);
    //Serial.print(", ");
    //Serial.print(yRaw);
    //Serial.print(", ");
    //Serial.print(zRaw);

    // Convert raw values to 'milli-Gs"
    long xScaled = map(xRaw, xRawMin, xRawMax, -1000, 1000);
    long yScaled = map(yRaw, yRawMin, yRawMax, -1000, 1000);
    long zScaled = map(zRaw, zRawMin, zRawMax, -1000, 1000);

    // re-scale to fractional Gs
    float xAccel = xScaled / 1000.0;
    float yAccel = yScaled / 1000.0;
    float zAccel = zScaled / 1000.0;


    Serial.print("X: ");
    Serial.print(xAccel);
    Serial.print("  Y:");
    Serial.print(yAccel);
    Serial.print("  Z:");
    Serial.println(zAccel);
    Serial.println();

    // LCD print
    lcd.setCursor(0,0);
    lcd.print("      ");
    lcd.setCursor(0,0);
    lcd.print("X") + lcd.print(xAccel);

    lcd.setCursor(7,0);
    lcd.print("      ");
    lcd.setCursor(7,0);
    lcd.print("Y") + lcd.print(yAccel);

    lcd.setCursor(0,1);
    lcd.print("      ");
    lcd.setCursor(0,1);
    lcd.print("Z") + lcd.print(zAccel);

    // DateAndTime();

    DateTime now = RTC.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.println(now.second(), DEC);
    Serial.println();

    // Compass() LSM303

    compass.read();
    int heading = compass.heading((LSM303::vector){
      0,-1,0    }
    );
    Serial.println(heading);
    Serial.println();

    // LCD print
    lcd.setCursor(7,1);
    lcd.print("    ");
    lcd.setCursor(7,1);
    lcd.print(heading) + lcd.print(char(0xdf));

    // SD Card Log

    File logFile = SD.open("ACCEL.csv", FILE_WRITE);

    if(logFile) 
    {
      logFile.print(id);
      logFile.print(",");
      logFile.print(xAccel);
      logFile.print(",");
      logFile.print(yAccel);
      logFile.print(",");
      logFile.print(zAccel);
      logFile.print(",");
      logFile.print(heading);
      logFile.print(",");
      logFile.print(now.hour(), DEC);
      logFile.print(",");
      logFile.print(now.minute(), DEC);
      logFile.print(",");
      logFile.print(now.second(), DEC);
      logFile.print(",");
      logFile.print(now.day(), DEC);
      logFile.print(",");
      logFile.print(now.month(), DEC);
      logFile.print(",");
      logFile.println(now.year(), DEC);
      logFile.close();

      //  LCD print
      lcd.setCursor(12,1);
      lcd.print("  ");
      lcd.setCursor(12,1);
      lcd.print("OK");
    }
    else
    {
      //  LCD print
      lcd.setCursor(12,1);
      lcd.print("   ");
      lcd.setCursor(12,1);
      lcd.print("Nil");

      Serial.println("No SD!");
      Serial.println();
    }

    // Increment ID
    id++;

    //  Sample delay 17 millisecs (60 Hz)  
    delay(17);
  }
}

//
// Read "sampleSize" samples and report the average
//
int ReadAxis(int axisPin)
{
  long reading = 0;
  analogRead(axisPin);
  delay(1);
  for (int i = 0; i < sampleSize; i++)
  {
    reading += analogRead(axisPin);
  }
  return reading/sampleSize;
}

//
// Find the extreme raw readings from each axis
//
void AutoCalibrate(int xRaw, int yRaw, int zRaw)
{
  Serial.println("Calibrate");
  if (xRaw < xRawMin)
  {
    xRawMin = xRaw;
  }
  if (xRaw > xRawMax)
  {
    xRawMax = xRaw;
  }

  if (yRaw < yRawMin)
  {
    yRawMin = yRaw;
  }
  if (yRaw > yRawMax)
  {
    yRawMax = yRaw;
  }

  if (zRaw < zRawMin)
  {
    zRawMin = zRaw;
  }
  if (zRaw > zRawMax)
  {
    zRawMax = zRaw;
  }
}
unsigned long timer = 0;

void loop()
{
if( (long)( millis() - timer ) >= 0  )
  { gatherdata(); }
}
  
void gatherdata()
{
timer = millis() + 17UL; // schedule next data sample for 17 ms hence
// gather data here
}

you print so much that a delay of 17 milliseconds won't be noticed ...

    // LCD print
    lcd.setCursor(0,0);
    lcd.print("      ");
    lcd.setCursor(0,0);
    lcd.print("X") + lcd.print(xAccel);

    lcd.setCursor(7,0);
    lcd.print("      ");
    lcd.setCursor(7,0);
    lcd.print("Y") + lcd.print(yAccel);

    lcd.setCursor(0,1);
    lcd.print("      ");
    lcd.setCursor(0,1);
    lcd.print("Z") + lcd.print(zAccel);

can be made faster, using only one setCursor per string.

    // LCD print
    lcd.setCursor(0, 0);
    lcd.print('X');   //  single char  'X'  is faster than string with one char "X"
    lcd.print(xAccel);
    lcd.print("     ");

    others similar

if you remember the previous value you can check if you need to update. you win a lot of time.

Same is true for

    // DateAndTime();

    DateTime now = RTC.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.println(now.second(), DEC);
    Serial.println();

I expect the first 3 fields will change only once per day or less, why print them every loop..?

if you want this loop to run 60x per second, get rid of all what is not needed, something like this and check if you can get 60 readings per second even without the delay.

void loop() 
{
  int xRaw = ReadAxis(xInput);
  int yRaw = ReadAxis(yInput);
  int zRaw = ReadAxis(zInput);

  if (digitalRead(buttonPin) == LOW)
  {
    AutoCalibrate(xRaw, yRaw, zRaw);
  }
  else
  {
     // Convert raw values to 'milli-Gs", re-scaled to fractional Gs
    float xAccel = map(xRaw, xRawMin, xRawMax, -1000, 1000)/1000.0;
    float yAccel = map(yRaw, yRawMin, yRawMax, -1000, 1000)/1000.0;
    float zAccel = map(zRaw, zRawMin, zRawMax, -1000, 1000)/1000.0;

    compass.read();
    int heading = compass.heading((LSM303::vector){ 0,-1,0 } );

    File logFile = SD.open("ACCEL.csv", FILE_WRITE);

    if(logFile) 
    {
      logFile.print(now.time(), DEC);  // one long int representing the time
      logFile.print(",");
      logFile.print(xAccel);
      logFile.print(",");
      logFile.print(yAccel);
      logFile.print(",");
      logFile.print(zAccel);
      logFile.print(",");
      logFile.println(heading);
      logFile.close();
    }
    else
    {
      Serial.print(now.time());
      Serial.println("  No SD!");
      Serial.println();
    }

    // delay(17);
  }
}