Accurate Interval Timer for AVR MCU
(Last updated 5 Jan 2013)

Updated 5 Jan 2013

Arlet posted a simple integer math solution for use within the ISR that accumulates cycles and converts to milliseconds.  As he explains, each interrupt is 19968 cycles and each millisecond is 20000 cycles, so this code:

a += 19968;
if (a >= 20000)
{
msec++;
a -= 20000;
}

performs the conversion.

Mathew, reboots, and lwatcdr all suggested a 20.48 MHz crystal, rather than the 20 MHz crystal I used.  This means a binary divisor will give an even integer multiple for the number of cycles per interrupts.  For this example, a prescaler of /256 yields a reload value of 80, which removes the need to muck about with the reload adjustments.

Alex Rossie provided a link to this earlier Hackaday entry describing a solution similar to mine for creating a TI Chronos wristwatch that keeps Martian time.

Martin and WitchDoc suggested setting the prescaler to /1 and using a reload value of 19999 (actually, 20000).  This works if you have a 16-bit timer available.

Daid and hat offered suggestions for improving the stability of the AVR's crystal oscillator using various temperature compensation ideas.

Tom the Brat suggested counting the numbe of milliseconds (ticks), multiplying by 1000, then dividing by 78125 to get the number of seconds for display.

jc and Hack Man recommended an external clock source of greater accuracy than the AVR's crystal.  In particular, Hack Man suggested the Maxim DS3234 SPI-compatible RTC with integrated crystal.

Julian Skidmore offered an interesting twist on my technique.  Rather than use CTC mode, which resets the timer to 0 on each OCR match, his technique does a simple calculation within the ISR to compute the next OCR value in free-run mode, adjusting for the lost cycles with fractional arithmetic.  I'm won't go into the details here, but strongly suggest you read through his comment in the Hackaday link above.

Odokemon (Danny Chouinard) posted a link to a page on his site where he ovenized a crystal oscillator, temperature sensor, and tiny heater (made from a couple of resistors), and packed all of this into a tiny insulated box.  The concept is to keep the crystal at a fixed temperature, which reduces or eliminates frequency variations due to temperature changes.  It is really a clever project and quite well done.  I did not find a mention of it on Hackaday, but it deserves one.

The bottom line for all of this: There are many different ways to achieve accurate interval timers with small MCUs like the ATmega168.  Depending on how much freedom you have over crystal frequency, 8-vs-16-bit timers, use of external timing sources, environmental controls, and other factors, one or more of the above suggestions may be well suited for your project.

Many microcontroller projects need an interval timer of some kind, often for accurate timing of intervals from a few seconds to several hours or days.  MCUs like the ATmega168 have timer/counter subsystems that can act as interval timers, but using these timer/counters runs into two snags.  First, the MCU's timing reference, usually a crystal, has built-in tolerance issues.  Second, MCUs such as the ATmega168 use a binary prescaler that does not permit an even division of common crystal frequencies down to even multiples of milliseconds.

In order to create an accurate interval timer with such an MCU, you need to address both of these issues.  The following design shows one method for creating such an interval timer, using C in AVRStudio 4.

The design
I'm basing this design on an ATmega168 with a 20 MHz crystal and suitable caps connected as a timing reference.  The actual schematic isn't really important; you can find such a design all over the web.  I'm focusing on the firmware design here.

The firmware consists of a simple main() function that sets up Timer1 (16-bit timer/counter) in CTC mode, with an interrupt on OC1A match.  The compare register, OCR1A, is loaded with a value that, with a prescaler of /256, gives an interrupt approximately 1000 times per second.

On each interrupt, a local msecs counter is incremented.  The counter is then checked; if it has hit 1000, it is immediately zeroed and a global seconds counter is decremented if that counter is not yet zero.

This design creates a down-counting timer that starts at some initial value and counts down once per second until it hits zero, where it stays until written with another value.

The difficulty lies with the reload value stored in OCR1A and the associated prescaler.  There is no combination of prescalers and reload value that provides an accurate one millisecond interval.

(Edit 5 Jan 2013: As pointed out by Martin and WitchDoc above, this is, stictly speaking, not true.  I should have said that such combination does not exist for 8-bit timers.  I was focused on making a solution that was general across both types of timers.  If you are using a 16-bit timer, you can use a prescaler of /1 and an OCR value of 20000 and forget about all of the following reload calcs.  Yes, I did choose a 16-bit timer for this example; I should have used an 8-bit instead; it would have been less confusing.  Sorry about that.)

For example, using a prescaler of /256 gives:

ClocksPerSecond / MillisecondsPerSecond / Prescaler = InterruptsPerSecond
(20000000 / 1000) / 256 = 78.125

Put another way, if you use a 20 MHz crystal, a /256 prescaler, and a reload value of 78.125, you will get one interrupt every millisecond.

Obviously, you don't get to choose a reload value of 78.125; the closest you can get is 78.  The problem is that after 1000 interrupts, you will not have timed one second, you will have timed a shorter interval.  To be precise, you will have timed:

1000 * (78 * 256) cycles or 19968000 cycles

This means that your one-second interval will be 0.9984 seconds, and your minute will be 59.904 seconds and your hour will be 3594.24 seconds, or nearly six seconds off.

