WWVB decoder software

@jremington, I found your WWVB magic decoder ring sketch on Github.

https://github.com/jremington/WWVB_decoder

I could pretend I know what cross correlation is, but what would be the point. Anyway, I'm looking for a way to calibrate the Aging register of the DS3231 RTC, and am having trouble doing that in Windows. So I've ordered one of those $7 WWVB receivers from the Far East to see if that could work.

What I wanted to ask you is whether the millis() interrupt on timer0 could interfere with your decoding. If not the interrupt, what about the fact that the millis() count jumps by two every now and then? My initial impression is that with events taking place hundreds of milliseconds apart, none of that would matter much. But I wanted to ask. I have a millis() replacement that interrupts exactly once every millisecond, and doesn't do the double jump, and wondered if I should insert that in your code. It just sets up timer0 differently. Of course accuracy is still dependent on the crystal or resonator.

No, the bit sample/decode happens in the Timer1 ISR (while interrupts are off). Since the ISR takes just a couple of microseconds, it should not interfere with normal timekeeping.

I use the TimeLib.h library, synchronized by the standard millis() interrupt, to keep rough track of daytime.

The time of day could be synchronized to the WWVB time, but the code to do so is merely outlined, untested and currently disabled (lines ~286-289).

BTW cross correlation is really simple. The method compares the incoming waveform with two(*) rectangular reference pulses representing "1" or "0" and simply reports the better match. This does take more effort than just measuring the ON time, which most people do, but it is quite a bit more noise-proof.

(*) if you are interested, there are actually three reference pulses: SYNC, 0 or 1.

Do you have a feel for what kind of resolution the WWVB process can achieve? Counting variations in propagation delay, at least in the short run, and limitations on how accurately the amplitude transitions can be timed, how many milliseconds of variation would you expect it to produce over a few hours at night?

Any variations in timing would depend entirely on the MCU used, the control software and the temperature, among other things. WWVB transmissions are accurate to 1 part in 10^12.

For your purpose, I recommend to instead use the 1 PPS signal from a GPS module, which is usually guaranteed to 1 nanosecond.

My approach to that is to use the 1PPS signal to calibrate the CPU clock of a 16 MHZ Arduino to 1 Hz, using this code, originally from Nick Gammon. The calibration depends on the temperature.

// Frequency timer using input capture unit
// Author: Nick Gammon
// Date: 31 August 2013
// added averaging JR 2015
// Input: GPS 1PPS capture signal on Pin D8

volatile boolean first;
volatile boolean triggered;
volatile unsigned int overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect)
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
{
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;

  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;

  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
  {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    first = false;
    return;
  }

  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
}  // end of TIMER1_CAPT_vect

void prepareForInterrupts ()
{
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet

  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
}  // end of prepareForInterrupts


void setup ()
{
  Serial.begin(115200);
  Serial.println("Frequency Counter");

  pinMode(8, INPUT_PULLUP);
  pinMode(7, OUTPUT);
  digitalWrite(7, LOW);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);


  // set up for interrupts
  prepareForInterrupts ();
} // end of setup

void loop ()
{
  static unsigned long average = 0;
  static int n = 0;
  // wait till we have a reading
  if (!triggered)
    return;

  PINB |= (1 << 5); //blink LED

  // period is clock cycles in one second
  unsigned long elapsedTime = finishTime - startTime;

  Serial.println (elapsedTime);
  average += elapsedTime;
  n++;
  if (n == 10) {
    Serial.print("System clock count, average of ten: ");
    Serial.println(average / 10);
    n = 0;
    average = 0;
  }


  // so we can read it
  delay (500);

  prepareForInterrupts ();
}   // end of loop

I understand that the transmissions are accurate, but then it takes a variable amount of time for the signal to propagate to me 600 miles away, and there may be some variation in when the $7 module detects the beginning of the second successive marker. For my purpose, it only matters that these things in the aggregate don't vary by much.

Yes, I have GPS on my list, but have never used it. I wonder if there's a GPS PPS app for Android. :slight_smile:

What I need to do is measure the number of ms, or possibly the number of ms plus the change in the timer count, between when the RTC square wave goes low at the beginning of a second and the time the "reference" time ticks that second. I'm looking for the Aging register value that makes that difference constant. But the datasheet says increasing or decreasing the aging register by one is a 0.1ppm change. So to get to the right Aging value within any reasonable amount of time would require detecting really small changes, and probably some form of successive approximation.

There are a number of unknowns that could affect how well this would work. And it will depend on temperature. I've discovered that it also depends on what else the RTC is doing. For example, I've detected that lots of I2C traffic makes the RTC run faster. I assume that's a temperature effect that's not adequately compensated internally.

It appears the GPS PPS output is really the way to go on this, as you suggested. I'd like to outline what I have in mind, and see if you think it would work.

So I have a DS3231 set to output a 1Hz square wave, which would interrupt the Arduino on D2. And I will have the DPS module PPS output interrupting the Arduino on D3. I don't want the millis() interrupt to complicate things, and besides, I need Timer0 elsewhere, so during the measurement process there wouldn't be any millis() activity.

I need to measure as accurately as possible how many Arduino clock cycles take place from the D2 interrupt to the D3 interrupt. So I would cascade Timer1 and Timer0 into a 24-bit timer by feeding the output of Timer1 into the external clock input of Timer0 (on D4).

