Step 4: Time keeping

To keep track of time I used only the arduino, and the standard arduino function millis(). This method won't be completely exact, because the 16Mhz crystal oscillator, that decides the clock frequency of the arduinos CPU, probably  won't be exactly 16 000 000 Hz. The good news is that you can measure how inexact your clock is and compensate for it in your code, because the offset should be constant for a given period of time.

Measure how inexact your arduino is:
As said earlier, the arduino will have a small time error, this error depends on the crystal oscillator and will be different for every arduino, to measure how much my arduino clock differed from the correct time, I programmed it to print out the time ( the hour, minute and second variables) via serial. I then let it run for a long time (Over night and more) and compared the arduino time with a clock I knew was exact, at the start and the end of my measuring period. Divide the time difference with the time the test took to calculate the error each hour. I found that my arduino is about 0.4 seconds to fast every hour. I used exacttimenow.com to get exact time, but if you have a watch you know is very exact, feel free to use that instead.
The code I used to keep the time is an adaptation of some code I found on the arduino forums. I rewrote it with if-statements instead of division and modulo, to see if there would be any speed difference and found that the if-version is more than 15 times faster (although both are still quite fast, more info about the test here).

Since I want the other stuff in my main loop (like checking the touch sensor, checking for button presses, etc.) to happen as often as possible, I used the faster version.

The code:

Every time the clock function is called it adds the time in milliseconds since last it was called to a variable m, when one second has passed (m>999) the second variable is increased by one and m is reset. When the seconds variable reaches 60, the minute variable will be increased by one, and seconds will be set to zero. The same thing happens with the minutes variable; when it reaches 60, add 1 to hours and reset minutes. The hour variable will be reset when it reaches 24.

To compensate that my arduino is 0.4 seconds faster evey hour, I decrease the seconds with two seconds every fifth hour.


The clock() function:

#define MAX_MILLIS_VALUE 34359738
unsigned long current_millis_value = 0;
unsigned long previous_millis_value = 0;
unsigned long m = 0;
int seconds = 0;
int minutes = 0;
int hours = 0;

void clock()
  current_millis_value = millis();
  if (current_millis_value < previous_millis_value) // if millis overflows
    m += MAX_MILLIS_VALUE - previous_millis_value + current_millis_value;
  else // if millis has not overflown
    m += current_millis_value - previous_millis_value;
  if (m>999)
    m = m-1000;
  if (seconds>59) // if seconds == 60
    seconds = 0;
  if (minutes>59) // if minutes == 60
    minutes = 0;
    if(hours%5==0) // adjust the time with -2 seconds every 5th hour.
      seconds = seconds - 2;  // this will cause seconds to be -2,
                             // therefore seconds can't be unsigned.
  if (hours>23) // if hours == 24
    hours = 0;
  previous_millis_value = current_millis_value;


This could be useful to detect individual differences from crystal to crystal, however, a crystal oscillator is affected by environmental factors as well. Mainly temperature, power supply ripples, even age of the crystal :(
I found this informative article http://kunz-pc.sce.carleton.ca/thesis/CrystalOscillators.pdf if you want super details. My latest arduino clock uses the DS3231SN chip (ChronoDot RTC product) which (in reasonable temperatures) is accurate to better than one minute per year drift. And the battery backup means you don't have to set the clock every time you loose power.
Laserman5955 years ago
When ever you do find out please post it so it would be possible to  compensate for it every 6 or so months.  =-)
njakol (author)  Laserman5955 years ago
 I'm not shure what you mean, but if you mean the clock error, then I have as you can read already compensated for it in the code, the discussion below is just about other possible ways to do it. I have however not tested a very long run of the clock (several weeks) without resetting yet because I have had some problems with the touchsensor being oversensitive and after a couple of days trigger alarm on/off constantly. I think I may have to ground the arduino better, the capsense page say that it's important.
daltore5 years ago
Have you thought about using ternary operators?  They're basically if() statements, but use a slightly lower-level structure.  I'm not sure if they run faster under the Arduino, they may actually end up compiled the same since it's an embedded environment, but basically, it goes like this:

condition ? run if true : otherwise run this;

So you could do that first if() statement like this:

current_millis_value < previous_millis_value ? m += MAX_MILLIS_VALUE - previous_millis_value + current_millis_value : m += current_millis_value - previous_millis_value;

It's not as pretty, but as always, C ignores whitespace, so you can write it like this if you want to organize it a little:

current_millis_value < previous_millis_value ?
m += MAX_MILLIS_VALUE - previous_millis_value + current_millis_value :
m += current_millis_value - previous_millis_value;

It might run faster and eliminate some of the timing error.

Also, you might be able to spread out the time correction a little more evenly than 2 seconds every 5 hours.  Since it's .4 seconds, that's 400 milliseconds every 60 minutes.  That goes down to 20 milliseconds every 3 minutes.  Since you're counting milliseconds directly, and the overall count of seconds is a multiple of 3, it might be overall a little more accurate to the user to do the correction more often.

Great tutorial though, I might have to try this!
Just wondering, why didn't you use a RTC for timekeeping? Nice guide btw.