Keeping accurate time

I am using the following code to display a digital clock using my arduino uno:

#include <Time.h>
#include <LiquidCrystal.h>

#define TIME_MSG_LEN 11 // time sync to PC is HEADER followed by Unix time_t as ten ASCII digits
#define TIME_HEADER 'T' // Header tag for serial time sync message
#define TIME_REQUEST 7 // ASCII bell character requests a time sync message

// T1262347200 //noon Jan 1 2010
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
int clockSet = 0;

void setup()
{
Serial.begin(9600);
lcd.begin(16, 2);
}

void loop()
{
if(clockSet == 0)
{
if(Serial.available() )
{
processSyncMessage();
clockSet = 1;
}
}
digitalClockDisplay();
delay(1000);
}

void digitalClockDisplay()
{
lcd.setCursor(1,0);
lcd.print(hour());
lcdPrintDigits(minute());
lcdPrintDigits(second());
}

void lcdPrintDigits(int digits){
// utility function for digital clock display: prints preceding colon and leading 0
lcd.print(":");
if(digits < 10)
lcd.print('0');
lcd.print(digits);
}
void processSyncMessage() {
// if time sync available from serial port, update time and return true
while(Serial.available() >= TIME_MSG_LEN ){ // time message consists of header & 10 ASCII digits
char c = Serial.read() ;
Serial.print(c);
if( c == TIME_HEADER ) {
time_t pctime = 0;
for(int i=0; i < TIME_MSG_LEN -1; i++){
c = Serial.read();
if( c >= '0' && c <= '9'){
pctime = (10 * pctime) + (c - '0') ; // convert digits to a number
}
}
setTime(pctime); // Sync Arduino clock to the time received on the serial port
}
}
}

Most of this is out of the example code for the time.h library. The problem is the clock loses about 2 minutes each day (i.e. it is 2 minutes slower than my computer clock). Any ideas on why this might be? I have cut down the code as much as I can such that it only calls processSyncMessage once (the first time I send the time to the arduino). I thought maybe executing processSyncMessage every time through the loop was slowing it down but that doesn't seem to be the case. Maybe I should change the delay to delayMicroseconds()?

Thanks.

It could be your LCD printing code is just taking about 1ms or so. Then, your delay() function is not 1000ms from the last delay, it's 1000ms from the the last delay plus LCD time.

Try just using millis() directly instead of delay(). Something like (warning...untested code):

uint32_t lastTime;

void loop(void)
{
  if ((millis() - lastTime) >= 1000) {
    lastTime += 1000;
    digitalClockDisplay();
  }
  ......
}

--
The Gadget Shield: accelerometer, RGB LED, IR transmit/receive, speaker, microphone, light sensor, potentiometer, pushbuttons

A better accuracy can be obtained by using a RealTimeClock like the DS1307.

Further you can speed up the connection with the PC by using a higher baudrate Serial.begin(115200); that way it takes less time.

Thanks for the great suggestions. I would like the arduino to run standalone (i.e. not connected to any computer), otherwise I would definitely use the RealTimeClock. I will try the idea of checking the time it takes to write to the display. I was kind of thinking in that direction.

standalone

Is the Arduino connected to the internet (ethernetshield) ?
If yes you can use the NTP to sync time
If not you may use a DCF77 signal (or similar) to get accurate time

Without an external precision clock your Arduino time measurement will only be as accurate as the processor clock. Perhaps a high-precision 16MHz crystal instead of the standard one would help.

It looks like 10ppm (parts per million) is the best you can get off the shelf. That's a little better than a second a day accuracy. You can get such crystals for under $1. Search Google Shopping for "16 MHz crystal 10ppm".

A 10ppm crystall will not give 10ppm accurarcy unless ALL other parameters will fit. Especially the load caps may get it much further out of tune than you would expect. Careless board layout and unstable power conditions will not improve things.
HIt is possible (but not easy) to compensate for frequency offset as well as temperature. However it is very much easier to use a DS3231 (e.g. a Chrono Dot) or to go for a time normal like DCF77 or GPS though.
In case of extraordinary precision requirements have a look at used Trimble Thunderbold Modules at Ebay.

