Let's talk 60 KHz.
As you may know, I have a lovely WWVB-synchronized clock that I built by hand about five years ago. Here's a photo:
The firmware is my own code and once in a while I tweak it a bit to try to get a little better performance out of it. The little module on the top left, just below the ferrite antenna, is a WWVB receiver module that pulls in the 60 KHz signal and outputs it on a single pin, as a high-or-low state. The pulse train from this pin must then be decoded. I think you can see that I used an Arduino Nano as the brains of the operation. It then speaks to the display using I2C.
We had a brief power blip, just for a few seconds, on Monday morning. Here on Wednesday evening it still hadn't picked up a full frame of time code. There is no battery backed RTC on this thing, and no manual setting buttons (a deliberate design choice) so it's been sitting there with a blank screen for three days. Not cool. As I drifted off to sleep last night, I noticed that the time code pulses (the yellow LED) were totally readable by me just by looking at them, so why couldn't I train the software to be smarter? What it needed was some hysteresis. The original code read the receiver pin every loop cycle and considered high=1 and low=0, so if there was even the tiniest flickering of the signal in the middle of a pulse, it would throw the whole frame away. I added some new code: now we take 100 samples (which still happens in less than 1 millisecond) and if at least 20% of those samples are high then we consider that a 1; fewer than 20% is a 0. If that sounds like a ridiculously low threshold to you, I agree. But I originally set it to 50% and it was still not enough.
The photo above was taken this evening, right after I brought the clock back into my bedroom after reprogramming it. It locked on to the time signal in the very first minute and set the clock.
That's what the red LED on the bottom is, by the way. I cut out the Nano's power LED. The built-in LED on D11 illuminates when the software has detected the top-of-minute frame marker and is locked in and receiving time code. If it receives any pulse length other than 200 (logical 0), 500 (logical 1), or 800 (marker bit) milliseconds, it assumes it's receiving junk and throws the frame away. The red LED goes out and it waits for a new frame to begin. If it completes the frame, however, it sets (or resets) the time, and illuminates the green LED. When I see green, I know it has performed a complete synchronization within the last 24 hours.
It's got a few more tricks up its sleeve, however.
As previously noted, there's no battery backed RTC. It's completely a free-running software clock timed by the 1 ms timer interrupt on the Arduino. That means 60,000 timer ticks is one minute. Or is it? As it turns out, the accuracy of this timer is terrible. One minute is somewhere around 60,000 ticks, give or take a few hundred. So even if we didn't receive a full frame of time code, if we receive the start-of-minute marker we can do a couple of things:
1. We know the end of the marker is exactly 800 ms past the top of the minute. So if the software clock reads anywhere between :45 and:15 seconds, we snap to :00.8
2. If we have two top-of-minute markers in a row, even if the time code in between was garbled, we now know the exact number of timer ticks that comprise a minute on this hardware. So we save that number. Up to ten of them, actually. And we average those out, and that becomes our reference minute.
Bonusfest: there's also a cadmium cell detecting ambient light. We crank up the display brightness when the room is bright, to avoid it washing out, and we dim the display in darkness, so it doesn't blasticate the room with display light while we're trying to sleep.
All in all it's been a ton of fun, and in addition to being a useful bedroom clock that never needs to be set manually, it also lets me geek out every night while I am drifting to sleep by watching das blinkenlichten.