DS 1307 stays behind in time day by day...

Hi All,

I am having an Arduino Uno Rev.3 connected with an RTC DS1307 and an LCD 2x16.
I am displaying the time and date using the RTC on the LCD.
After a week I noticed that the time displayed in the LCD and the time in my PC have a difference of a few minutes.
For example: PC time --> 11:04:17 and my RTC -->11:01:41
My RTC is not keeping the time as it should be but stays day by day a little behind from the "actual" time.
Do you have any ideas on how I can synchronize my RTC with my PC system time?

Many Thanks!

The Time library, Arduino Playground - Time has a Processing sketch that runs on the PC for this purpose. Have not used it myself. I might search the forum a bit too, as this is a question that comes up occasionally and there are likely other solutions available.

BTW, if your example is typical of your actual observations, i.e. a 156 second difference in a week's time, that is an unusually large error (258 ppm). I'd expect a DS1307 to be more accurate than that by a factor of ten or more. It might be worth posting your code. (I'm assuming the PC is being kept accurate via NTP, etc.)

After a week I noticed that the time displayed in the LCD and the time in my PC have a difference of a few minutes.
For example: PC time --> 11:04:17 and my RTC -->11:01:41

I have always found the DS1307 to be a poor keeper of time.
The way I improved the timekeeping in a standalone project with no computer connection was to work out how much time the RTC loses per day and then at the midnight hour add to the current RTC time and save it. You could modify this to update hourly instead of daily.

//Enumerations
const int driftComp = 9;    //Seconds to adjust clock by per day to compensate for RTC drift
const int delayComp = 500;  //Micro seconds to delay clock adjust by (driftComp * 1000) - delayCom = 8.5 seconds per 24 hours

void adjustClock(){
    static boolean driftApplied = false;

    RTC.readClock();
    int h=RTC.getHours();

    // RTC Drift compensation
    if(h == 0)                              //Is it the midnight hour
    {
        if(!driftApplied) {                 //Have I already done drift compensation
            int seconds = RTC.getSeconds(); //Get seconds
            seconds = seconds + driftComp;  //Apply drift
            delay(delayComp);               //Apply delay
            RTC.setSeconds(seconds % 60);   //Put seconds
            RTC.setClock();                 //Set clock
            driftApplied=true;              //Signal drift applied
        }
    }
    else{                                   //Not midnight hour
        driftApplied = false;               //Reset drift applied
    }
}

Thank you Jack and Riva for your answers.
Riva because I am quite new to the Arduino. How can I use your code?
What I am asking is how can I use void adjustClock() function on my program?

My code is a very simple code and i state it below:

// Date and time functions using a DS1307 RTC connected via I2C and Wire lib
#include <LiquidCrystal.h>
#include <Wire.h>
#include "RTClib.h"
 
RTC_DS1307 RTC;
LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);
void setup () {
  
  lcd.begin(16, 2);
  lcd.clear();
  Serial.begin(57600);
  Wire.begin();
  RTC.begin();
 
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    //RTC.adjust(DateTime(__DATE__, __TIME__));
  }
 
}
 
void loop () {
    
  DateTime now = RTC.now();
   
  lcd.clear(); // Clear the LCD screen
  lcd.print("Time: ");
  if (now.hour() < 10) lcd.print("0"); // Check if we need to add leading zero
  lcd.print(now.hour(), DEC); // Output current hour
  lcd.print(":");
  if (now.minute() < 10) lcd.print("0"); // Check if we need to add leading zero
  lcd.print(now.minute(), DEC); // Output current minute
  lcd.print(":");
  if (now.second() < 10) lcd.print("0"); // Check if we need to add leading zero
  lcd.print(now.second(), DEC);
  lcd.setCursor(0, 1);
  lcd.print("Date: ");
  if (now.day() < 10) lcd.print("0"); // Check if we need to add leading zero
  lcd.print(now.day(), DEC); // Output current hour
  lcd.print(":");
  if (now.month() < 10) lcd.print("0"); // Check if we need to add leading zero
  lcd.print(now.month(), DEC); // Output current minute
  lcd.print(":");
  if (now.year() < 10) lcd.print("0"); // Check if we need to add leading zero
  lcd.print(now.year(), DEC);
  
  Serial.println();
  delay(1000);
  lcd.clear(); 
}

Thank you!!!

Well the code does seem pretty straightforward. DS1307 accuracy is governed by the crystal's accuracy, circuit layout, and temperature. Many crystals used with DS1307s have specs of ±20 or ±30 ppm. My expectation would be for the circuit to operate reasonably close to that. While I don't consider 20-30 ppm to be great accuracy, practically, it's probably OK for many applications, but represents a minimum standard IMHO.

I use a different RTC library to you but I think I have altered your program to suit. You will need to work out the correct driftComp & delayComp values to suit your DS1307. The values you give indicate a 22.286 second loss per day so once every 24 hours (assuming your clock is 24 hour mode) I add 23 seconds and then wait 714 milliseconds = 22.286 and then set the clock. As you have such a large variation you could maybe alter the code to adjust the clock every hour instead.

