Keeping accurate time

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().

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().

Actually the other way around. If pin 10 (SS) is set to be an input then the SPI hardware will setup to be as a SPI slave device. Defining it (SS pin 10) as a output is required for the master device so that it's SPI hardware will setup to be a master. This is true even if the master is using other pin(s) as slave select outputs, pin 10 must be setup as an output pin in the master device even if it's not being used by the master. The slave must only use it's pin 10 as the slave select line.

Lefty

I changed SS to pin 10 and changed the code accordingly as:

const int  cs=10; //chip select 
.
.
.
int RTC_init(){ 
	  pinMode(cs,OUTPUT); // chip select
.
.
.

Same result. Still only returns zeros.

kdecker:
I changed SS to pin 10 and changed the code accordingly as:

const int  cs=10; //chip select 

.
.
.
int RTC_init(){
 pinMode(cs,OUTPUT); // chip select
.
.
.



Same result. Still only returns zeros.

The pinMode should probably happen before the call to RTC_init as that then initilizes the SPI which looks at pinmode for pin 10 to decide to be master or slave.

Lefty

The pinMode() statement is the first thing that happens in the init so that should make sure it is set before the rest of the SPI initialization. Here's the whole thing as it looks right now. My apologies for not understanding all the detail around this code. It's the sample code from the Sparkfun web site for the RTC. I've left a request for info on that site as well. Thanks everyone for all your help.

#include <SPI.h>
const int  cs=10; //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(05,04,11,18,21,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_MODE3); // 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]-b*10;
		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+b*10;
		}
		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);
}

Try the slowest clock speed: SPI.setClockDivider(SPI_CLOCK_DIV128);

If that helps you can reduce the divider to see how fast you can go before it fails. :slight_smile:

No joy. I put SPI.setClockDivider(SPI_CLOCK_DIV128); in the init routine but it didn't change anything. I'm starting to wonder if the RTC itself is defective.

Finally determined that the RTC was bad. Got a new one, wired it up, and it is working fine.

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

By any chance to you have a part # for the crystal?

Thanks,
William

sorry to hear the uno went ceramic.

Yah, unless you have a software glitch, the basic processor can keep perfect time with the crystal, the same way the rtc module keeps time with ITS OWN crystal. As long as the period is consistent, you can make any necessary adjustments to convert to wall time. If it is off by a fixed amount, that is trivial to fix in software (in fact I think the timer library already has an adjustment for it Arduino Playground - Time )

I cannot think of any application where I would bother with an rtc instead of putting the crystal directly on the cpu and calibrating/validating it to real time. There might be one but it has never been on my todo.

here is a 10ppm 16mhz crystal http://www.mouser.com/ProductDetail/TXC-Corporation/9C-16000MEEJ-T/?qs=sGAEpiMZZMsBj6bBr9Q9aWUu9Vmt5DeoFlToalzCQFw%3D , if you want more precision. I've only messed with the 50ppm fox crystals.

  1. to generate logfiles that need to be processed later ...

KE7GKP:

  1. Keeping accurate date/time without main power supply.
  2. The Arduino is NOT a "real-time" system. There are things you can program that could keep the internal millis() or micros() from keeping accurate time.
  3. You want to run something for more than 50 days (for millis) or 70 minutes (for micros)
  4. You want to actually know what time and/or date it is in the real world.

If we maintain a count of seconds, we can easily keep track of time for more than 100 years irrespective of millis/micros overflow. Tracking real-time is also no show stopper as it is just a matter of setting initial time as will be required for a RTC as well. Real-time accuracy is a property of the oscillator and not unique to RTC. It is also possible to operate a timer (timer2 on AtMega328) independent of the main clock from a separate external crystal.

The main benefit of using a RTC chip as I see it is low power battery operation. A dime-size coin cell battery will keep a DS1307 ticking for 10 years. You can even use the RTC without a crystal as a low cost I2C NVRAM chip to preserve accumulators/state/preferences across microcontroller power cycles or add the crystal to get both time keeping and data retention.