Timing accumulated errors?

Hello folks,

I'm trying to setup something that samples at exactly 50 Hz (and outputs to the serial port).

To do this, I'm using the following code:

unsigned long lt, t;
unsigned long theDelay = 20000; // delay 20ms = 50Hz

void setup()
{
      lt = micros();
}

void loop()
{
      // grab the current time
      t = micros();
      
      // every "theDelay" uS, sample it!
      if((t - lt) >= theDelay)
      {
            // inform *exactly* how much time has passed (not always exactly 20,000) (the "timestamp")
            Serial.println(t - lt);
            
            // do whatever here for sampling data
            // and output it and whatnot
            
            // set the last time..
            lt = t;
      }
}

It works fairly well, however I'm finding that if running this for several minutes, the error between how long it says it's been running (ie, adding up all the "timestamps"), and how long my computer says it's been running gets to be over a second - something significant enough to throw my data off.

This is slightly confusing for me, however, as micros() has a resolution of 4us. So, if I'm sampling at 50Hz, I could get up to (4us * 50 = ) 200us error per second. Multiplying that by 4 minutes (240 seconds), I get a maximum error of 48 ms. However, when I actually run it, I get an error of around 1 second over the 4 minutes.

Could the Arduino's crystal really be this much off? I'm using an Arduino Mega by the way.

Is there anything I can do to lessen the time-shifting I'm getting?

Thanks!

Could the Arduino's crystal really be this much off?

Not likely.

Also your timed loop looks good and the error should be no more than +/- 8us total for any number of samples (that is individual sample errors will not add up).

If time spent for every sample exceeds 20,000us however you may never catch up. Serial.print is a slow function so you may want to consider this in your calculations.

Serial.print is a slow function

Good point - in the absence of a "Serial.begin", what is the default baud rate?

(9600 baud is about 1ms per character)

Err, my bad.

Right now I'm using 57600 baud rate, connected to an XBee, and sending an average of 13 characters.

This should only take approximately 2ms, right? And so I have 3 ms for the sampling code, which is just doing a digitalRead. I suppose I could change the digitalReads to direct port manipulation, but I'm not sure if that's the problem.

The timestamp is continually ~20000 (sometimes 20004 or rarely, 20008)

13 characters at 57600 baud would add up to about 2.26ms - so that would not explain the issue then. However I would have expected you to see numbers just above or below 20000 - not consistently 20004 or 20008.

If it is a fixed time skew - you could consider timing it and adjust the delay accordingly.

Is this your actual code:

// set the last time..
            lt = t;

or is it perhaps like this:

// set the last time..
            lt = micros();

@BenF:
It's like the first one.

I made a test sketch (without doing any sampling) to get a better feel for what's going on:

unsigned long lt, t;

void setup()
{
  Serial.begin(57600);
  lt = micros();
}

void loop()
{
  t = micros();
  
  if((t - lt) >= 20000)
  {
    Serial.println(t - lt, DEC);
    lt = t;
  }
}

Running this, I get almost exactly 50Hz (49.989845) over about 5 minutes of running. This makes me inclined to believe that the code I was running inside before was too slow, so I'll look into speeding that up.

However, shouldn't the time difference have made up for this fact? I think I might have been staring at this too long, I'm getting all confused..

However, shouldn't the time difference have made up for this fact?

It should - unless the "inside" code runs consistently longer for the last samples in the timed series.

Another possible source of error is if you disable interrupts for longer than a millisecond. If that is the case, the Arduino will start to miss timer overflow interrupts and slow down the clock (in increments of 1024 microseconds). This would not be noticed by your interval print, but would show up if timed by another clock.