Keeping Accurate Time

Ive put together a little clock using the Time library and a 7-segment display. No big deal right? Wrong. I set it, leave it and within a few hours it is already a few minutes off. Im no genius but I think it should only be off by a few seconds per day, not minutes. I know an RTC is a fix but Im really trying to make it without one.

Try it this way. Keeps quite good time I've found.

unsigned long currentmicros = 0;
unsigned long nextmicros = 0;
unsigned long interval = 10000; // adjusted for my board

byte tens_hours = 0; 
byte ones_hours = 0;  
byte tens_minutes = 0;
byte ones_minutes = 6;
byte tens_seconds = 0;
byte ones_seconds = 0;
byte tenths = 0;
byte hundredths= 0;

byte prior_seconds = 0;

void setup()

{
  Serial.begin(57600);
  nextmicros = micros();
}

void loop()

{

  currentmicros = micros(); // read the time.

  if ((currentmicros - nextmicros) >= interval) // 10 milliseconds have gone by

  {

    hundredths = hundredths +1;

    if (hundredths == 10){
      hundredths = 0;
      tenths = tenths +1;
    }

    if (tenths == 10){
      tenths = 0;
      ones_seconds = ones_seconds +1;
    }

    if (ones_seconds == 10){
      ones_seconds = 0;
      tens_seconds = tens_seconds +1;
    }

    if (tens_seconds == 6){
      tens_seconds = 0;
      ones_minutes = ones_minutes +1;
    }

    if (ones_minutes == 10){
      ones_minutes = 0;
      tens_minutes = tens_minutes +1;
    }

    if (tens_minutes == 6){
      tens_minutes = 0;
      ones_hours = ones_hours +1;
    }

    if (ones_hours == 10){
      ones_hours = 0;
      tens_hours = tens_hours +1;
    }
    if ((tens_hours == 2) && (ones_hours == 4)){
      ones_hours = 0;
      tens_hours = 0;
      delay(1000);
    }

    nextmicros = nextmicros + interval; // update for the next comparison

  }  // end time interval check

  // counters are all updated now, send to display

  if (prior_seconds != ones_seconds){

    Serial.print (tens_hours, DEC);
    Serial.print (" ");
    Serial.print (ones_hours, DEC);
    Serial.print (" : ");
    Serial.print (tens_minutes, DEC);
    Serial.print (" ");
    Serial.print (ones_minutes, DEC);
    Serial.print (" : ");
    Serial.print (tens_seconds, DEC);
    Serial.print (" ");
    Serial.println (ones_seconds, DEC);

    prior_seconds = ones_seconds;   // show time update once/second
  }  // end one second passing check
  
  // do other stuff in the meantime ...

} // end void loop

Using the internal time from the arduino can be inaccurate as the crystal/resonator may be slightly off frequency and can also drift depending on temperature. Set the time and then measure the drift after 24 hours. Then add some code to adjust for this drift amount every hour or day. E.G. If it gained 6 seconds in 24 hours then the code could adjust the time by -0.25 seconds per hour or 6 seconds per 24 hours. Repeat this several times and over longer durations to improve accuracy.

16 MHz crytal equipped Arduino's seem to track US time clock quite well sitting in my living room. Resonator, I don't have as much experience with.

Except the 60 HZ waverform I posted about a while ago, that was pretty dead nuts on.

Hey thanks a lot, Ill give it a try. I picked up most of my arduinos on ebay so its likely a chinese knockoff with a substandard crystal.

Could be.

The Frequency Stability, Frequency Tolerance, can vary quite a bit.
30PPM parts do pretty good

Well, I've had no success. Within 45 minutes, it was off by 15. By adding some of my own code to the end, do I throw of the timing or something? At first I though it was my power supply giving 5.2v, but I don't think that would even do anything anyways. So I've tried it on USB and same results. Not sure whats going on.

You may find this $16 U.S. solution interesting...
Added: cost of GPS serial module from Hong Kong. ... Check eBay
http://forum.arduino.cc/index.php?PHPSESSID=ov3r8hgbhp4sq7bs46kk2pjbj5&topic=199216.0

GPS clock is always "dead on" and rather an interesting project.

Ray

