Average Clock is six clocks running independently, slowly diverging, while a microcontroller tries to make sense of it all.
The quartz crystals that regulate most household clocks oscillate at 32,768 cycles per second. This frequency is used because it is equal to 2^15 cycles per second, and hence can be divided easily by computers using base 2 math. In practice, however, inexpensive crystals never run exactly at 32,768 cps. The discrepancy shows up over days or weeks and we have to adjust the time—from time to time. Crystals are also affected by temperature, aging, shock and even radiation.
Average Clock was developed on the premise that consumer-grade timekeeping crystals diverge in accuracy equally above and below the target frequency. Given enough inaccurate clocks, if they are inaccurate equally, the average time will be correct.
This appliance depends on failure for its accuracy… That’s the theory, anyway.
The Hardware
Download the Eagle files, the main board and dual-clock display module schematics.
The six clocks are Maxim DS1307 real-time clock chips. They require a minimum of additional support circuitry: VCC and Ground, a 32 kHz crystal, a couple of 4.7k resistors (which are not required because we’re using pullup resistors in the controller) and a 3v backup battery. The chip communicates with an ATMEGA328 using the I2C protocol, using just two wires: SDA (serial data input/output) and SCL (serial clock). These lines are connected to the controller on Analog pins 4 and 5.
The I2C protocol has a wide address space, which basically means that you can—in principle—hang a hundred different I2C slave devices off of just two wires, and as long as you know the address of each device you can read or write to it. Most I2C devices, such as memory, LCD displays and bus controllers are individually addressable, but since Maxim figured that only someone truly demented would want to have more than one clock in a device, they designed the address of the chip to be the same for each chip.
This drawback is solved with external hardware, using two 4051 CMOS multiplexer/demulitplexer chips. The 4051 allows us to direct our attention to one of up to eight—though we only need six—of the clocks. We need two 4051’s: one for the SDA line and another for the SCK line.
Time for each individual clock is shown using small red seven-segment 4-digit LED displays. Three MAX7219 LED driver chips display the six times and a third 7219 displays the average time using a larger amber 4-digit display.
The circuitry is modular: three display modules on a home-made two-sided printed circuit board are connected to a hand-wired main microcontroller interface that holds the clocks, batteries and microcontroller.
Average Clock has a small control panel oriented at right angles to the rest of the hardware.
The Software
Download the Arduino files here.
The software handles the clock initialization, multiplexer control for addressing each clock, sending the clock data to the MAX7219 LED drivers, and calculating the average time. This was, in some respects, the most challenging part of the project. While it’s fairly simple to sum each time reading from six clocks, then divide that by 6, I discovered the “day rollover problem”. When approaching 23:59, the average time became erratic as each clock rolled over from 23:59 to 0:00. I experimented with various methods of overcoming this problem but as the program became more and more convoluted, I decided to give up, or at least put the problem aside for a rainy day. I can always pull the controller chip and re-flash it if I come up with a solution later.
Control
MASTER RESET activates the reset pin on the controller, causing it to reboot.
Average time can be hidden with the View switch; when it is in the VIEW OFF position,
pressing the VIEW button turns on the average time. When the switch is in the VIEW ON
position, average time displays normally.
Setup Procedure:
- Press the ZERO button. This sets all clocks to 0:00.
- Using the HOUR and MINUTE buttons, set all clocks to the correct time.
- Press the RANDOM button. This changes each clock by +/- 5 minutes. Press several times to scramble each clock while keeping the average value the same. Be prepared to go back to Step 1, since the clock scrambling may move the average clock time ahead or behind, and the more you randomize, the worse it gets. Ideally, two or three RANDOM presses should result in a correct time reading on the Average Clock.
You may disconnect the power at any time; the two CR2025 lithium cells will keep the clocks running at extremely low currents for months, or possibly even years. If you need to replace the batteries, you should disconnect the main power first.
I’ve included an automatic adjustment for LED intensity based on ambient lighting. No extra charge.
Some times the displays will lock up: press MASTER RESET to return the clocks to normal.
Average Clock by Michael B LeBlanc is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.5 Canada License.
Contact the writer for permissions beyond the scope of this license.
One thing to remember is that all the crystals share the same dependency to temperature. So even when you get a good average by averaging the time of 6 crystals, you will get the same temperature curve. And this means easily 20ppm deviation in frequency, which you cannot correct that way.
re: day rollover problem, could you have the master clock check in with the other clocks only at 30 past the hour? so long as they are all within 30 mins of each other it won’t screw up the master clock. Really to correct for the initial discrepancy of the crystal, the clock would only need to check in once ever. temperature, age, radiation etc would all be similar for all crystals in your setup.
Really interesting concept!
This is for the “Average Clocks” ‘sketch’ but it also works for
anyone dealing with calculations involving time.
Convert Hours, Min, Sec to decimal seconds
(i.e. h x 36000 + m x 600 + s x 10).
Count up or down by 1 (1/10th of a sec) then divide by 36000 to get
back to decimal hours.
Most Hand/Computer Calculators have a ‘dms’ button to convert
Decimal Degrees to degrees, minutes & seconds and will also convert
decimal hours to hours, minutes & seconds.
Here is a C/C++ function for the ‘dms’ button:-
float dms(float dd) {
d = floor(dd); // an int
mf = (dd – d) * 60.0; // a float
m = floor(mf); // an int
sf = (mf – m) * 60.0; // a float
s = (floor(sf * 1000.0)) / 1000.0; // 3 decimals,
// no rounding
return float( d + (m/100.0) + (s/10000.0); }
This should return a float in this form ‘hh.mmssddd’ just like the
Calculator. (I verified this in Ruby but no guarantees!)
Regarding the “rollover” problem just add 24 hours (converted as
above) to any ’00:mm’ time and after calculating your average if
the result is ’24:xx just replace it with ’00:xx.’
Avoid Time Functions as most cannot handle ’24:xx’ times!
Tip: When writing code use the ‘wheel’ do not try to re-invent it!
@James: Your rollover solution does not work, you will have the problem around 1 am instead of around midnight.
I suggest the following solution:
1) Have
gettime()
return an *unsigned* number (uint16_t) representing the current time in 1/65536 day units (instead of centihours, as in your current version). This way arithmetics will automatically work modulo one day. You can compute the time in seconds (in a long int) and thenreturn time_in_seconds * 512 / 675;
2) Compute the average as following:
uint16_t average = aTime + (int16_t)(bTime + cTime + dTime + eTime + fTime - 5*aTime)/6;
where aTime, etc, are all uint16_t. This will work as long as aTime is less than 2 hours off the average. Notice the conversion from unsigned to signed and back: all the magic lies here.
Here is how it works: arithmetics on unsigned numbers works modulo UINT16_MAX+1. You can think of these numbers as living on a “number circle” instead of a number line, where 0 (meaning 00:00:00) comes right after 65535 (circa 23:59:59). In order to compute an average, you have to “cut” the circle, but you should avoid cutting in-between the numbers you are averaging, otherwise you have the rollover problem.
Instead of cutting at midnight or, as James suggests, at 1 am, I suggest cutting at aTime±12h, which should always be a safe place. This is done by first shifting everything by -aTime, thus the “relative” times are aTime-aTime, bTime-aTime, cTime-aTime… These relative times are averaged by computing the sum and then dividing by 6 (the term in parentheses is a simplification of the sum). However, before dividing, the sum is converted to a signed integer, which is equivalent to “cutting” the circle very far from zero: you get a result in [-32768,+32767]. Then, dividing by 6 provides the proper average of relative times. This is implicitly converted back to an unsigned number when we add back aTime, so the proper modular behavior is warranted.
You can then convert back to a number of seconds by
uint32_t average_seconds = average * 675L / 512;
The beauty of this method is that you do not have to do any tests for times being close to midnight, for overflows, etc: everything is handled automatically by the fact that the hardware works “naturally” in modular arithmetics.