Thursday, October 19, 2017

My WWVB Radio Clock

Obelisk, a.k.a. O-3, is the third (and probably final) digital clock I've designed and built that is ultimately synchronized to the atomic clocks that serve as our standard for time in the U.S. It synchronizes to the sixty kilohertz transmission from radio station WWVB in Fort Collins Colorado. WWVB is operated by the National Institute for Standards and Technology (NIST). NIST has one of their big facilities in Boulder Colorado, where the F2 master atomic clock, the source of ultimate time and frequency reference for the United States, is located. Fort Collins is just a hour or so drive north of where I am located near Denver Colorado. But the WWVB time signal reaches most of the continental U.S. and beyond, typically at night when the sun doesn't interfere with the long-wave transmission.

Untitled

The hardware of Obelisk is very similar to my prior two clocks. It is based on a Raspberry Pi 3 Model B running Raspbian, a Linux distro based on Debian. It uses an Adafruit LCD display. And it includes a real-time clock board with a battery backup, based on the DS1307 chip, to maintain the time when the system isn't running.

What sets this clock apart is that it uses a tiny SYM-RFT-60 AM radio receiver configured out-of-the-box to pick up the WWVB sixty kilohertz transmission. The receiver provides just six pins: one for power, one for ground, two for the antenna, one for reset, and the last for the digitized and inverted data derived from the modulated radio signal. The reset and data pins are connected to GPIO pins on the Pi.

The WWVB radio signal is amplitude modulated: the reference state is 0 dBr and the asserted state is -17 dBr.  The receiver inverts this so that the reference state is read by the GPIO as a zero, and the asserted state is read as a one. The transmitter emits one pulse per second, with the leading edge of each pulse aligned to the beginning of a second as determined by an atomic clock located at the transmitter. The duration of the pulse is used to encode three symbols: 200 milliseconds is a binary zero, 500 milliseconds is a binary one, and 800 milliseconds is a special marker used to identify the boundaries in the data frame.

A frame consists of exactly sixty pulses, so there is always one frame per minute. The leading edge of the first pulse in the frame indicates the beginning of the first second, :00, in the current minute, and the leading edge of the last pulse of the frame indicates last second, :59.