// Date and time functions using a DS1307 RTC connected via I2C and Wire lib
#include <LiquidCrystal.h>
#include <Wire.h>
#include "RTClib.h"

const int driftComp = 23;    //Seconds to adjust clock by per day to compensate for RTC drift
const int delayComp = 714;  //Milliseconds to delay clock adjust by (driftComp * 1000) - delayCom = 8.5 seconds per 24 hours

RTC_DS1307 RTC;
LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);

void setup () {
    
    lcd.begin(16, 2);
    lcd.clear();
    Serial.begin(57600);
    Wire.begin();
    RTC.begin();
    
    if (! RTC.isrunning()) {
        Serial.println("RTC is NOT running!");
        // following line sets the RTC to the date & time this sketch was compiled
        //RTC.adjust(DateTime(__DATE__, __TIME__));
    }
    
}

void loop () {
    static boolean driftApplied = false;
    
    DateTime now = RTC.now();
    
    // RTC Drift compensation
    if(now.hour == 0) {                     //Is it the midnight hour
        if(!driftApplied) {                 //Have I already done drift compensation
            int seconds = now.second();     //Get seconds
            seconds = seconds + driftComp;  //Apply drift
            delay(delayComp);               //Subtract delay
            now.Seconds(second % 60);       //Put seconds
            RTC.adjust(now);                //Set clock
            driftApplied=true;              //Signal drift applied
        }
    }
    else{                                   //Not midnight hour
        driftApplied = false;               //Reset drift applied
    }
    
    lcd.clear(); // Clear the LCD screen
    lcd.print("Time: ");
    if (now.hour() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.hour(), DEC); // Output current hour
    lcd.print(":");
    if (now.minute() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.minute(), DEC); // Output current minute
    lcd.print(":");
    if (now.second() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.second(), DEC);
    lcd.setCursor(0, 1);
    lcd.print("Date: ");
    if (now.day() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.day(), DEC); // Output current hour
    lcd.print(":");
    if (now.month() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.month(), DEC); // Output current minute
    lcd.print(":");
    if (now.year() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.year(), DEC);
    
     delay(1000);
}

In the code that sets the time you will notice DATE and TIME. Are you using these to set the time? If so then these are known as preprocessor directives. What happens (in brief) is that the first thing that happens when you start to compile and download your sketch is that these two items are replaced by the current date and time according to your pc. The problem is that then the sketch is compiled and squirted down to the processor, all of which takes time. Hence the RTC ends up several seconds behind the pc.

To get round this you can specify the time you want to set the RTC to:

RTC.adjust(DateTime(DATE, "13:00:00"));

If you remove the test to check if the RTC is running and upload the sketch then as soon as you run the Serial Monitor it will set the time to that specified. So using the example above, about thirty seconds before one o'clock I would upload the sketch and then, a couple of seconds before time, start up the Serial Monitor. Don't run the Serial Monitor again otherwise you will reset the time again!

With a little trial and error you should be able to time it just right!

However, setting it right won't stop the time from drifting over a week due to the factors outlined by Jack. If accuracy is important you might wish to consider a DS3232 based module.

I have a 1307 in my project and it runs for days and days between getting checked out by me. I only display the time accurate to a minute but it matches my PC whenever I check. I used part of a sample sketch that sets the time by sending Unix time to it via serial. I've only set it the once when I got a battery for it, probably a good month ago.

Did I get lucky? :smiley:

