TL;DR You're doing it wrong. Use RTC.
Ok, longer explaination is required here.
millis() is not accurate and here is why.
First, grab official Arduino source code from their Github repo.
contains few interesting macros (I'm pasting
grep output here):
Arduino.h:#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
Arduino.h:#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
Arduino.h:#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
clockCyclesPerMicrosecond() will evaluate to
16. Keep this number in memory.
Those macros are used in
// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
MICROSECONDS_PER_TIMER0_OVERFLOW macro is evaluated as
(64 * 256)/16 - a dodgy looking 1024. Look how they round it down to calcuate
Let's assume that you run your Arduino with ideal main clock signal.
At 16MHz main clock, the timer frequency is 1.024kHz, not 1kHz. This results in 2.4% inaccuracy. You can get away with that in most cases, but not when you're building a clock.
This 2.4% error is best case. Your main oscillator is not 16MHz - it has it's own inaccuracy, that is unknown and it sums up.
You should use an external, accurate time reference provided by RTC module. When assembled properly, they guarante low time drift, temperature compensation and other goodies. Be smart - use RTC. :)
If you are scared of I2C RTC modules, you may find my tutorial
and another tutorial on MCP7940N RTC module. Know-how is applicable with other RTCs as well.