Values in fields are encoded as binary coded decimal (BCD) digits. So, for example, the minute of the hour is encoded as two BCD fields, the tens portion, and the ones portion. The UTC minute and hour, the day of the year (Julian day), and the last two digits of the year, are encoded in fields in the frame, along with other useful stuff like whether the current year is a leap year, whether a leap second is going to be added at the end of the current month, whether Daylight Saving Time is in effect, and the difference between the UTC time and UT1 time. (UTC is kept within +/- 0.9 seconds of UT1, a time reference based on celestial observations that is relative to the Earth's rotation, through the introduction of leap seconds.)

Obelisk merely samples the GPIO pin in user-space using a periodic timer, and measures the pulse width to determine the three symbols. Most everything else is just the bookkeeping involved with decoding the fields in the frame and turning them into a useful timestamp. Not that that was completely trivial.

The hardest part of this process was designing the state machine to parse the language whose grammar is implied by the layout of the symbols in the frame. While the AM radio signal can be received throughout all or most of the continental United States, it is extremely susceptible to interference. (This is what Steely Dan was alluding to when they sang "no static at all" in their 1978 song FM.) The WWVB signal can be corrupted by the placement of the radio antenna, by other electronic devices (particularly LCD displays), and even by strong sunlight. So the Obelisk parser tends towards paranoia, with a lot of stuff for error detection and recovery.

One technique I have found useful to measure the quality of my clocks is to run the Network Time Protocol daemon on them and use my implementation as an NTP reference clock. Obelisk emulates a GPS time source by emitting standard National Marine Electronics Association (NMEA) RMC sentences containing a UTC time stamp every second once it is disciplined to WWVB. It also generates a Pulse Per Second (PPS) on another GPIO pin; the PPS is synchronized, subject to some software delay and jitter, to the leading edge of the WWVB pulses that are timed to begin at the start of every second.

NTP continuously compares the reference clock with clocks from other NTP servers on the internet. Querying the NTP daemon on Obelisk for its statistics often reveals that my implementation is pretty usable. It will never be as good as a GPS-disciplined NTP server; the WWVB signal is just not that reliable. But as a desk clock, it's completely serviceable. Below you can see the output from the ntpq command that includes my radio clock masquerading as PPS and GPS reference clocks. This snapshot was taken while NTP was pretty happy with the output of my software.

Untitled

WWVB radio clocks are inexpensive and common; there are several such off-the-shelf devices scattered around the Palatial Overclock Estate. And WWVB radio clocks can be quite small. One of the wristwatches in my collection is a Casio Wave Ceptor with a built in WWVB radio receiver which it uses to set itself. (It's also solar powered.)

Casio Wave Captor Multi Band 6 Tough Solar

My first clock, Hourglass (O-1), synchronizes to the signals from the satellites in the Global Positioning System. Like other radio navigation systems I have studied - for example, LORAN, for Long Range Navigation - GPS is based on time: the accuracy of the GPS position fix depends on the precision of the timestamps generated by the GPS system. Each GPS satellite has multiple atomic clocks, each of which is synchronized to the U.S. Air Force master atomic clock at the GPS ground station near Colorado Springs Colorado. I described Hourglass in My Stratum-1 Desk Clock. It was a relatively straightforward project, using off the shelf hardware and open source software. If you want an inexpensive stratum-1 NTP server, this is the approach I would recommend; reasonably priced commercial products are also available and which I describe in this same article.

Hourglass on a Stand

My second clock, Astrolabe (O-2), similarly synchronized to GPS, but uses a miniature cesium atomic clock to maintain time if and when the GPS signal was lost or compromised. I described Astrolabe in My Stratum-0 Atomic Clock. The software for Astrolabe was almost identical to that of Hourglass, although I found the hardware design much more challenging; there was a fair amount of hardware hacking (for a software guy like me anyway) to integrate the board containing the chip-scale atomic clock with the rest of the system. The biggest problem was that the Raspberry Pi is a 3.3 volt device and the CSAC board is a 5 volt device with standard RS-232 12 volt serial output. The design was complex enough that I felt the need to design in test points through which I could examine both the cesium atomic clock and its uBlox GPS receiver independently of the rest of the system.

Untitled

Obelisk differs from these two clocks in that I had to write a substantial amount of software to decode the WWVB signal and put it in a form usable by the open source software in the clock. But that's what learning experiences are all about. Obelisk provides a user-space application I call wwvbtool. The lower level details of decoding the WWVB transmission is handled by functions in the Obelisk library, which can be used for other WWVB-based applications. wwvbtool also makes use of my Hazer (GPS sentence generation and parsing) and Diminuto (Linux systems programming) libraries. wwvbtool, Obelisk, Hazer, and Diminuto are all written in C. All of the code is open source - licensed under either the GPL or the LGPL - and can be found on GitHub.
https://github.com/coverclock/com-diag-obelisk 
https://github.com/coverclock/com-diag-hazer  
https://github.com/coverclock/com-diag-diminuto 
Although wwvbtool can be used from the command line, Obelisk runs it as a background daemon where it communicates with the GPS daemon gpsd, which in turn communicates with the NTP daemon ntpd. Obelisk also uses the same hourglass.py Python script I wrote for Hourglass and Astrolabe to maintain the LCD time display.

When Mrs. Overclock (a.k.a. Dr. Overclock, Medicine Woman) and I first moved to Colorado in 1989, I didn't appreciate that I was going to be living at a nexus of super precise time keeping, what with NIST's F2 atomic clock in Boulder, the WWVB NIST transmitter near Fort Collins, and the GPS ground station near Colorado Springs, all a relatively short drive west, north, and south respectively.

But precision timing and frequency sources keep cropping up again and again in my professional life, from the supercomputers and their associated peripherals and networks that I worked with at the National Center for Atmospheric Research in Boulder, to the traffic shaping and traffic policing firmware I wrote for ATM OC-3 optical networking products at Bell Labs north of Denver, to the geolocation and navigation systems in products I helped develop for the business aviation marketplace for a client near Denver.

The other day I was talking about Hourglass, Astrolabe, and Obelisk with a friend and colleague of mine. He repeated that aphorism "A man with more than one clock never knows what time it is." I had to remind him that my three clocks all display exactly the same time.

9 comments:

tychotithonus (Royce Williams) said...

Do you take requests? ;)

I'd be super interested in a follow-up project that investigates feasibility of implementing additional time precision based on the newer WWVB phase modulation introduced in 2012. Leap seconds and other information are now included.

Dan Drown's done some initial and subsequent work analyzing the new signal.

I'm more software than hardware, but would love to help/test. I bought one of the only commercial clocks that uses the new signal - the La Crosse UltrAtomic 404-1235UA-SS (teardown from LeapSecond.com here), and it works reasonably well in my traditionally WWVB-challenged area (southcentral Alaska), so I'm optimistic that this might yield something NTP-ready that's outside of the de-facto GPS monoculture and usable in Alaska.

Chip Overclock said...

Alas, I am also more software than hardware. So unless someone sells a receiver that is within my limited capabilities to interface to, say, a Raspberry Pi, the glue portion of such a project is probably beyond me. But thanks for the tip, I'll look into it!

Paul Theodoropoulos said...

Hi John (and howdy Royce!),
I'm trying to get this working with a different chipset (because that's what I happened to purchase on ebay). Rather than the SYM-RFT-60, I have a device built on the MAS6180c https://universal-solder.ca/product/wwvb-msf-jjy60-atomic-clock-receiver-module-60khz/ .

