Controlling Time with an Arduino

Correction: In the original version of this article I stated that you could "use an unmodified Arduino with a 16 MHz crystal it you change the CLOCK_FREQ#define in the code to 16000000."  I'm sorry to say that this was an error on my part and it will not work.  In my original design I was working with ATTiny85 running at 8 MHz and, in this case, you could set the CLOCK_FREQ #define to 8000000 to generate a frequency close to 60,000 Hz.  But, setting it to 16000000 cause the value computed for TIMER_VAL to exceed the 8 bit value this timer can handle.  If you have an Arduino running at 8 MHz you can set CLOCK_FREQ to 8000000 and that will work.  My apologies for this error.  It was a last minute change to the text (repeated in the code) that I did not circle back and verify, as I had then shifted to using an Arduino with a 15.36 MHz crystal.  Note: I have updated the code linked at the end of this article to correct the same error.

This article was inspired by a recent post to the "time-nuts" mailing list mentioning the possible future demise, due to budget cuts, of the time broadcast stations WWV, WWVH and WWVB now maintained by the National Institute of Standards and Technology (NIST.)  This lead to a great deal of discussion about what might happen to those consumer "atomic clock" devices you can buy that rely on WWVB to set the time automatically.  Would these all just become useless paperweights should NIST actually decide to shut down WWVB?  If so, this article is intended to provide a possible alternative.  As a solution,  some in the time-nuts group proposed the idea of locally generating a synthetic WWVB signal using a GPS receiver as a source of accurate time.  I thought this sounded like a reasonable idea, so I decided to see what it would take to do this with a minimal amount of components and work.

For some background, WWVB is a time signal radio station that broadcasts an amplitude modulated time signal at a frequency of 60 kHz. (Note: WWVB was upgraded in 2012 to also simultaneously broadcast a phase modulated signal, but this signaling system is more complex and I will not discuss it further in this article.)  The amplitude-modulated carrier is broadcast on precisely 60,000 Hz and takes 60 seconds to transmit a complete frame of data.  Each second in the frame coveys one of three different types of bits, one bits, zero bits and marker bits (more on this later.)  It's important that the carrier signal be as close to 60,000 Hz as possible because WWVB receivers are designed with an extremely narrow passband to avoid interference and allow what can be a very weak signal in remote locations to reliably receive the time signal.  

For my experiments, I decided to use an Arduino Duemilanove because its 16 MHz oscillator crystal comes in an HC-49-style, through hole package which makes it easy to unsolder and replace with a 15.36 MHz crystal. The photo below shows my test setup. I used a 15.36 MHz crystal because it can be divided evenly down to 60,000 Hz. Conveniently, 15.36 MHz is close enough to 16 MHz that I did not need to modify the boot loader's baud rate.

      

Click image on left for larger view, and on the animated GIF on right to see the time change on the BALDR clock

The time and the date shown in the photo, 4:15 PM on March 6th was received by the BALDR Model B0114ST Atomic Alarm Clock by the signal broadcast via the small, wire antenna connected to pin 3 of the Arduino.  This signal is created by using timer 2 to generate a PWM output signal on Arduino at that frequency on data pin 3.  The WWVB signal modulates the carrier at one of two different signal levels, transmitting either at an ERP of 70 kW (0 dB) or at -17 dB below this level.  To simulate this, the code in the Arduino sketch varies the PWM value between a duty cycle of 50/50 to simulate WWVB's maximum carrier strength, or a duty cycle of 1/22 to simulate the lower, -17 dB strength signal. 

To get a source of accurate time, I used a GlobalTop "LadyBird" GPS module (PA6C011449) because I had one left over from another project.  However, the Adafruit "Ultimate GPS Breakout" uses a very similar GS module that should work fine with my code.  Here's how to connect it to the Arduino.

Arduino with GPS Module on Breadboard Shield and Tuned Ferrite Antenna

(click for larger view)

How It Works

Generation of the 60 kHz carrier is done by using the Fast PWM mode on timer 2.  The code that configures timer 2 looks like this:

DDRD |=  _BV(PWM_PIN);  // Set PD3 Arduino D3 as PWM Output pin

TCCR2A = _BV(COM2B1) |  // Clear OC2B (PD3 Arduino D3) on Compare Match

         _BV(WGM21) |   // Waveform Gen Mode 7

         _BV(WGM20);    // Waveform Gen Mode 7

TCCR2B = _BV(WGM22) |   // Waveform Gen Mode 7

         _BV(CS20);     // Set clock prescaler to 1:1

OCR2A = TIMER_VAL;      // Set OCR2A Top Count (clock rate / (OCR2A + 1) = 60 kHz)

OCR2B = PWM_FULL;       // Set OCR2B value to full carrier

The following set of define statements in the code is use to compute several of the values used in configuring timer 2 as well as other places on the code:

#define CLOCK_FREQ    15360000          // Used to compute the following timer values

#define TIMER_VAL     ((CLOCK_FREQ / 60000) - 1)

#define PWM_WEAK      (TIMER_VAL / 22)

#define PWM_FULL      (TIMER_VAL / 2)

The loop that transmits the code is locked to the PPS signal from the GPS and uses calls to the millis() function to control the timing of the modulation of each data bit.  Each bit takes one second and the loop uses a switch statement to select which bit to transmit next.  The switch statement, in turn, uses a set of macros to obtain the value of each different bit.  Bits transmitted  can be one of three different types, ZERO  ONE  or MARKER  where the modulation level starts out in the weak carrier state and then switches partway though the 1 second interval to the strong carrier state using the following timing:

Additional code is used to calculate the Leap Year flag (bit 55)  and the two bits used to signal changes in daylight savings time (bits 57 and 58.)  As written, this code is designed to run on a modfied Arduino where the 16 MHz crystal has been replaced with a 15.36 MHz crystal. You can run this code on an unmodified, 16 MHz Arduino if you change the CLOCK_FREQ #define to 16000000, but it will be a bit harder for for WWVB clocks to receive the PWM signal, as it will not be exactly 60 kHz.

Source code, in the form of an Arduino Sketch, is available as a zip file download at the bottom of this page. 

Test and Validation

To test this modulation and timing scheme, I opened up the BALDR Model B0114ST clock and brought out the demodulated data signal and a ground connections as shows in the following photo of the clock's PCB:

Click for a larger view

Then, I added some conditional code to generate a sync signal that goes high at the start of the first marker bit at second zero and goes low at the end.  Using this signal to sync my scope and putting the modulated PWM signal from pin 3 and the demodulated signal from the clock, I compared the signal to see how well the demodulated signal tracked my simulated WWVB signal.  The following photo shows the result:

The bottom trace shows the sync signal framing the first Marker bit. The middle trace shows the PWM signal from pin 3 of the Arduino and the top trace shows the demodulated data stream from the BALDR clock. As you can see, the demodulated signal seems to track quite well with the modulated signal.  After some experimentation, I tried wrapping the antenna wire around the ferrite core of a WWVB module module (unpowered) I happened to have lying around (see photo below) and this increased the distance I could place the clock from the antenna to around 6 to 8 inches rather then 3-4 inch range I saw using just the wire as an antenna.  It's possible that this is because the ferrite rod helps to magnetically couple the signal to the similar, tuned ferrite rod antenna in the clock, but it also might be due to the coil and capacitor on the rod being tuned to 60 kHz.