The Uno, unlike the Duemilanove, doesn't use a crystal to clock the microcontroller - you can't rely on millis()/micros() being better than 0.1% accurate or whatever performance ceramic resonators have... This is very unfortunate. It is possible to replace the resonator since the board has pads for a standard crystal and load capacitors but you'll need a steady hand and a solder-sucker to clear the thru-holes.

A quick experiment on my Uno showed the resonator frequency being pulled 0.05% (500ppm) just by putting a finger on it. Only 4.5ppm on the Duemilanove's crystal/caps.

In fact I got motivated enough to replace the resonator on my Uno board, partly to see how tricky it was to do.

Here's the board before modding it:

Then I desoldered the resonator and the unnecesary resistor:

Had some 22pF SM caps (0805 size, about the smallest I like to solder :slight_smile:

And the crystal - there seemed to be insufficient clearance for the standard 49U package from the caps (they weren't tiny enough!) but I wanted it clear of the bare resonator pads anyway to avoid shorts so its sticking up a little from the board:

Finally at some point I will add a blob of glue from a hot-glue gun to support it better mechanically. Overall not too fiddly if you have tackled SM components before. A solder sucker is required to clear the thru-holes for the crystal leads, the tiny resonator and resistor can be heated on alternate sides till it moves and can be pushed off the pads. Only attempt at your own risk of course :wink:

One thing I found out correcting the errors of my 1307, was to keep an eye on my PC's clock.
Standard it's synchronized once a week and it took me a while to notice it was less accurate as my arduino-clock.

Synchronizing it too often per hour with time.windows.com can result in temporary denial of services by the way :*

  1. The microcontroller crystal (or ceramic resonator) IS NOT capable of serving as a real-time clock. It was simply never made for that purpose. You will fight with it the rest of your life if you try to do that.

Nonsense. A crystal oscillator is a crystal oscillator. Unless you specifically use a higher-precision crystal, carefully match your capacitances, and provide some sort of temperatures compensation, a DS1307 "RTC" chip is not going to be any more accurate than a crystal connected directly to the microcontroller. (and you COULD use similar care in implementing the microcontroller crystal oscillator.) (not to say that the RTC chip doesn't have other features.)
(now, there ARE other "RTC modules" that specifically DO attack the accuracy problem, by using fancier RTC chips, or ovens to keep the critical circuitry at a specific temperature, or other mechanisms. One example is the "ChronoDot": http://macetech.com/store/index.php?main_page=product_info&cPath=5&products_id=8 )

The OP hasn't said how accurate he actually needs, and I don't think we've confirmed or eliminated software bugs as the cause of the original inaccuracies. You should be able to get better than 5000ppm (0.5%) even with a ceramic resonator, and 100ppm or better with the microcontroller clock controlled by a crystal and millis() (or with a DS1307 based RTC module and different code.) The "Chronodot" is supposed to be good for about 2.5ppm over a moderate temperature range (and someone has already done the hard parts!)

Well this has generated quite a discussion. Thanks to all for your input. The upshot of all this appears to be that using the arduino as a standalone clock is not practical due to the issues described here. I won't pursue that further and will take the advice of getting an RTC module. That seems to be the most expedient path. I'm not terribly concerned about microsecond accuracy over great lengths of time. I just want it to keep time as well as the digital clock on my nightstand. That shouldn't be too difficult I should think. I was just surprised at how inaccurate the arduino's microcontroller clock is. But at least I understand why now.

kdecker:
The upshot of all this appears to be that using the arduino as a standalone clock is not practical due to the issues described here. I won't pursue that further and will take the advice of getting an RTC module.

I would add that for a USB connected Arduino, syncing to the PC clock is as good as anything you can buy in terms of RTC. You may have to sync frequently (say every hour or so) to maintain exact time, but this is not much different from reading time off a RTC module at regular intervals.

The two minute delay you observed is mainly due to the software issue pointed out by retrolefty above (the logic is incorrect). Change the program logic in line with his advice and you should be good for a day or so between updates.

kdecker:
I'm not terribly concerned about microsecond accuracy over great lengths of time. I just want it to keep time as well as the digital clock on my nightstand.

If your Arduino is powered by an AC outlet (like your clock radio) another source of accurate time is the 60Hz AC power grid. The long-term accuracy of the 60Hz power is very good. Possibly even atomic clock good. I've seen examples somewhere on how to get a nice interrupt from an AC transformer. Just count the cycles, seconds, minutes, hours, and days. Put in rules for Daylight Savings Time and you'll never need to set the clock unless you lose AC for a long time.

I have several DS1307 RTCs, and they work well, but aren't what I would call super accurate. They can gain or lose a couple seconds per day. The DS3231 is much better, check out the "Chronodot" breakout board at Adafruit, or Sparkfun has a "dead on" RTC using the DS3234, which accuracy-wise, is the same as the DS3231, but it has an SPI interface rather than an I2C interface. From a software standpoint, I've found the DS1307 and DS3231 to be interchangeable, i.e. DS1307 software libraries work fine with the DS3231.

Easiest way is the external clock.

Check here:
http://arduino.cc/forum/index.php/topic,51802.0.html

I reworked the time and DS1307 libraries to add some functions which should help. Using the internet or your PC as a time reference is the best solution -- not the cheapest. The only other solution is a radio chip for WWV/CHU or equivalent.

Well here's the latest. I purchased the Sparkfun DS3234 breakout board to keep accurate time. I have it wired as follows:

Pin 13 to CLK on the RTC
Pin 12 to MISO
Pin 11 to MOSI
pin 8 to SS.

Using the example code below, I tried MODE1 and MODE3 and both result in the following being displayed in the Serial output display:

45/25/165 18:85:85

or

0/0/0 0:0:0

The example code is from the Sparkfun web site for the RTC. Any ideas?

Thanks.

#include <SPI.h>
const int cs=8; //chip select

void setup() {
Serial.begin(9600);
RTC_init();
//day(1-31), month(1-12), year(0-99), hour(0-23), minute(0-59), second(0-59)
SetTimeDate(11,12,13,14,15,16);
}

void loop() {
Serial.println(ReadTimeDate());
delay(1000);
}
//=====================================
int RTC_init(){
pinMode(cs,OUTPUT); // chip select
// start the SPI library:
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE1); // both mode 1 & 3 should work
//set control register
digitalWrite(cs, LOW);
SPI.transfer(0x8E);
SPI.transfer(0x60); //60= disable Osciallator and Battery SQ wave @1hz, temp compensation, Alarms disabled
digitalWrite(cs, HIGH);
delay(10);
}
//=====================================
int SetTimeDate(int d, int mo, int y, int h, int mi, int s){
int TimeDate [7]={s,mi,h,0,d,mo,y};
for(int i=0; i<=6;i++){
if(i==3)
i++;
int b= TimeDate*/10;*
int a= TimeDate_-b10;_
_
if(i==2){_
_
if (b==2)_
_
b=B00000010;_
_
else if (b==1)_
_
b=B00000001;_
_
} _
_ TimeDate= a+(b<<4);*_

* digitalWrite(cs, LOW);*
* SPI.transfer(i+0x80);*
_ SPI.transfer(TimeDate*);
digitalWrite(cs, HIGH);
}
}
//=====================================
String ReadTimeDate(){
String temp;
int TimeDate [7]; //second,minute,hour,null,day,month,year
for(int i=0; i<=6;i++){
if(i==3)
i++;
digitalWrite(cs, LOW);
SPI.transfer(i+0x00);
unsigned int n = SPI.transfer(0x00);
digitalWrite(cs, HIGH);
int a=n & B00001111;
if(i==2){
int b=(n & B00110000)>>4; //24 hour mode*

* if(b==B00000010)
b=20;
else if(b==B00000001)
b=10;
TimeDate=a+b;
}
else if(i==4){
int b=(n & B00110000)>>4;
TimeDate=a+b10;

* }
else if(i==5){
int b=(n & B00010000)>>4;
TimeDate=a+b10;

* }
else if(i==6){
int b=(n & B11110000)>>4;
TimeDate=a+b10;

* }
else{
int b=(n & B01110000)>>4;
TimeDate=a+b10;

* }
}
temp.concat(TimeDate[4]);
temp.concat("/") ;
temp.concat(TimeDate[5]);
temp.concat("/") ;
temp.concat(TimeDate[6]);
temp.concat(" ") ;
temp.concat(TimeDate[2]);
temp.concat(":") ;
temp.concat(TimeDate[1]);
temp.concat(":") ;
temp.concat(TimeDate[0]);
return(temp);
}*_

You have to mark code as code (put 'code' in square brackets before it and '/code' in square brackets after it) otherwise all the square brackets get stripped.

Look like you got your date and time register addresses reversed:

00 = Seconds
01 = Minutes
02 = Hours
03 = Day of the week
04 = Day
05 = Month
06 = Year

kdecker:
Well here's the latest. I purchased the Sparkfun DS3234 breakout board to keep accurate time. I have it wired as follows:

Pin 13 to CLK on the RTC
Pin 12 to MISO
Pin 11 to MOSI
pin 8 to SS.

Using the example code below, I tried MODE1 and MODE3 and both result in the following being displayed in the Serial output display:

45/25/165 18:85:85

or

0/0/0 0:0:0

The example code is from the Sparkfun web site for the RTC. Any ideas?

Thanks.

#include <SPI.h>

const int  cs=8; //chip select

void setup() {
  Serial.begin(9600);
  RTC_init();
  //day(1-31), month(1-12), year(0-99), hour(0-23), minute(0-59), second(0-59)
  SetTimeDate(11,12,13,14,15,16);
}

void loop() {
  Serial.println(ReadTimeDate());
  delay(1000);
}
//=====================================
int RTC_init(){
  pinMode(cs,OUTPUT); // chip select
  // start the SPI library:
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE1); // both mode 1 & 3 should work
  //set control register
  digitalWrite(cs, LOW); 
  SPI.transfer(0x8E);
  SPI.transfer(0x60); //60= disable Osciallator and Battery SQ wave @1hz, temp compensation, Alarms disabled
  digitalWrite(cs, HIGH);
  delay(10);
}
//=====================================
int SetTimeDate(int d, int mo, int y, int h, int mi, int s){
int TimeDate [7]={s,mi,h,0,d,mo,y};
for(int i=0; i<=6;i++){
if(i==3)
i++;
int b= TimeDate[i]/10;
int a= TimeDate[i]-b10;
if(i==2){
if (b==2)
b=B00000010;
else if (b==1)
b=B00000001;
}
TimeDate[i]= a+(b<<4);
 
digitalWrite(cs, LOW);
SPI.transfer(i+0x80);
SPI.transfer(TimeDate[i]);       
digitalWrite(cs, HIGH);
  }
}
//=====================================
String ReadTimeDate(){
String temp;
int TimeDate [7]; //second,minute,hour,null,day,month,year
for(int i=0; i<=6;i++){
if(i==3)
i++;
digitalWrite(cs, LOW);
SPI.transfer(i+0x00);
unsigned int n = SPI.transfer(0x00);       
digitalWrite(cs, HIGH);
int a=n & B00001111;   
if(i==2){
int b=(n & B00110000)>>4; //24 hour mode
if(b==B00000010)
b=20;       
else if(b==B00000001)
b=10;
TimeDate[i]=a+b;
}
else if(i==4){
int b=(n & B00110000)>>4;
TimeDate[i]=a+b
10;
}
else if(i==5){
int b=(n & B00010000)>>4;
TimeDate[i]=a+b10;
}
else if(i==6){
int b=(n & B11110000)>>4;
TimeDate[i]=a+b
10;
}
else{
int b=(n & B01110000)>>4;
TimeDate[i]=a+b*10;
}
}
temp.concat(TimeDate[4]);
temp.concat("/") ;
temp.concat(TimeDate[5]);
temp.concat("/") ;
temp.concat(TimeDate[6]);
temp.concat("    ") ;
temp.concat(TimeDate[2]);
temp.concat(":") ;
temp.concat(TimeDate[1]);
temp.concat(":") ;
temp.concat(TimeDate[0]);
  return(temp);
}
[/code
[/quote]

Oh thanks John. Didn't know I could do that with the code snippet. I'll do that next time and thanks for the response on the registers. I'll take a look at that.

kdecker:
Oh thanks John. Didn't know I could do that with the code snippet. I'll do that next time and thanks for the response on the registers. I'll take a look at that.

My mistake. I didn't notice that you re-ordered the values between the function arguments and the array.

I have read in various places that when you use the SPI library you are supposed to set the hardware Slave Select line (D10) to an output. It sounds like a strange requirement but couldn't hurt to try. Just put it in setup().