Arduino Clock + DS3234

I've been working on a clock that will display the time down to the hundredths of a second. I am using a RTC (the DS3234) to keep the clock time and an Arduino with a highly accurate crystal to do the counting and output the time to an 8 digit 7 segment LED display (using a MAX 7219).

Basically, the Arduino gets the current time from the RTC and loads it into variables which are then incremented as needed by an ISR running at 100Hz. The program only checks the RTC when it starts up and at midnight to correct any drift that may have occurred; the rest of the time it is not being polled. The problem I am having is that the RTC clock is drifting by a huge amount, on the order of minutes per day. I have verified that the arduino can keep time (within 1-2 seconds using a standard crystal) independent of the RTC. The RTC is also accurate. However, as soon as I put the two together the time starts to drift. As soon as I restart the program or when midnight rolls around the time jumps. I even checked the RTC and found that the RTC's time was off by 2-3 minutes after 8 or so hours. The arduino was keeping time to within a second until it checked the RTC. I have no idea how this is even possible since I am not writing anything to the DS3234. It appears to be a cumulative error that is building up, because if I reset the program it after an hour or so, only a few extra seconds have been added.

The only thing I can think of is that the RTC is "ticking" too fast, but what would cause this to only be present when it is hooked up and running in conjunction with the MAX7219 and arduino?

#include <SPI.h>
#include <LedControl.h>
#include <avr/interrupt.h>

int DIN = 11;              // Pin 1  on the Max72xx
int CLK = 13;              // Pin 13 on the Max72xx
int LOADCS = 10;           // Pin 12 on the Max72xx

int ledBrightness = 15;    // range is 0-15.  0=lowest, 15 = full power

int years=0;
int months=0;
int days=0;
int hSecond=0; // these are our time variables  
int seconds=0;
int minutes=0;
int hours=0;

LedControl lc=LedControl(DIN,CLK,LOADCS,1);    // DIN, CLK, Load/CS, 1 = only one chip MAX chip attached

byte chip_id = 0;          // This is not strictly reqd, but if using more than one display this will be needed
byte row = 0;              // Set the starting position

int val = 0L;               // Variable to store the data from the serial port

const int  cs=8; //chip select for RTC

void setup() {
  pinMode(DIN, OUTPUT);        // once only, lets make the pins outputs 
  pinMode(CLK, OUTPUT); 
  pinMode(LOADCS, OUTPUT);
    
  for(int index=0;index<lc.getDeviceCount();index++)  // take pins out of power save mode
  {
      lc.shutdown(index,false);
  }
  lc.setIntensity(0,ledBrightness);
  boot();
  RTC_init();
  //SetTimeDate(23,5,13,18,11,00); //day(1-31), month(1-12), year(0-99), hour(0-23), minute(0-59), second(0-59) only if needed
  ReadTimeDate(); // load current RTC time into time variables
  clearSPI();
  
  
  Serial.begin(19200);
  
noInterrupts();
   
    //This sets Timer1 to interrupt every 1/100 of a second, assunming a clock of 16MHz. 
    TCCR1A = 0; // set entire TCCR1A register to 0
    TCCR1B = 0; // same for TCCR1B
    TCNT1  = 0; //initialize counter value to 0
    // set compare match register to desired timer count: 100hz
    OCR1A = 19999; //19999; // 12499 for 10mhz
    // turn on CTC mode:
    TCCR1B |= (1 << WGM12);
    // Set CS11 bits for 8 prescaler:
    TCCR1B |= (1 << CS11); // |(1 << CS10);
        // enable timer compare interrupt:
    TIMSK1 |= (1 << OCIE1A);//|(1 << OCIE1B);
    
interrupts();
}

ISR(TIMER1_COMPA_vect)  // This code executes at each interrupt of Timer1
  { 
    hSecond++;
    
      if (hSecond >= 100) {
      seconds++;
      hSecond = 0;
   }
    if (seconds >= 60) {
      minutes++;
      seconds = 0;
    }
    if (minutes>= 60) {
      hours++;
      minutes = 0;
    }
    if (hours == 24) {
      hours = 0;
      SPI.begin();
      SPI.setBitOrder(MSBFIRST); 
      SPI.setDataMode(SPI_MODE3); // both mode 1 & 3 should work RTC_init();
      ReadTimeDate();
      clearSPI(); 
    }     
//  Serial.print(hours);  
//  Serial.print(":");
//  Serial.print(minutes); 
//  Serial.print(":");
//  Serial.print(seconds);
//  Serial.print(".");
//  Serial.println(hSecond);
    
  led_print(hours, 6);  // Print the hour
  led_print(minutes, 4);  // Print the minutes
  led_print(seconds, 2);  // Print the seconds
  led_print(hSecond, 0); //Print the hundreths of seconds
  
}