When the D2 interrupt takes place, the ISR would simply clear both timers. Then the D3 ISR would read the count values of both timers. The measurement would be repeated every 15 minutes, and the Aging register adjusted until the reading is the same time after time. I'll have to come up with an adjustment formula that hopefully would get me to the final setting within a few hours.

Then after each reading I would temporarily set all the timers back to whatever is needed to do Serial and Wire activity, then go back into measurement mode. Note that it doesn't matter whether the RTC is set to the correct time. It only matters whether the RTC is getting faster or slower, and by how much, relative to the PPS tick. It also doesn't matter what the Arduino's clock speed is so long as it's consistent.

I've never tried cascading the two AVR timers, and am always a bit overwhelmed by the timers sections of the datasheet for the 328P, but it does seem like this should work. And 24 bits would essentially equal the resolution of a 16MHz crystal - although, if memory serves, Timer1 won't actually run at 16Mhz, but only at 8MHz. But that's still 8000 times better resolution than the nearest millis(). And depending on the jitter of the two interrupt sources, it may actually be more than is needed.

This should also work with the WWVB receiver, but it seems GPS is just a less variable source of comparison because of WWVB's propagation variability. But I hope to try both and see if there's any material difference.

And finally, does this GPS module look ok?

https://www.amazon.com/Microcontroller-Compatible-Sensitivity-Navigation-Positioning/dp/B07P8YMVNT

Timer1 does run at 16 MHz, and the overflow can be used to increment a counter, creating a 24 or 32 bit timer. That is how Nick Gammon's Input Capture program works, posted in post #4 above (slightly modified). That is where I would start.

The linked GPS module is claimed to be a NEO-6M which would be fine, except that the market is flooded with counterfeits of that model, which often do not work as advertised. For a few more $ you can get the genuine item from reliable distributors.

I just don't want the timer interrupt to interfere with the D3 interrupt, which it might. And with the hardwired overflow setup, there are no timer interrupts at all.

Aside from Adafruit or Sparkfun, do you know of a reliable distributor? I don't see these on Digikey.

If you use Input Capture mode, no interrupt is required. The entire point of Input Capture is that you start or stop the timer in one CPU clock cycle, which I think is exactly what you need to do. There is no overhead.

The NEO-6M is obsolete but you can still order one from Mouser. Just about any successor obtained from a reliable distributor should be fine.

The Amazon GPS module arrived and seems to work fine, at least the PPS output does. I also got the larger 28db antenna for an extra $6 since the biggest complaint in the Amazon comments was that the included antenna is no good. The big antenna is four times the size of the included antenna. I don't know what difference that makes at GPS frequencies,but with the big antenna it locked on in about 30 seconds at the beginning, and since then it comes up right away when powered up. Both antennas are "active".

So I have the RTC outputting a 1Hz square wave to D2 and the PPS line connected to the capture input, as you suggested. Timer1 is running at 16MHz, with overflows counted in software. The RTC interrupt starts the clock running, and the capture interrupt stops it and collects the timer values. The elapsed time is taken every five minutes, and the difference between the last two is used to adjust the Aging register.

The math is that at 16Mhz a change of one in Aging, which is 0.1ppm at room temperature, is equal to 480 clocks over five minutes. So dividing the five minute differential by 480 gives me the change to Aging. In practice, this overshoots a bit at first, but gets to a final result in about 25 minutes. Of course there is no exact answer, so one setting will be a little fast and +1 will be a little slow, and you take your pick. If allowed to run, the system will just oscillate between the two.

There is some jitter in the system, both in the RTC square wave and possibly the Nano's clock over half a second, but I've found that if I average the elapsed time over 16 seconds that seems to deal well with any jitter.

I have more work to do on the code, but will post it at some point. And tracking shows the WWVB receiver is out for delivery this morning, so that will let me test my original idea. Just as an example, the RTC I'm testing with ends up with Aging set to -44 to match the PPS rate. In theory that's 4.4ppm better than Aging = 0. And in general, getting the nearest correct Aging setting means you get a maximum time error of about 3 seconds per year. Of course that's only if the temperature is constant. I would expect the optimium Aging setting would change with temperature changes.

Sounds great! I'm sure people will be interested in the results, so please post a project description when you are happy with it.

More information for your interesting project.
Look at the web site for WWV/WWVB and find their log of maintenance or outages for the signals. I found it because one time our weather station WWVB time went haywire one time. WWVB has many outages, sometimes for hours or longer.
Also, the GPS satellites are ALL coordinated by ground stations both for position and time. A set of moving clocks can never be the same time, remember relativity? There are 4 ground stations in the US. There are a few others around the world. There used to be 3 more across Russia, but were removed when the US would not allow Russia to place their coordination stations in the US.
All the ground stations are coordinated to a single clock otherwise accurate timing could not exist.
Good luck with the project!

You mean just post it here, or is there an official place for posting Arduino projects?

Got a bit of a nasty surprise today. The RTC I've been testing with, clearly marked DS3231SN, is a fake. It behaves exactly like a DS3231M. It only produces a 1Hz square wave, and it does temperature conversion every second. That's why it overshot. I have one other "SN" I can test, but if it's fake too, I may need to go hunting for a real SN. Adafruit maybe.

Thanks very much. Yes, otherwise I might conclude the receiver isn't working when in fact the transmitter is down.

I opened this thread to get info about WWVB, but as part of my main question about calibrating the Aging register of the DS3231. Since I've settled on using GPS for this instead of WWVB, I've returned to the original thread to follow up with GPS, and post my software:

https://forum.arduino.cc/t/ds3231-setting-aging-register-to-optimum-value/1112826/13