rclymer:
Within 45 minutes, it was off by 15.

Off by 15 minutes?? :astonished:

15 minutes ,that can't be. What'd you do to the code?

GPS, time only accurate if the board can see a satellite.

You may find this $16 U.S. solution interesting...
Added: cost of GPS serial module from Hong Kong. ... Check eBay
GPS time-date - Exhibition / Gallery - Arduino Forum

GPS clock is always "dead on" and rather an interesting project.

Yeah, I know about radio, gps and rtc, Im trying to do this with just arduino.

Off by 15 minutes??

Indeed.

I added some code at the end for reading two buttons to set the time and a switch, nothing too serious and no delays.

When I watch the serial monitor, it flips through the seconds way too slow.

Most Arduinos use a ceramic resonator as the timing source. The frequency tolerance of this is normally +/- 0.5%, which is +/- 18 seconds per hour. So if your timing drift is within that value, it is quite normal. If it is greater, then there may be something wrong with your code. For example, you may be calling delay() to define an interval between operations, and not allowing for the time taken by the operations themselves.

Thre are a number of ways of getting increased accuracy (assumimg your code is correct and the error is due to the resonator being slightly off-frequency):

  1. Use an Arduino that uses a crystal instead of a ceramic resonator, e.g. a Leonardo.

  2. Use a RTC.

  3. Use an Ethernet shield and synchronise your clock to as timer server.

  4. Use a GPS module and synchronise to GPS time (as already suggested).

  5. Use a radio receiver module to synchronise to a radio time standard.

  6. Measure how fast or slow your resonator is, and allow for it in your software (as already suggested).

Load the blink sketch and watch it for a minute or so. It's possible that the oscillator is erratic or otherwise wildly inaccurate. I've seen this happen, and actually built a couple :blush: If this is some cheap knockoff board I wouldn't rule anything out.

Ceramic resonators as typically used in Uno, etc. are on the order of ±0.5% accurate. So worst case, that could be around seven minutes per day.

OTOH crystal oscillators are typically accurate to 20-30 PPM (parts per million) so a few seconds per day. I've gotten lucky and have built some that run within just a few PPM. But that is strictly luck of the draw, a random result of the variation in individual components.

Post your code - as written, my sketch outputs updated time once a second.
If slower, you messed up the sketch somehow.

Sure, here it is. Its still bit sloppy

#include <Wire.h>
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_GFX.h"
#include <CapacitiveSensor.h>

boolean currenthourButton = false;
boolean lasthourButton = false;
boolean currentminuteButton = false;
boolean lastminuteButton = false;

const byte relayPin = 12;
const byte minutePin = 6;
const byte hourPin = 7;

boolean relay = false;

unsigned long buttonmicros = 0;
unsigned long switchmicros = 0;
unsigned long currentmicros = 0;
unsigned long nextmicros = 0;
unsigned long interval = 10000; // adjusted for my board

byte tens_hours = 0; 
byte ones_hours = 0;  
byte tens_minutes = 0;
byte ones_minutes = 0;
byte tens_seconds = 0;
byte ones_seconds = 0;
byte tenths = 0;
byte hundredths= 0;

byte prior_seconds = 0;

Adafruit_7segment matrix = Adafruit_7segment();

CapacitiveSensor   cs_10_11 = CapacitiveSensor(10,11);

void setup()

{
  pinMode(hourPin, INPUT_PULLUP);//hour button
  pinMode(minutePin, INPUT_PULLUP);//minute button
  pinMode(relayPin, OUTPUT);
  Serial.begin(9600);
  matrix.begin(0x70);
  nextmicros = micros();
  switchmicros = micros();
}