The simplest way to fix this issue is to modify the OCR1A reload value inside the ISR, adjusting the value occasionally so that after 1000 interrupts, you have used precisely 20000000 cycles.

Given the above conditions, you would need to use a reload of 78 for seven interrupt intervals, but switch to a reload of 79 for the eighth interrupt interval.  This would give you:

1000 * ((78 * 7) + 79) * 32 cycles or 20000000 cycles

The implementation
It's easy to implement the above design, but it won't work, in the sense that it will give you exactly 20000000 cycles per 1000 interrupt intervals, but you still won't have an accurate second.  The problem here is that crystals drift and have their own frequency tolerance.  A typical Fox 20 MHz crystal from Digikey shows a frequency stability of +/- 50ppm and a frequency tolerance of +/- 30ppm.  That's pretty darned good for a 42-cent part, but your seccond could still be off by up to 1600 cycles, which adds up over an hour.

Rather than hard-code the use of seven reloads of 78 and one reload of 79, I opted to use named literals for how often I modified the reload value; I call this trimming in my code and the literals define a trim value used to modify the reload value in the ISR.

The final step is to test against a reference clock, then adjust the named literals MSEC_ISR_TRIM_VALUE and (if necessary) RELOAD_1MSEC, and recompile.  Note that your trim value (MSEC_ISR_TRIM_VALUE) should be a whole divisor of 1000.  If it isn't, you will miss the final trim at the 1000th interrupt.  Note also that lower trim values cause longer intervals, as the greater reload value is used more often; a trim value of 5 means the greater reload value is used 200 times, but a trim value of 20 only uses the greater value 50 times.

In my case, I did not have a NIST-traceable interval timer, so I ended up using a Radio Shack interval timer.  With repeated testing and tweaking, I now have an interval timer that matches the Radio Shack timer.  (Yes, I know, that's not absolutely accurate, but it's relatively accurate, and that's what I was shooting for.)  If you duplicate this work and have a NIST-traceable timer, I would be interested to know how closely your final trim values match the nominal values of 78 for RELOAD_1MSEC and 8 for MSEC_ISR_TRIM_VALUE.

Here is the final code that I'm using in my project:

========================================================================

#define  MSEC_ISR_TRIM_VALUE     5

/*
*  ISR for Timer1 OCR1A compare
*
*  Control reaches here each time Timer1 reaches its OCR1A counter
*  value.  This interrupt is used to define one millisecond.
*
*  However, there is an issue.  The MCU is driven with a 20 MHz
*  crystal but the MCU uses a binary divider chain to determine
*  the interrupt rate.  This means that there is no accurate
*  divider; you can't divide 20 MHz by a binary prescaler and get
*  an even multiple of cycles for one msec.
*
*  For example, using a prescaler of 256 gives:
*
*  20000000 / 1000 / 256 = 78.125 per msec
*
*  If you load OCR1A with 78 and set the prescaler to 256, you will
*  get an interrupt every (78 * 256) cycles, or 19,968 cycles.  This
*  means your msec will be off by 32 cycles and your second will be
*  off by (32 * 1000) or 32,000 cycles.  This is really inaccurate.
*
*  The key is to add back in the 0.125 per msec that is missing from
*  the reload value in the above equation.  Since 0.125 = 1/8, we
*  need to use 79 as our our OCR1A load value once every eight msecs.
*  For one full second, this will give:
*
*  (78 * 256 * 875) + (79 * 256 * 125) = 20000000 cycles
*
*  which is perfect; our one-second clock will be as accurate as
*  our crystal.
*
*  However, this isn't necessarily accurate, since the crystal
*  has tolerances of its own.  So the code below uses a named
*  literal, MSEC_ISR_TRIM_VALUE, to provide a selected trim value
*  The trim value is the number of msecs that must elapse before
*  an additional count is added to the OCR1A value (79 vs 78 in
*  the above example).
*
*  For example, on one of my projects, the above trim value of
*  8 gave too much delay and the clock would lose time versus a
*  reference clock.  After repeated tests, I settled on a trim
*  value of 5 and a base reload value of 77, which gave a more
*  accurate clock.
*
*  Note that the trim value should be an even divisor of 1000 or
*  you won't get the last adjustment when hitting msec #999.
*
*  Note that you don't normally reload the OCR1A value inside the
*  ISR, as you see below.  The only reason I'm doing this is to
*  ensure the OCR1A value adjusts for the missing fractions of
*  seconds.  If you don't need the accuracy and can live with a
*  constant reload value, you don't need to modify OCR1A here.
*/
ISR(TIMER1_COMPA_vect)
{
static unsigned int        msecs = 0;

OCR1A = RELOAD_1MSEC;            // this is correct most of the time
if ((msecs % MSEC_ISR_TRIM_VALUE) == MSEC_ISR_TRIM_VALUE-1)    // but on each trim msec...
{
OCR1A = RELOAD_1MSEC + 1;    // we adjust for the missing cycles
}
msecs++;                         // count the msec that just passed
if (msecs == 1000)               // if we have done a full second...
{
msecs = 0;                   // rewind the msecs counter
if (ElapsedTimeSecs)         // if big timer is active and non-zero...
{
ElapsedTimeSecs--;       // count the second that just passed
}
}
}

===================================================

Home