I'm able to build and run all of the tools required as described in the obelisk 'suite' - but I can't seem to get the correct output. If I connect the timepulse out from this board to RPI pin 12/GPIO 18, I get an appropriate PPStest output; however if I connect as described in the obelisk readme - timepulse to RPI pin 18/24, with the additional 'jumped' pins RPI pin 22/GPIO 25 to RPI pin 12/GPIO 18, I get no PPS. Likewise, the NMEA test (wwvbtool -r -s -u -l -n -p | gpstool -R) gives no output.

Could this be related to the SYM-RFT-60 inverting the assert line, whereas (as far as I can tell) the MAS61880c does not? If so...is there a fairly straightforward fix?

All that said, it's also entirely possible that I'm just out of my depth here. Not a coder, fair to midling at hardware. If you have any insight I'd be grateful, and if this is too far afield, I understand that as well. Thanks!

Chip Overclock said...

Paul:

I believe the output of both our boards should be logically correct: high == 1, low == 0. I can’t remember if the chip on my board inverts the WWVB signal or additional circuitry on the tiny board its on does it (the former I dimly recall), but my code definitely is implemented for high == 1 being the start of a frame, although you’re quite correct that the raw WWVB AM modulated signal is the opposite.

If I’m full of crap, you can ask the GPIO system on the Pi to invert the signal by adding

diminuto_pin_active(pin_in_t, 0);

in wwvbtool.c right before

pin_in_t_fp = diminuto_pin_input(pin_in_t);

to indicate that the pin is active low (the “0”) not active high. But I don’t think that’s the issue.

My chip, and I believe also yours, doesn’t generate a 1PPS signal. The Tout digital output is a raw modulated signal that has its own framing conventions and protocol. That’s what wwvbtool decodes. The 1PPS output is strictly synthesized by the software from the incoming WWVB frames (and it’s not anywhere nearly as accurate as, for example GPS, partly because of software jitter in the Linux scheduling). wwvbtool synthesizes a 1PPS and outputs it on GPIO 25 (I think), which is jumpered to another pin that is read by the GPSd/NTPd deamons (which expect it to arrive on a pin… it’s a bit of a hack on my part).

Wikipedia has a good article on this format, along with URLs for more information. The README.md in my repo has these web links.

I also found my receiver to be extremely sensitive to position, orientation, and location. I found the same to be true of three commercial WWVB clocks in my home as well that don’t implement the latest PSK format framing (something new, very hard to get chips to receive it, but I do have one commercial clock that handles it). I needed it to be near a window, the ferrite antenna to be orthogonal to the direction to Fort Collins (just an hour drive north of my home), and not near a big LCD display or electric pencil sharpener (both of which sit on my computer desk in my office). When my wife uses the pencil sharpener (daily) it completely jams the signal while the electric motor is activated, although my code recovers very quickly.

I’m not sure what else to suggest. I’ll look more at the data sheets you pointed me to and if anything occurs to me I’ll let you know.

Best of luck!

:John

Chip Overclock said...

Royce:

I thought I'd mention I bought one of the new La Crosse wall clocks that use the new WWVB PSK signal. It does indeed work much better without all the drama of having to worry about position, orientation, location, etc. I have had no luck finding a source of receivers using the chip that they use in that clock. An eval board is mentioned here and there, which would be ideal for guys like me (and you). But I've had no luck acquiring one.

Regards!

:John

Chip Overclock said...

Paul:

Some more thoughts.

Make sure the PDN (power down, "P1" on my device) pin is in the correct state (low or ground) to enable the chip. My chip's equivalent P1 pin is also active low.

Don't be afraid to put a voltmeter - or even better a hobbyist quality oscilloscope - on the T output pin and see what it does.

