Timer accuracy loss on ATMega328P (Arduino UNO)

Hi all,

I have a GPS-enabled project and I let it run for about 41 hours. In addition to recording GPS time, I’m also using the millis() function to timestamp the instrument’s activities in order to correlate multiple microcontrollers, only one of which is GPS-enabled. So each microcontroller uses millis() to do so. I’m using Arduinos and ChipKit UNOs, so the language is the same.

Now, the GPS timestamp shows 41 hours of activity. The ChipKit’s timestamp also shows 41 hours of activity. However, the Arduino’s timestamp only shows 31 hours of activity - a 10 hour loss over 41 hours. I have higher-accuracy calculations further below.

Looking at the first several lines of readings (emphasis added):

7/11/2012 [b]*1:14:20.70*[/b] 39.54094 -119.81431 1418.10 [b]*00:00:49.6*[/b] 30.20 85943 1.93 -24.66
// skipping about twenty records here
7/11/2012 [b]*1:15:20.70*[/b] 39.54092 -119.81434 1423.60 [b]*00:01:35.5*[/b] 30.30 85939 2.04 -24.00

The first value in bold is the GPS timestamp, the second bold value is the Arduino millis() counter adjusted into HH:MM:SS.S using the following code:

void maketime(unsigned long _T)
{
  int H = _T / 3600000UL;
  int M = (_T % 3600000UL) / 60000;
  float S = (float)((_T % 3600000UL) % 60000) / 1000.0;
  if(H < 10)
  {
    data.print("0");
  }
  data.print(H);
  data.print(":");
  if(M < 10)
  {
    data.print("0");
  }
  data.print(M);
  data.print(":");
  if(S < 10.0)
  {
    data.print("0");
  }
  data.print(S,1);
}

Where _T comes in from the millis() function. I have literally identical code running on the ChipKit and I get accurate timekeeping. So we see on the Arduino that it’s already losing about 14.1 seconds over the course of a minute. Extrapolated out to 41 hours, that corresponds to about 9.69 hours of loss. Since I actually lost 10.01 hours after 41.2167 hours of recording, the timing loss is apparently nonlinear, if only slightly.

Could this be due to an inaccuracy in the timers on the ATMega328P? The clock? This particular Arduino?

If you'd like help, post the entire sketch.

I have modified my post to include all the extra code that shouldn’t affect when the timing readings are taken.

#include <SD.h>
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <ctype.h>
#include <BMP085.h>
#include <Wire.h>

#define RXPIN 2  //GPS defines
#define TXPIN 3
#define GPSBAUD 4800

TinyGPS gps;
SoftwareSerial uart_gps(RXPIN, TXPIN);
void getgps(TinyGPS &gps);
void filesetup();

bmp085 tp;

File data;

char datafile[] = "0711rsnd.txt";
char kmlfile[] = "0711kml.txt";

void setup()
{
  pinMode(8, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(12, INPUT);
  uart_gps.begin(GPSBAUD);
  
  if(!SD.begin(8))
  {
    
  }
  filesetup();
  Wire.begin();
  tp.Init();
}

void loop()
{
  while(uart_gps.available())
  {
    int c = uart_gps.read();
    if(gps.encode(c))
    {
      unsigned long Time = millis();
      getgps(gps);
      data = SD.open(datafile, FILE_WRITE);
      maketime(Time);
      data.print(" ");
      float T = (float)tp.GetTemp() * 0.1;
      data.print(T);
      data.print(" ");
      data.print(tp.GetPressure());
      data.print(" ");
      float H = humidity();
      data.print(H);
      data.print(" ");
      data.println(dewpoint(T,H));
      data.close();
     }
  }
}

void getgps(TinyGPS &gps)
{
  float latitude, longitude, alt;
  gps.f_get_position(&latitude, &longitude);
  alt = gps.f_altitude();
  
  int year;
  byte month, day, hour, minute, second, hundredths;
  gps.crack_datetime(&year,&month,&day,&hour,&minute,&second,&hundredths);
  
  data = SD.open(kmlfile, FILE_WRITE);
  data.print(longitude,5); data.print(","); data.print(latitude,5); data.print(","); data.print(alt); data.print(" ");
  data.close();
  
  data = SD.open(datafile, FILE_WRITE);
  data.print(month, DEC); data.print("/"); data.print(day, DEC); data.print("/"); data.print(year);
  data.print(" ");
  data.print(hour, DEC); data.print(":"); data.print(minute, DEC); data.print(":"); data.print(second, DEC); 
  data.print("."); data.print(hundredths, DEC);
  data.print(" ");
  data.print(latitude,5);
  data.print(" ");
  data.print(longitude,5);
  data.print(" ");
  data.print(alt);
  data.print(" ");
  data.close();
}

void filesetup()
{
  data = SD.open(datafile, FILE_WRITE);
  data.println("##Date Time(UTC) Latitude Longitude Altitude SystemTime TempC Pressure Humidity DewPoint##");
  data.close();
  
  data = SD.open(kmlfile, FILE_WRITE);
  data.close();
}

float humidity()
{
  float _temp = (float)analogRead(1) * 0.001074;
  return (100.0 * _temp);
}

float dewpoint(float T, float RH)
{
  const float a = 17.271;
  const float b = 237.7;
  
  float gamma = ((a * T) / (b + T)) + log(RH / 100);
  
  return ((b * gamma) / (a - gamma));
}

void maketime(unsigned long _T)
{
  unsigned long H = _T / 3600000UL;
  unsigned long M = (_T % 3600000UL) / 60000UL;
  float S = (float)((_T % 3600000UL) % 60000UL) / 1000.0;
  if(H < 10)
  {
    data.print("0");
  }
  data.print(H);
  data.print(":");
  if(M < 10)
  {
    data.print("0");
  }
  data.print(M);
  data.print(":");
  if(S < 10.0)
  {
    data.print("0");
  }
  data.print(S,1);
}

SoftwareSerial disables interrupts while receiving. At 4800 baud, each byte takes just over 2ms (10/4800*1000) to frame. With interrupts disabled for 2ms there is a very high probability that the millis interrupt will underrun. The problem is compounded slightly by anything else interrupt driven (like Wire).

Move the GPS to a hardware USART and the problem will go away. Increase the GPS baud rate and the problem may go away.

This may help... http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html

[quote author=Coding Badly link=topic=114155.msg858980#msg858980 date=1342211292]

SoftwareSerial disables interrupts while receiving. At 4800 baud, each byte takes just over 2ms (10/4800*1000) to frame. With interrupts disabled for 2ms there is a very high probability that the millis interrupt will underrun. The problem is compounded slightly by anything else interrupt driven (like Wire).

Move the GPS to a hardware USART and the problem will go away. Increase the GPS baud rate and the problem may go away.

[/quote]

This solved the problem entirely, thank you. I just have to remember to move the GPS shield toggle from UART to DLINE when I upload new code.