Controlling Time with an ATTiny85
Note: this is a work in progress that I will continue to revise as new features, such as a GPS input, are added.
Revised 8/30/2018 after adding an 8 MHz crystal rather than use the ATTiny85's internal, 8 MHz oscillator. The source code has been updated accordingly.
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.
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.) 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. With inspiration from an Instructables article by sbmull, the following photo shows what I came up with:
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:39 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 6 of the ATTiny85 (see schematic below.) An 8 mHz crystal is connected from pin2 to pin 3 of the ATTiny85 and each of these pins if connected via an 18 pF capacitor to ground. The 60 kHz signal is generated as a PWM signal using the ATTiny85's timer2 and timer0 is used to drive a 60 Hz interrupt that controls the transmission of the data bits. The PWM value is varied between a duty cycle of 50/50 to simulate the maximum carrier strength sign and duty cycle of 1/22 to simulate the lower strength signal.
How It Works
Generation of the 60 kHz carrier is done by using a common feature of the AVR series micro controllers called Clear Timer on Compare Match (CTC) Mode on timer 1. Setting the COM1A0 bit (in combination with also setting the PWM1A bit) in the TCCR1 register puts timer 1 into a mode where timer 1 will continually count from 0 to the value set in register OCR1C and then reset back to 0. In addition, setting the CS10 bit in the TCCR1 register sets the timer prescaler to 1:1 so it's counting directly from the 8 mHz clock. With these settings, timer 1 becomes a counter that divides by the value set in OCR1C + 1. With the prescaler set to 1:1, the formula for setting the PWM output frequency is fPWM = clkFreq / (OCR1C + 1), or a value of 132 generates a PWM frequency of ~60,150 Hz. The value set into register OCR1A controls the PWM duty cycle as the value varies from zero up to the value set into register OCR1C, the duty cycle of the PWM signal on pin 6 of the ATTiiny85 will value form zero to 100%. To simulate the WWVB modulation levels, OCR1A is set to a value of 6 for the weak carrier and 66 for strong carrier signal. Here is the code that configures timer 1:
TCCR1 |= _BV(CS10) | // Set clock prescaler to 1:1
_BV(PWM1A) | // Enable PWM based on OCR1A
_BV(COM1A0); // Set PWM compare mode
DDRB |= _BV(PB1); // Set PB1 - pin 6 as PWM Output
OCR1A = 6; // Set beginning OCR1A value to weak carrier
OCR1C = 135; // Set OCR1C Top Count (8 MHz / (132 + 1) = 60 kHz)
// Note: (Actual OCR1C value tweaked for internal osc variance)
The code also sets up timer 0 to produce a 60 Hz interrupt that drives a loop used to generate the timecode. The prescaler feeding timer 0 is set to divide by 1024 so, using the same formula as for timer 1, a value of 129 generates an interrupt at ~60.096 Hz. Each bit takes one second so, on each interrupt, it 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:
ZERO bits transmits weak carrier for 0.2 seconds and then switches to strong
ONE bits transmits weak carrier for 0.5 seconds and then switches to strong
MARKER bits transmits weak carrier for 0.8 seconds and then switches to strong
Currently, the code simply transmits a test signal where the time signal is set for a UTC starting time of 00:00 on March 6th of year xx18 which, in the Pacific time zone, will set the clock to a time after 4:00 PM. On the theory that minutes need to vary from frame to frame in order for the clock to properly sync, I set the test code to cycle from 00:00 through 00:09. In my observation, it takes the BALDR clock 3 to 4 minutes before it syncs to the simulated time signal.
Source code, in the form of an Arduino Sketch, is available as a zip file download at the bottom of this page. In order to compile and use this code, you need to first install ATTinyCore by Spence Konde. This adds in the ability to use the Arduino IDE to compile ATTiny85 code. You'll also need an ICSP Programmer (or an Arduino programmed to act like one) to program the ATTiny85. Note: I may rework this code to run on a standard, ATMega328-based Arduino board in the near future, as that should make it a bit easier for other people to use this code.
Programming the ATTiny85
Programming the ATTiny85 with ATTinyCore requires some additional setup in the Arduino's "Tools" menu. First, using the "Board" submenu, you'll need to select the ATTiny85 as the Target (should be listed in a section headed by the label "DIY Tiny".) Next, using the additional submenus that appear once you've selected ATTiny85 in the "Board" submenu, set the other submenu options that are shown with an asterisk, as follows:
Next, with the ISCP Programmer connected and 5 volts supplied to the the ATTiny85, select "Burn Bootloader" in the "Tools" menu. This step will program the fuses in the ATTiny85 to configure it to use an external crystal for its clock. Note: you'll only need to perform this step one, as the fuse settings are not altered when you upload program code to the ATTiny85. From this point on, you should be able to use the Arduino IDE's "Upload" button to program the sketch into the ATTiny85.
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 6 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 6 of the ATTiny85 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, but I did have to fiddle with the placement of the antenna wire next to the clock to get the clean demodulation signal you see above. However, 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 seemed to greatly reduce the twitchiness I initially observed with the antenna placement. It's possible that this is just because the ferrite rod help couple the signal, but it also might be due to the coil already on the roof being tuned to 60 kHz.
Things To Come
I've ordered a 15.36 MHz crystal to replace the 8 MHz one, as dividing 15.36 MHz by 256 will produce exactly 60,000 Hz. I'm hoping this will additionally reduce the twitchiness I observed with the antenna placement. Using a non standard frequency for the clock could create problems for the baud rate timing used by SoftwareSerial library that I'll need to communicate with a GPS module (or the boot loader if switch to using an 328-based Arduino), but 15.36 mHz is close enough to 16 mHz that I can probably just tweak the baud rate a bit to compensate.
Then, the next step is probably to hook up a GPS module and modify the code to use this to obtain the actual time to transmit. In order to properly sync the tranimssion to GPS time, I'll also need to sync the bit times in the overall transmission frame to the PPS signal provided by the GPS and match this to the NMEA message I use to obtain the time in order to sync bit zero of the transmitted frame. This may vary from GPS to GPS and may require the code to transmit one, or more configuration messages to the GPS module in order to set up properly.
Finally, there's also the issue of setting the Daylight Savings Time (DST) bits (bits 57 and 58 in the frame) as well as the Leap Year flag (bit 55.) I'm sure these can be addressed algorithmically in the code, but a bit of research is needed here before I can code that up.