void loop() 
{
   
 }
 
 //=====================================
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);
          
}

//=====================================
void clearSPI() {
  SPI.begin();
  SPI.end();
}

//=====================================
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;	
			}
	  }

        hours = TimeDate[2];
        minutes = TimeDate[1];
        seconds = TimeDate[0];
        years = TimeDate[6];
        months = TimeDate[5];
        days = TimeDate[4];
      
  return(temp);
}

void led_print(int time_int, int pos){              // Ask for the number and the position to print
  byte ones, tens;                                  // A couple of variables to fill with digits
  ones=time_int%10;                                 // %10 divides by ten and extracts the remainder
  tens=time_int/10%10;                              // Handy for splitting the digits in two for printing
  lc.setDigit(chip_id, pos, (byte) ones, true);    // These two lines send the digits to the Max7221 
  lc.setDigit(chip_id, pos+1, (byte) tens, false);  // one by one
}

You shouldn't be doing any I/O in the ISR. The I/O should be done in loop() and the ISR should signal when it wants the led updated or the RTC read.
You must also declare any variable used by the ISR to be volatile as I have done for the two flag variables in the code below.
Try this (untested) code:

volatile unsigned char tick_flag = 0;
volatile unsigned char day_flag = 0;
ISR(TIMER1_COMPA_vect)  // This code executes at each interrupt of Timer1
{ 
  hSecond++;

  if (hSecond < 100) return;
  seconds++;
  hSecond = 0;
  // signal loop that the led should be updated
  tick_flag = 1;
  if (seconds < 60) return;
  minutes++;
  seconds = 0;
  if (minutes < 60) return;
  hours++;
  minutes = 0;
  if (hours == 24) {
    day_flag = 1;
    hours = 0;
  }    
}

void loop() 
{
  if(tick_flag) {
    led_print(hours, 6);  // Print the hour
    led_print(minutes, 4);  // Print the minutes
    led_print(seconds, 2);  // Print the seconds
    led_print(hSecond, 0); //Print the hundreths of seconds
    tick_flag = 0;
  }
  if(day_flag) {
    SPI.begin();
    SPI.setBitOrder(MSBFIRST); 
    SPI.setDataMode(SPI_MODE3); // both mode 1 & 3 should work RTC_init();
    ReadTimeDate();
    clearSPI();
    day_flag = 0;
  }
}

Pete

Thanks for your help. That appears to be a much simpler and more elegant way of accomplishing what I want the program to do. I tried it out and it works, but is updating the display every second instead of every 1/100 of a second. I think the interrupt is preventing the loop from updating the hundredths since the ISR is triggered every 1/100 of a second, but I'm not sure. The hSecond variable is getting updated as I expected it to, but the data never makes it to the display; those digits are at 00 all the time. I will however run this overnight and see if the RTC drift issue is corrected.

but is updating the display every second

My mistake, I put "tick_flag = 1" after the first "if". It should be before that so that it is set on every entry to ISR.
Also make sure that you have done this:

volatile int hSecond=0; // these are our time variables  
volatile int seconds=0;
volatile int minutes=0;
volatile int hours=0;

I don't know if you can send updates to the display at 100/sec. If not, you could try reducing the amount of data that needs changing. For example, every "tick" changes hSeconds but 99 of the 100 "ticks" do not result in a change of the seconds, minutes or hours. And 59 out of 60 updates of the seconds won't change the minutes or hours. For each of the seconds, minutes and hours, add another variable that holds the last value that was displayed. If the new value is the same as the old, don't try to update that one.

Pete

Moving the tick flag did the trick! The display also didn't have any problems updating fast enough. However, after running this for 10 hours, the RTC is still too fast. It was ahead of the actual time (which the arduino had been keeping within 1 second) by about 2 minutes. I am going to try disconnecting the DS3234 from the data lines after the clock is set and see if that fixes the problem.

The Time.h library includes for example:

 setSyncProvider(getTimeFunction);// Set the external time
                                  //  provider
 setSyncInterval(interval);    // Set the number of
                               //  seconds between re-sync

With:

..
setSyncProvider(RTC.get); 
..
setSyncInterval(300);
..

you may, for example, start an automatic RTC(DS3234)-based sync of the arduino's internal time which happens at 300secs intervals..

Does the RTC, by itself, keep time?

Does the RTC, by itself, keep time?

Provided the RTC==DS3234 then yes, of course..

The

setSyncProvider(RTC.get);

requires the "RTC.get" reads the external RTC, ie. ds1307, DS3234, GPS, NTP, DCF77 and all others..
See Arduino Playground - HomePage

pito:
Provided the RTC==DS3234 then yes, of course..

I'm asking FlightLevel since they are having problem with it keeping time.

Yes, the DS3234 is keeping correct time when I have it hooked up to the arduino by itself. I used the example code that Sparkfun has on their website to read the RTC once a second and output that on the serial connection. The DS3234 was able to keep time with no drift over the course of 24 hours. It is when I use the clock program I wrote that the time on the 3234 starts to drift. The only reason I am using the RTC at all is to save the time in case of power loss or if I need to reset it.

It is when I use the clock program I wrote that the time on the 3234 starts to drift.

The 3234 cannot start to drift when you only read the register to get the time and date. So you must have an issue with accessing the 3234's internal registers and you writing the 3234's somewhere.
I can imagine the situation where when the reading process (reading the registers) takes more than 1 second you may loose a second. That is with older I2C RTC types which stops updating the internal time/date registers when the I2C interface is active (between I2C's start and stop).

As pito said, if the DS3234 keeps accurate time on its own, but not with your program, then it's something that you're doing that's causing it to slow down. Are you sure you're not writing a timestamp back to it (in essence trying to update the time on it)? Or there's something on the I2C bus that's causing a delay when accessing the DS3234 causing it to lock up briefly.

That is an interesting idea. The RTC shares an SPI bus with the MAX7219 but the slave select line for the DS3234 is kept high all the time except during startup and at midnight when it checks the time. I was under the impression that if the SS line was high, the RTC would ignore any data being sent to it, nor would it send any data out. Since the drift appears to be cumulative, the register delay could be a factor, but that would suggest that I am reading the RTC every hundredth of a second which is not the case. There is a provision to write the time to the RTC, but it is always commented out so that the RTC doesn't continually reset itself to the same time. I will try running the RTC with the clock program but using the serial connection to view the time and see if the MAX7219 data is causing the problem.

i might end up getting GPS module or some other time constant. The RTC is a much simpler way of keeping time but the drift is way too big to make it useful. The other alternative is to use the RTC as a "key" that I plug in to set the clock and then remove when it has been updated. Not the self contained solution I had in mind, but better that what it is currently doing

something on the I2C bus that's causing a delay

It is an SPI device - even so I would recommend to read all the time/date registers in the burst mode (w/ autoincrement of the register's address) and I would use the DS3234 for syncing the arduino's internal RTC only (ie. each 600secs). See my above post on the Time.h library and setSync stuff.
The DS3234 is an extremely accurate, temperature compensated RTC with +/- 2ppm - maybe few minutes a year(!) - so unless you have destroyed the chip, the issue you have is related to improper software driver you use :wink:

The DS3234 is SPI and although my mods to your code only access it once every 24 hours (I hope), you have set up the MAX7219 to use pin 10 but this is the chip select for the SPI device. That is going to be bouncing up and down 100 times a second which will drive the RTC nuts. If possible, you must move the LED device off the SPI bus pins (10, 11, 12, 13).

Pete

Sorry, I got mixed up with the DS3231, which is an I2C. DS3234 is SPI.

If possible, you must move the LED device off the SPI bus pins (10, 11, 12, 13).

The chips may share the SPI bus, but they must have a separate CSelects. Otherwise you read/write DS3234 when you do r/w to the MAX72...

Yup, it should work as long as they don't share chip select pins but I'd be more comfortable moving the MAX72xx off the SPI bus altogether since it isn't an SPI device. And to be safe, the 3234 chip select should be pin 10 and move the MAX72 chip select somewhere else.

Pete

I agree with you on that. According to the MAX datasheet, while it is compatible with SPI, it is not a true SPI device, so that might be the problem. The 3234 is an actual SPI device (at least as far as the datasheet leads me to believe). I will use the Arduino SPI bus just for the RTC and move the 7219 to some other digital pins. They will still share the SPI clock pin but all of the data will be on different lines

DS3234's SPI clock is max 4MHz, btw..