:John

Paul Theodorpoulos said...

Thank you so much for all the info John! Much to sort through here. I'll report back (eventually!)

Paul Theodoropoulos said...

So, a quick followup.

Adding 'diminuto_pin_active(pin_in_t, 0);' and rebuilding made no difference, as suspected.

I wasn't totally clear in my description of the ppstest - when I connect time pulse out from the board to RPI pin 12/GPIO 18 - with nothing else running - no gpsd, no wwvbtool - I get a fairly steady output from ppstest /dev/pps0 -

root@ raspberrypi /usr/local/src # ppstest /dev/pps0
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1536432535.260326675, sequence: 964 - clear 0.000000000, sequence: 0
source 0 - assert 1536432536.254569675, sequence: 965 - clear 0.000000000, sequence: 0
source 0 - assert 1536432537.279755675, sequence: 966 - clear 0.000000000, sequence: 0
source 0 - assert 1536432538.287769675, sequence: 967 - clear 0.000000000, sequence: 0
source 0 - assert 1536432539.260693675, sequence: 968 - clear 0.000000000, sequence: 0
source 0 - assert 1536432540.254929675, sequence: 969 - clear 0.000000000, sequence: 0

Compared to the pps out from my gps-based clocks, the large variations of the decimal values suggests that this is the wwvb signal, warts and all (I'm in the SF bay area)

For comparison:

root@RaspiPi3B+ Neo M8N clone ntpsec: ~ # ppstest /dev/pps0
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1536562885.000000359, sequence: 793702 - clear 0.000000000, sequence: 0
source 0 - assert 1536562886.000000935, sequence: 793703 - clear 0.000000000, sequence: 0
source 0 - assert 1536562887.000000774, sequence: 793704 - clear 0.000000000, sequence: 0
source 0 - assert 1536562888.000001218, sequence: 793705 - clear 0.000000000, sequence: 0
source 0 - assert 1536562888.999999526, sequence: 793706 - clear 0.000000000, sequence: 0
source 0 - assert 1536562890.000000800, sequence: 793707 - clear 0.000000000, sequence: 0

A much much tighter output - as expected from gps.

Likewise - using pigpiod and monitor.py from the same source - and nothing else running as before - I also get a pretty steady output:

root@ raspberrypi /usr/local/src # pigpiod
root@ raspberrypi /usr/local/src # python monitor.py
G=18 l=0 d=492911
G=18 l=1 d=508852
G=18 l=0 d=200660
G=18 l=1 d=792812
G=18 l=0 d=503486
G=18 l=1 d=500642
G=18 l=0 d=791207
G=18 l=1 d=236040

So I'm inclined to believe I have something fubar, somewhere, in how I'm trying to get this all running. But more experimentation is needed, which will have to wait for tomorrow evening.

Thanks again for your indulgence.

Chip Overclock said...

Paul:

Yeah, I agree. Could be something I've described incorrectly (or failed to update) in the README. Or some assumption I've made that isn't correct.

When you examine /var/log/syslog, do you find stuff like this, cut and pasted from my own WWVB clock? I have wwvbtool set up to log the time every hour.

pi@obelisk:/var/log $ grep -i wwvb syslog
Sep 10 06:59:59 obelisk wwvbtool[23103]: wwvbtool: time zulu=2018-09-10T12:59:59 julian=2018/253 day=MON dst=+ dUT1=+0.1 lyi=0 lsw=0.
Sep 10 07:59:59 obelisk wwvbtool[23103]: wwvbtool: time zulu=2018-09-10T13:59:59 julian=2018/253 day=MON dst=+ dUT1=+0.1 lyi=0 lsw=0.

When I deliberately jam the signal with the electric pencil sharpener next to the clock (who knew it would be such a useful diagnostic tool), wwvbtool complains about losing the signal, and then describes resynchronizing on the framing and then reacquiring the time once it has received a complete frame.

pi@obelisk:/var/log $ tail -n 0 -f syslog
Sep 10 08:20:23 obelisk wwvbtool[23103]: wwvbtool: lost risings=0 fallings=0.
Sep 10 08:20:24 obelisk wwvbtool[23103]: wwvbtool: synchronizing.
Sep 10 08:21:01 obelisk wwvbtool[23103]: wwvbtool: synchronized.
Sep 10 08:21:59 obelisk wwvbtool[23103]: wwvbtool: acquired.

Finally, don't hesitate to contact me via email at coverclock at diag dot com.

:John