You do not need an RTC to build a clock: the ATmega chip has all the
hardware needed to perform the duties of the RTC itself. Here is how:
Get a 32768 Hz watch crystal: either buy it or disassemble an
old clock. These crystals, specifically designed for time keeping,
have an extremely small temperature drift. You would also need one
of those if you wanted to use an RTC chip.
Configure the fuses of your ATmega to run off the 8 MHz RC
oscillator. This will make your millis()
function horribly
inaccurate, and also free the XTAL1 and XTAL2 pins.
Connect the watch crystal to the TOSC1 and TOSC2 pins. These are the
same pins as XTAL1 and XTAL2 (9 and 10 on the 328P). The different
names are used to mean different functions.
Configure the Timer/Counter 2 for asynchronous operation, normal
counting mode, prescaler set to 128, and enable the timer overflow
interrupt.
Now you will get a TIMER2_OVF interrupt at a very steady rate of once
per second. You only need to advance the clock display by one second in
the ISR. In between the interrupts, you can put the MCU in very deep
sleep (power-save sleep mode: nothing runs but Timer/Counter 2) and run
for years on a couple of AA cells. Unless the display is power-hungry,
obviously.
I did exactly this to build my 24-hour one handed wall
clock.
This link points now to the English translation of the original
documentation in French.
Quartz calibration
If you do not calibrate your quartz, you can expect a significant drift,
typically a few seconds per week. The drift rate depends on the
stray capacitance of the traces that connect the crystal to the MCU. In
principle, it could be removed by adding some extra, finely tuned
capacitance. It is worth noting that you would have the same drift
problem with an RTC.
If you are satisfied with this kind of accuracy, then live with it and
be happy. However, if you care to measure the drift, you will notice
that it is very stable. You can then easily compensate for it in
software, and achieve an accuracy of a few seconds per year.
The algorithm for correcting the drift is very simple. From the measured
drift, you figure out the precise delay between the interrupts, which
should be very close to 109 nanoseconds, then:
#define ONE_SECOND 1000000000 // in nanoseconds
#define ONE_INTERRUPT 999993482 // for example
ISR(TIMER2_OVF_vect)
{
static uint32_t unaccounted_time;
unaccounted_time += ONE_INTERRUPT;
while (unaccounted_time >= ONE_SECOND) {
advance_display_by_one_second();
unaccounted_time -= ONE_SECOND;
}
}
In the above example, the quartz is slightly too fast, and the software
compensates by “missing” a tick every few days. If the quartz was too
slow, the same code would instead double-tick once every few days.
This kind of calibration could also be done for an RTC, but it would be
significantly more complex because the RTC reports the time in a
broken-down form that does not naturally lend itself to arithmetic
operations.
millis()
.