Note: This post was originally published on the author’s Github repository for this project.
As described in the accompanying Hands On article, this project is a digital clock that uses a high stability 10 Megahertz oscillator as its timing reference. The oscillator is an oven controlled crystal oscillator, or OCXO, a technology that has seen significant improvements in recent years, drawing much less power at a much lower price.
The basic platform here is an OCXO combined with an Arduino Nano. The Nano offers far more processing power than is necessary for performing a simple hours-minutes-seconds type clock but is cheap and simple to use. However, the raw 10 MHz clock signal from the OCXO cannot be used to drive the Nano directly. The Nano uses a 16 MHz ceramic resonator for its system clock and it is not easy to change the frequency of this, to say 10 MHz, without significant modification to the Nano’s firmware because the 16 MHZ clock signal is used to derive the timing signal for the USB connection needed to load programs onto the microcontroller. A physical switch to switch between 16 MHz and 10 MHz after the program was loaded was an option, but not very practical.
Instead, the approach was to use interrupts. As with all microprocessors, the Nano supports interrupts, which, when triggered, can pause the currently running program code running, move the CPU’s registers to a memory location, carry out an interrupt service routine and then return the controller to normal operation by restoring the registers. Interrupts can generally be triggered by a clock-driven timer or an input on a dedicated external pin. The Nano includes three interrupt timers, and Timer0 and Timer1 can also be driven by an external clock source through the T0 and T1 pins. The timers work by being loaded with a binary number, which is then decremented every time a pulse is detected at their corresponding pin. When the timer reaches zero, an interrupt is triggered. Changing the number loaded into the timer alters the interval between interrupts. Timer0 is 8-bits where as Timer1 is 16 bit.
However, it was still not possible to drive the timers directly with the 10 MHz signal from the OCXO. When used with an external clock on T0 or T1, the signal at the pin is sampled relative to the 16 MHz system clock. Using a maximum Nyquist sampling factor of 2.5, a realistic maximum external input frequency of 6.4 MHz results—any higher and incoming pulses at the pin will be missed. Adding a 4-bit binary counter after the OCXO’s output results in one pulse being emitted by the counter for every 16 pulses it receives, as the counter counts from 0000 to 1111. With the OCXO operating at 10 MHZ, the counter’s output signal frequency is then 625 kHz. Consquently, having the timer count down from 62500 will result in an interrupt every tenth of a second. Seconds, minutes, and hours are then updated appropriately.
I chose an Arduino Nano as the clock’s microcontroller because it offer the same capabilities as larger Arduino controllers but is in a more compact form factor and is very cheap. In particular, it includes the usual SPI and I2C interfaces which make it easy to interface to modern displays with their built-in drivers. The OCXO is a Raltron Electronics OX4120A-D3-5-10.000-3.3. Any suitable OCXO could be used, even one with a difference frequency by changing the divider ratio. It also comes in a standard 14-pin dual-inline-package (DIP) configuration, although only 4 pins are actually used. The OCXO is a 3.3-volt part, so a LD1117V33C linear low-drop-out (LDO) voltage regulator derives this voltage from the 5-volt system voltage that is provided by the USB-C connection used to power the clock. A second, identical, regulator provides 3.3V to the display. The regulator type is not critical, alternative LDO or switching types could be used. The 4-bit counter is 74HC191. Although the counter is powered by the 5V system voltage, its input is compatible with the OCXO’s 3.3V logic level so a voltage shifter is not necessary.
The display is made up of six Adafruit CharliePlex FeatherWings. Each CharliePlex consists of 7x15 pixel array of LEDs which can be driven individually in a graphical mode or using fonts built into the Adafruit_IS31FL3731 library. They are communicated with via an I2C connection. The address of each CharliePlex can be set to one of two values: by default 0x74 but you can be changed to 0x77 by bridging a link with solder. To drive multiple displays I used a breakout board with the TCA9548A I2C expander chip, which lets me connect multiple CharliePlex’s and address each on a separate I2C bus, avoiding address collisions.
A single rotary encoder with a momentary press switch is used as the user interface. This gives the clock a modern minimalist feel. Separate press switches could of course be used with only minimum changes to the hardware and/or software.
All of the components used in this project have through-hole packaging for easy construction. A bill of materials with Digikey product reference numbers are available in my Github repository.
Printed Circuit Board
The printed circuit board was designed in KiCad. The PCB design is made available in the repository both as KiCad files and in Gerber format for others to have fabricated or modify under the creative commons licence.
The final board is not complex and designed as a double sided board which is easy to solder. It is can be easily broken into two sections along two cutouts, either with a saw or scoring and snapping. If broken into section, pads are provided so that headers can be soldered in to plug one board behind the other, making a more compact form suited to a desk clock. In this form, the “top” board holds the display and rotary encoder, with all else on the back board. The rotary encoder is fitting in the centre of the board, but this could course be moved to elsewhere and attached via flying leads. The prototype PCB was ordered through JCL in China, but any PCB fabrication house would do. The bottom portion of the board contains the Arduino, the I2C expander, the OCXO, divider chain and voltage regulators. The voltage regulators are mounted with the tabs facing outwards so heatsinks can be fitted if required. This wasn’t found necessary in the prototype, but if, for example the displays are driven at a high overall brightness it may be necessary. If the boards are mounted one behind the other, they should be placed back to back, so all the components will be accessible from the back of the clock. This will make the clock easier to hack later if required. M3 holes are fitted in the board for easy mounting and these line up for the upper and lower parts of the board if they are mounted back to back. Metal spacers can be fitted to provide the correct clearance and mechanical stability. Two mounting holes are also fitted above and below the OCXO. This is to provide mechanical stability. The OCXO is a relatively heavy component and held into the board simply by four pins in a DIP socket. This is also mounted vertically, so not particularly stable. A piece of wire can be threaded through the two holes with its ends twisted together to hold the OCXO in place. Cable ties could also be used, or a clamp if available. There are two sets of schematics, PCBs and gerbers files. This is because the TCA9548A modules comes in two flavours. The Adafruit one has a spacing of 0.6 inches between the rows (the “N” version) while some modules available on eBay have a 0.7 inch spacing. Double check which one you are using before fabricating the PCBs.
The software is also available in my Github repository. The source code relies on only two additional libraries, the aforementioned Adafruit_IS31FL3731 and the common Wire library which enables serial communication, essential for I2C connection to the display. Driving the displays requires individually programming each of the 105 LEDs on each CharliePlex. This is done using a set of look-up tables (LUTs) that are defined right after the library includes in the source code. There is one LUT per numeral. It is obvious by looking at them which numeral is stored in which LUT, and it is simple to replace the LUTs with a new set, representing a different font. Next, setup code checks that all of the CharliePlexes are functioning correctly and will send a debug message to any connected computer over the USB serial interface if not. Next Timer1 is configured to accept pulses from an external clock and to count down from 62499. This is 62499 and not 62500 to account for the time needed to reset the counter each time it reaches zero and triggers an interrupt of the program’s main loop. The primary function of the main loop is to call the subroutine that reads the rotary encoder. The encoder subroutine tries to eliminate switch bounce and is also easily modifiable. The main loop also updates the display, but only when a flag bit has been set by the interrupt timer. There is also a brightness variable here which is user settable as is the variable for temporarily increasing the brightness of a digit when it is being set. The code for the Timer1 interrupt service routine follows the main loop. This is called ten times a second, and takes care of incrementing the seconds, minutes, and hours as needed. A pair of LEDs between the seconds and minutes, and another pair of LEDs between the minutes and hours are also flashed here, toggling on and off every half second. Their function could be easily changed if desired. The final subroutine handles transmitting update to the display via the TCA9548A I2C expander chip.
The OX4120A-D3-5-10.000-3.3 used in this project is a fixed OCXO—its frequency can not be adjusted. Other OCXOs allow adjustments to be made by applying a control voltage to one of the pins, but I went with the simpler oscillator its stability was believed to be high enough for this project not to require adjustment. To check the accuracy of the clock requires an even higher accuracy reference clock. The best the author had in his lab was a HP 53150A frequency counter with its own OCXO reference. It was last calibrated in 2013. When measuring the OCXO reference the frequency on the counter fluctuated between 10,000,000 and 9,999,999. This suggests an agreement, and hence accuracy, of better than 1 part in 10,000,000. Being newer, it is likely that the OCXO in this clock is better than the HP counter!