void loop()
{
  currentmicros = micros(); // read the time.

  if ((currentmicros - nextmicros) >= interval) // 10 milliseconds have gone by
  {
    hundredths = hundredths +1;

    if (hundredths == 10){
      hundredths = 0;
      tenths = tenths +1;
    }

    if (tenths == 10){
      tenths = 0;
      ones_seconds = ones_seconds +1;
    }

    if (ones_seconds == 10){
      ones_seconds = 0;
      tens_seconds = tens_seconds +1;
    }

    if (tens_seconds == 6){
      tens_seconds = 0;
      ones_minutes = ones_minutes +1;
    }

    if (ones_minutes == 10){
      ones_minutes = 0;
      tens_minutes = tens_minutes +1;
    }

    if (tens_minutes == 6){
      tens_minutes = 0;
      ones_hours = ones_hours +1;
    }

    if (ones_hours == 10){
      ones_hours = 0;
      tens_hours = tens_hours +1;
    }
    if ((tens_hours == 1) && (ones_hours == 3)){
      ones_hours = 1;
      tens_hours = 0;
      
    }

    nextmicros = nextmicros + interval; // update for the next comparison

  }  // end time interval check

  // counters are all updated now, send to display

  if (prior_seconds != ones_seconds){

    Serial.print (tens_hours, DEC);
    Serial.print (" ");
    Serial.print (ones_hours, DEC);
    Serial.print (" : ");
    Serial.print (tens_minutes, DEC);
    Serial.print (" ");
    Serial.print (ones_minutes, DEC);
    Serial.print (" : ");
    Serial.print (tens_seconds, DEC);
    Serial.print (" ");
    Serial.println (ones_seconds, DEC);

    prior_seconds = ones_seconds;   // show time update once/second
  }  // end one second passing check
  
  
///////////////////////////////////////////////////////////
  
  
  
  unsigned long start = millis();
  unsigned long total1 =  cs_10_11.capacitiveSensor(30);
  
  if(total1 > 1000 && (currentmicros - switchmicros >= 200000))
  {
    relay = !relay;
    digitalWrite(12, relay);
    switchmicros = micros();
  }
  
  currenthourButton = debounceH(lasthourButton);
  currentminuteButton = debounceM(lastminuteButton);
  
  if(currenthourButton == LOW && (currentmicros - buttonmicros >= 120000))
  {
      ones_minutes = ones_minutes + 1;
      buttonmicros = micros(); 
  }
  
  if(currentminuteButton == LOW && (currentmicros - buttonmicros >= 120000))
  {  
      ones_hours = ones_hours + 1;
      buttonmicros = micros();
  }
  
  
  if(tens_hours == 0)
  {
    matrix.clear();
  }else{
    matrix.writeDigitNum(0, tens_hours);
  } 
  matrix.writeDigitNum(1,ones_hours);
  matrix.writeDigitNum(3,tens_minutes);
  matrix.writeDigitNum(4,ones_minutes);
  matrix.drawColon(true);
  matrix.writeDisplay();


} // end void loop

boolean debounceH(boolean last)
{
  boolean current = digitalRead(7);
  if(last != current)
  {
    delay(5);
    current = digitalRead(hourPin);
  }
  return current;
}
boolean debounceM(boolean last)
{
  boolean current = digitalRead(6);
  if(last != current)
  {
    delay(5);
    current = digitalRead(minutePin);
  }
  return current;
}

Another thing to try, run the TimeSerial example that comes with the Time library. Start it, sync it once, let it run for an hour. That should indicate whether there is a hardware or software problem.

I don't know what your code does, but if it's not completing in 10mS then you're messing up the time interval testing. delay(5) will screw things up -
You could test for a longer interval, say 100000 for every 100mS and drop the hundredths check, leave more time for your stuff.
Serial.begin(9600); this will slow things too, that's why I had 57600. 115200 is even better.

Time serial wont compile, it doesn't like BYTE. I changed it to DEC and it worked but no time ever synced. I think this arduino is garbage.

I don't know what your code does, but if it's not completing in 10mS then you're messing up the time interval testing. delay(5) will screw things up -
You could test for a longer interval, say 100000 for every 100mS and drop the hundredths check, leave more time for your stuff.
Serial.begin(9600); this will slow things too, that's why I had 57600. 115200 is even better.

the delays are only when a button is pressed, and serial is only at the start, shouldn't that have no effect on the rest of the code? or does it throw off the interval? I tried shortening the interval to compensate and no matter I small I make it, it never speeds up the time?!

Man, is 1.0.5 messed up for COM ports? I used to be able to have 2 IDE open, 2 COM ports - now when I try, the 2nd IDE takes over the 1st COM port.