Thank you all for your help and ideas!
Regarding Riva:
Thank you for modifying the code. Unfortunately when I am verifying the code I am having an error on line if(now.hour == 0) {                    //Is it the midnight hour.
I changed it to if(now.hour() == 0) {                    //Is it the midnight hour and now it gives me an error on   now.seconds(second % 60);      //Put seconds. The error I get is the following:

sketch_oct04c.cpp: In function 'void loop()':
sketch_oct04c:38: error: 'class DateTime' has no member named 'seconds'
sketch_oct04c:38: error: 'second' was not declared in this scope

I have stuck with this error.

My code now is :

// Date and time functions using a DS1307 RTC connected via I2C and Wire lib
#include <LiquidCrystal.h>
#include <Wire.h>
#include "RTClib.h"

const int driftComp = 23;    //Seconds to adjust clock by per day to compensate for RTC drift
const int delayComp = 714;  //Milliseconds to delay clock adjust by (driftComp * 1000) - delayCom = 8.5 seconds per 24 hours

RTC_DS1307 RTC;
 LiquidCrystal lcd(8, 7, 5, 4, 3, 2);

void setup () {
    
    lcd.begin(16, 2);
    lcd.clear();
    Serial.begin(57600);
    Wire.begin();
    RTC.begin();
    
    if (! RTC.isrunning()) {
        Serial.println("RTC is NOT running!");
        // following line sets the RTC to the date & time this sketch was compiled
        //RTC.adjust(DateTime(__DATE__, __TIME__));
    }
    
}

void loop () {
    static boolean driftApplied = false;
    
    DateTime now = RTC.now();
    
    // RTC Drift compensation
    if(now.hour() == 0) {                     //Is it the midnight hour
        if(!driftApplied) {                 //Have I already done drift compensation
            int seconds = now.second();     //Get seconds
            seconds = seconds + driftComp;  //Apply drift
            delay(delayComp);               //Subtract delay
            now.seconds(second % 60);       //Put seconds
            RTC.adjust(now);                //Set clock
            driftApplied=true;              //Signal drift applied
        }
    }
    else{                                   //Not midnight hour
        driftApplied = false;               //Reset drift applied
    }
    
    lcd.clear(); // Clear the LCD screen
    lcd.print("Time: ");
    if (now.hour() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.hour(), DEC); // Output current hour
    lcd.print(":");
    if (now.minute() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.minute(), DEC); // Output current minute
    lcd.print(":");
    if (now.second() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.second(), DEC);
    lcd.setCursor(0, 1);
    lcd.print("Date: ");
    if (now.day() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.day(), DEC); // Output current hour
    lcd.print(":");
    if (now.month() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.month(), DEC); // Output current minute
    lcd.print(":");
    if (now.year() < 10) lcd.print("0"); // Check if we need to add leading zero
    lcd.print(now.year(), DEC);
    
     delay(1000);
}

I have also changed

LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);
to
LiquidCrystal lcd(8, 7, 5, 4, 3, 2);

Regarding Jimmy60:
Your way of dealing with the DS1307 sounds very interesting. Could you please sent me your code to have a look?

Many Thanks to all of you!

            int seconds = now.second();     //Get seconds
            seconds = seconds + driftComp;  //Apply drift
            delay(delayComp);               //Subtract delay
            now.seconds(second % 60);       //Put seconds

The now.second() call with no argument returns the current value. The same method, with a value, sets the value. The compiler is right, just so you know.

One possible issue is that watch 32768Hz crystals are designed for the average temperature of a wristwatch (close to body heat for 2/3 of the time). You might find it keeps better time if kept at 30C rather than room temp.

Also crystals are cut for a specific load-capacitance - if the crystal is not designed for the oscillator it will run somewhat out of spec.

I'm using the Time.h library I found here:

http://www.arduino.cc/playground/Code/time

The included sample, TimeRTCSet, has the method I used to set it.

As far as ambient air temps go my Arduino has spent most of it's running time around 20C or lower. The last week has been closer to 15C. It's in an unheated shop.

Time.h includes a DS1307RTC.h, I notice you aren't using Time.h

stefanosfok:
  now.seconds(second % 60);       //Put seconds. The error I get is the following:

sketch_oct04c.cpp: In function 'void loop()':
sketch_oct04c:38: error: 'class DateTime' has no member named 'seconds'
sketch_oct04c:38: error: 'second' was not declared in this scope

I have stuck with this error.

From the error message try this line instead.

now.second(seconds % 60);

Jimmy60:
I have a 1307 in my project and it runs for days and days between getting checked out by me. I only display the time accurate to a minute but it matches my PC whenever I check. I used part of a sample sketch that sets the time by sending Unix time to it via serial. I've only set it the once when I got a battery for it, probably a good month ago.

Did I get lucky? :smiley:

Yes. Accuracy of a 1307 RTC or any RTC for that matter, is based on the accuracy of the crystal and how well it's matched electrically to the chip. Even your PC internal RTC will drift some amount over time and needs to be reset either manually or via some on-line updating method. Some 1307 designs have a small trimmer cap wired to one of the crystal pins and ground to allow a tweeking control to try and get better long term accuracy but even with that temperature changes will effect long term accuracy.

This story seems appropriate here. The contractor was the dad of someone I used to work with...

A contractor working on a nuclear sub noticed that the ships chronometer was a few minutes off. He set it to WWV, and didn't think any more about it. A few days later he noted that it was off by a few minutes again, this was way out of spec, so he started to get concerned. He set again and kept checking it. A few hours later, the CPO came in, glanced at his wristwatch and reset the chronometer by it.

KeithRB:
This story seems appropriate here. The contractor was the dad of someone I used to work with...

"Man with watch always know what time it is. Man with two watches never quite sure."

stefanosfok:
Do you have any ideas on how I can synchronize my RTC with my PC system time?

Be careful. It's a very slippery slope you're on...

You should check your crystal frequency. I believe the DS1307 uses the external 32.768kHz crystal. These are temperature sensitive and the cheaper ones tend to be less accurate.
There are better TXCO with a self heated or stabilized reference. You might use a trim capacitor on X1 or X2 to trim the frequency. You might just swap out the crystal and get better (or worse) synchronization. I hope this helped.