Arduino as a RTC

So, I’ve been putting together this project where I’m receiving accurate time via a WWVB receiver (see previous post). To really make use of this, I need some way to keep track of the time during those times of the day when the WWVB signal isn’t available.

Initially, I looked at the chronodot from Adafruit industries, but one of the things I didn’t like about it was the fact that the battery mounts to the back side of the breakout board. I know that this was done to reduce the cost of the breakout board, but it makes the end result kinda bulky. Besides that, changing out that battery could be tricky depending on how you mount it in your project enclosure. Also, the fact that it’s round doesn’t help when have to mount it into my project case, either.

In addition to considering the chronodot, I also have been waiting on a similar product from Sparkfun.com which is based on the ds3234 real time clock module. Their version, at the time I started development, was simply just the bare timekeeping chip and the breakout for it was unavailable. Fast forward a few months and now the breakout is available, but yet again, the battery is mounted to the back of the board 😦

Not being too sure of the best way to proceed, it hit me one day that I might be able to program the arduino to perform the real time clock functions. Yeah, the crystal that’s on the board may drift with temperature by a few parts per million, but if I’m receiving a valid WWVB signal at some point of the day, I might not need extreme accuracy at all. In fact, after considering the requirements of my project, it wouldn’t really matter if the time drifted by up to 5 minutes over the course of the day.

Below is the proof of concept code I wrote:

// What do you do when an accurate, temperature compensated real time 
//    clock chip breakout is unavailable from your favorite vendors?  
//    Well, you first consider the less accurate battery backed 
//    alternatives until you later realize that there's not really a 
//    big difference between using the non-temp-compensated rtc and 
//    just programming the arduino to do it directly. 
//
//   So - that's what this is.  A real time clock implemented based on 
//    16bit Timer1 and the cpu's clock frequency.  Since both the 
//    dedicated rtc and the this rtc are both dependent on temperature 
//    variations and since both are using crystals for their time source, 
//    the amount of error should be acceptible.  Also, if you integrate 
//    wwvb receiver code with this code, your accuracy should be more 
//    than close enough...

#include <avr/io.h>
#include <avr/interrupt.h>

#define ISR_TIMER1_COUNT 15625 // 16.0MHz clock / (prescaler = 1024)

struct TIME {
  int seconds;
  int minutes;
  int hours; 
};

volatile TIME t;  // global time struct

void setupTimer1(void)
{
  // Setup Clear Timer on Compare Match mode.  We should interrupt each 
  //    time TCNT1 is equal to the value in OC1A register.  TCNT1 will 
  //    automaticall be reset to 0 each time a compare match happens.  
  //    Because this is done in hardware, the interrupt frequency should 
  //    be very stable (no interrupt service routine overhead to deal 
  //    with).
  
  // Please refer to the atmega168 data sheet for an explanation of the 
  //    registers and the values chosen here.
  
  // WGM13:0 = 4  for CTC on OCF1A value.  Prescaler set to divide by 1024.
  TCCR1A &= ~(1<<COM1A1) &   // Clearing bits
            ~(1<<COM1A0) &
            ~(1<<COM1B1) &
            ~(1<<COM1B0) &
            ~(1<<WGM11) &
            ~(1<<WGM10);
            
  TCCR1B &= ~(1<<ICNC1) &    // Clearing bits
            ~(1<<ICES1) &
            ~(1<<WGM13) &
            ~(1<<CS11); 
            
  TCCR1B |= (1<<WGM12) |    // Setting bits
            (1<<CS12) |
            (1<<CS10);
  
  OCR1A = ISR_TIMER1_COUNT;
  
  
  // OCIE1A interrupt flag set
  TIMSK1 |= (1<<OCIE1A);
  
  // Start counter at 0, not that it would matter much in this case...
  TCNT1 = 0;
}

// This interrupt service routine gets called once per second
ISR(TIMER1_COMPA_vect)
{
  t.seconds++;
  if (t.seconds > 59)
  {
    t.minutes++;
    t.seconds = 0;
  }
  if (t.minutes > 59)
  {
    t.hours++;
    t.minutes = 0;
  }
}

void setup()
{
  Serial.begin(9600);
  
  t.seconds = 0;  // initialize our time struct
  t.minutes = 0;
  t.hours = 0;
  
  setupTimer1();
  sei();    // allow interrupts globally
}

void loop()
{
  int sec = t.seconds;
  while (sec == t.seconds) {delay(100);} // wait for t.seconds to increment
  
  Serial.print("Time: ");
  Serial.print(t.hours);
  Serial.print(":");
  Serial.print(t.minutes);
  Serial.print(":");
  Serial.println(t.seconds);
}

You’ll note here that very little of the actual arduino library is used at all. I decided to use the 16bit Timer1 on the microcontroller directly with an interrupt to keep track of the time. When I started monitoring the program, I wrote down the current time on the computer. After 24 hours, I came back and again compared the time on the computer with the time that was coming out of my program and discovered that the time drift was only 3 seconds over the course of a full day! If it performs that well, I guess I don’t need a separate clock chip.

In the next post, I’ll be writing about the TimeLord library.

Leave a comment