Go Down

Topic: Arduino and atomic clock (WWVB) receiver (Read 9895 times) previous topic - next topic

blanham

I'm trying to build an atomic clock using the CMAX CMMR-6 receiver module, which simply outputs raw WWVB timecodes. Has anyone experimented with using this module with arduino? I've found some information for using it with other platforms, but it has not been helpful. I basically need to read the pulses into an array, which I can then decode. I can handle the second part, but cannot figure out the first. Any suggestions?

AWOL

Whne you say "raw", do you mean the bitstream has the same timing as the transmitted signal?

Ie.200mS for a zero, 500mS for a one and 800mS for a marker bit?

Looks like a job for pulseIn.
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

capt.tagon

#2
May 18, 2009, 09:32 am Last Edit: May 18, 2009, 03:53 pm by Sean Reason: 1
Get the long loopstick antenna for that and make sure you get it up off the workbench and into a Colorado facing window with the antenna broadside. Noise is your enemy.

I used the non-inverting output.

We're working with Cold War Era techonology, none of that fancy schmancy parity stuff, and actual dates so most of the preexisting work with DCF77 won't work. On top of that each BCD decimal is separated from the next one by a zero non-data bit, so you cannot just read a number, you have to read each digit. And then the WWVB data has MSB0/LSB0 reversed, so you have to read it backwards to get the BCD stuff to actually give you proper digits. 1001 gives you 9 just fine, but 0101 becomes 1010 if you get the drift. Then if you want dates, you will have to calculate them somehow. You are given a year, day of year and leapyear bit.

I took the DCF77 work already done by Mathias Dalheimer http://gonium.net/md/2006/11/05/arduino-dcf77-radio-clock-receiver/ and reworked his data structure in reverse (ie minutes come last instead of first) like so:

Code: [Select]
// WWVB time format struct - acts as an overlay on wwvbRxBuffer to extract time/date data.
// All this points to a 64 bit buffer wwvbRxBuffer that the bits get inserted into as the
// incoming data stream is decoded.
struct wwvbBuffer {
 unsigned long long U12       :4;  // no value, empty four bits only 60 of 64 bits used
 unsigned long long Frame     :2;  // framing
 unsigned long long Dst       :2;  // dst flags
 unsigned long long Leapsec   :1;  // leapsecond
 unsigned long long Leapyear  :1;  // leapyear
 unsigned long long U11       :1;  // no value
 unsigned long long YearOne   :4;  // year (5 -> 2005)
 unsigned long long U10       :1;  // no value
 unsigned long long YearTen   :4;  // year (5 -> 2005)
 unsigned long long U09       :1;  // no value
 unsigned long long OffVal    :4;  // offset value
 unsigned long long U08       :1;  // no value
 unsigned long long OffSign   :3;  // offset sign
 unsigned long long U07       :2;  // no value
 unsigned long long DayOne    :4;  // day ones
 unsigned long long U06       :1;  // no value
 unsigned long long DayTen    :4;  // day tens
 unsigned long long U05       :1;  // no value
 unsigned long long DayHun    :2;  // day hundreds
 unsigned long long U04       :3;  // no value
 unsigned long long HourOne   :4;  // hours ones
 unsigned long long U03       :1;  // no value
 unsigned long long HourTen   :2;  // hours tens
 unsigned long long U02       :3;  // no value
 unsigned long long MinOne    :4;  // minutes ones
 unsigned long long U01       :1;  // no value
 unsigned long long MinTen    :3;  // minutes tens
};


I then fill the buffer from bit 64 back to bit 5. Believe me, it's easiest this way. As follows:

Code: [Select]
/************************************************************************************************
* appendSignal()
*
* Append a decoded signal bit to the wwvbRxBuffer and decrement bufferPosition counter.
* Argument can be 1 or 0. The bufferPosition counter shifts the writing position within
* the buffer over 60 positions. Reverse bit order by starting at 63 and working back towards
* 0 to account for MSB0/LSB0 mismach between processor and transmitted data.
************************************************************************************************/

void appendSignal(byte signal) {
 // bitwise OR wwvbRxBuffer contents with signal bit shifted to bufferPosition
 wwvbRxBuffer = wwvbRxBuffer | ((unsigned long long) signal << bufferPosition);
 bufferPosition--;                    // decrement bufferPosition for next bit

 #ifdef DEBUG_DATASTRUC               // debug output
 Serial.print(signal, DEC);
 if (bitCount % 10 == 0) {
   Serial.print(" : ");
 }
 #endif
}


Once you have your data stuffed in the buffer, you point your data structure at it as follows:

Code: [Select]
 // point wwvbBuffer structure at wwvbRxBuffer to allow simpler extraction of data bits to decode
 // WWVB time and date information.
 struct wwvbBuffer *rx_buffer;
 rx_buffer = (struct wwvbBuffer *)(unsigned long long)&wwvbRxBuffer;


and start reading it out with pointers like the following:

Code: [Select]
rx_buffer->MinTen
rx_buffer->MinOne
rx_buffer->HourTen
rx_buffer->HourOne


These are the bcd numbers, convert to decimal and you start having time information to play with.

( edited to change 8 to 9, 1001 BCD is 9)

capt.tagon

@AWOL

If you want just a simple data readout of the time when you're getting a good signal. the pulseIn method works great for decoding the bits. It's a polled method though, so the processor spends a lot of time waiting on timing the pulse duration.

I went with the interrupt edge detection method because given signal readability, the only use for the WWVB signal is to periodically update a running clock, once a second when it gets a good signal (around sunset and sunrise here); updating whenever when it is getting mostly garbage.

AWOL

Quote
1001 gives you 8 just fine

That must be confusing!

Agreed though, edges are good - I keep forgetting what a slooooow bit rate it is!
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

capt.tagon

It was late at night, hmm glad I wasn't programming.  8-)

8 != 9, 9 being 1001 BCD

capt.tagon

#6
May 19, 2009, 07:07 pm Last Edit: May 19, 2009, 07:12 pm by Sean Reason: 1
For those bad receive days where you want to work on your data decode, but the signal's not cooperating, or your neighbor just fired up some sort of technological marvel hash generator, here's a WWVB Signal Simulator. Run on a second Arduino, run a two wire data cable between the two (common ground, signal) and fiddle to your heart's content.

Code: [Select]
/*****************************************************************************************
* WWVB Receiver Simulator  by Capt.Tagon
*
* For those days when you want to work on your code, but the singal is bad, somebody
* fired something off that spurs on 60kHz or you just fried your WWVB receiver
*****************************************************************************************/

#define clockOutPin 13                // data out, send this to your second
                                     // Arduino's WWVB Rx input pin

int bitCount = 60;                    // 60 bits, one per second


// Clock output data, fiddle the zeros and ones to your heart's content,
// but the twos must remain! All bits noted as blank are ALWAYS ZERO.

byte clockData[60] = {                // transmitted data 2 Mark, 1 or 0
 2, //  0 frame reference Pr -> *** MUST BE 2 ***
 0, //  1 minute 40                        
 0, //  2 minute 20
 1, //  3 minute 10
 0, //  4 blank
 0, //  5 minute 8
 1, //  6 minute 4
 0, //  7 minute 2
 0, //  8 minute 1
 2, //  9 mark P1 -> *** MUST BE 2 ***
 0, // 10 blank
 0, // 11 blank
 0, // 12 hours 20
 0, // 13 hours 10
 0, // 14 blank
 0, // 15 hours 8
 1, // 16 hours 4
 1, // 17 hours 2
 0, // 18 hours 1
 2, // 19 mark P2 -> *** MUST BE 2 ***
 0, // 20 blank
 0, // 21 blank
 0, // 22 day of year 200
 1, // 23 day of year 100
 0, // 24 blank
 0, // 25 day of year 80
 0, // 26 day of year 40
 1, // 27 day of year 20
 1, // 28 day of year 10
 2, // 29 mark P3 -> *** MUST BE 2 ***
 1, // 30 day of year 8
 0, // 31 day of year 4
 0, // 32 day of year 2
 1, // 33 day of year 1
 0, // 34 blank
 0, // 35 blank
 0, // 36 UTI sign +
 0, // 37 UTI sign -
 0, // 38 UTI sign +
 2, // 39 mark P4 -> *** MUST BE 2 ***
 0, // 40 UTI correction 0.8
 0, // 41 UTI correction 0.4
 0, // 42 UTI correction 0.2
 0, // 43 UTI correction 0.1
 0, // 44 blank
 0, // 45 year 80
 0, // 46 year 40
 0, // 47 year 20
 0, // 48 year 10
 2, // 49 mark P5 -> *** MUST BE 2 ***
 1, // 50 year 8
 0, // 51 year 4
 0, // 52 year 2
 1, // 53 year 1
 0, // 54 blank
 0, // 55 leapyear
 0, // 56 leapsecond
 1, // 57 dst bit 1  set 11 dst, 00 standard
 1, // 58 dst bit 2
 2, // 59 frame reference P0 -> *** MUST BE 2 ***
};


void setup() {
 Serial.begin(9600);
 pinMode(clockOutPin, OUTPUT);      // set pin digital output
}


void  loop(){
for(int i = 0; i < bitCount; i++) {
  if (clockData[i] == 2) { genMark(); } else if (clockData[i] == 1) { genOne(); } else { genZero(); }
 }
}

void genMark() {                      //generate 800ms Mark
 digitalWrite(clockOutPin, HIGH);
 delay(200);
 digitalWrite(clockOutPin, LOW);
 delay(800);
}

void genZero() {                      // generate 200ms Zero
 digitalWrite(clockOutPin, HIGH);
 delay(800);
 digitalWrite(clockOutPin, LOW);
 delay(200);
}

void genOne() {                       // generate 500ms One
 digitalWrite(clockOutPin, HIGH);
 delay(500);
 digitalWrite(clockOutPin, LOW);
 delay(500);
}


This is set up to simulate the C-Max CMMR-6P-60 TCO Positive Output Signal

GrendelT

I just got my Arduino last week (I love it!) and my CMMR-6P module in a few days later.
I've made some really hacked code (I need to optimize it) to get my module to receive WWVB data using pulseIn. It then fills in an integer array with either a 0, 1, 2 (marker) or 3 (unknown/bad data) based on the length of the LOW pulse.

By doing this and parsing the bits/nibbles, it figures out GMT/UTC and then I have it handle my timezone offset and DST, all is well so far.
The YEAR is straight forward and easy enough, but the DAY of the year is the tough spot. WWVB tells you the number of the day of the year; not month and day as the DCF77 Atomic Clock so nicely does.

Have any of you come across a "clean" way of calculating the day and month based on the number since January 1 that WWVB provides?

capt.tagon

Here's an edge triggered method for decoding the pulses

Code: [Select]
/**********************************************************************
* Clock WWVB Data Decode Edge Triggered  by capt.tagon
*
* WWVB receiver input on digital pin 2
**********************************************************************/

#define wwvbIn      2  // WWVB receiver data input digital pin
#define ledRxPin    4  // WWVB receiver state indicator pin
#define ledFramePin 6  // Data received frame indicator pin
#define ledBitPin   5  // LED data decoded indicator pin
#define ledMarkPin  7  // Data received mark inicator pin

// variable changed by interrupt service routine - volatile
volatile byte wwvbInState;            // store receiver signal level

byte prevWwvbInState;                 // store previous signal level
unsigned int prevEdgeMillis;          // store time signal was read
byte bitVal;                          // bit decoded 0, 1 or Mark
byte badBit;                          // bad bit, noise detected
byte prevMark;                        // store previous mark bit

void setup() {
 pinMode(wwvbIn, INPUT);
 pinMode(ledRxPin, OUTPUT);
 pinMode(ledFramePin, OUTPUT);
 pinMode(ledBitPin, OUTPUT);
 pinMode(ledMarkPin, OUTPUT);
 attachInterrupt(0, readLevel, CHANGE); // fire interrupt on edge detected
 Serial.begin(9600);
 lcdInit();
}

void loop() {
 if (wwvbInState != prevWwvbInState) {
   pulseValue();
   prevWwvbInState = wwvbInState;
 }
}

/******************************************************************
* pulseValue()
*
* determine pulse width 200ms = 0, 500ms = 1, 800ms = mark
******************************************************************/

void pulseValue() {
 unsigned int edgeMillis = millis();         // save current time
 badBit = 0;                                 // set noise counter to zero
 if (wwvbInState == 1) {                     // rising edge
   prevEdgeMillis = edgeMillis;              // set previous time to current
 }
 else {                                    // falling edge
   int pulseLength = edgeMillis - prevEdgeMillis; // calculate pulse length millis
   if (pulseLength < 100) {                  // less than 100ms, noise pulses
     badBit = 1;
   }
   else if (pulseLength < 400) {           // 800ms carrier drop mark
     bitVal = 2;
   }
   else if (pulseLength < 700) {           // 500ms carrier drop one
     bitVal = 1;
   }
   else {                                  // 200ms carrier drop zero
     bitVal = 0;
   }
   if (badBit == 0) {
     printBitVal();
   }
 }
}

/******************************************************************************
* readLevel() {
*
* Pin 2 INT0 Interrupt Handler Reads pin state - flashes signal indicator LED
******************************************************************************/

void readLevel() {
 wwvbInState = digitalRead(wwvbIn);   // read signal level
 digitalWrite(ledRxPin, wwvbInState); // flash WWVB receiver indicator pin
}

/******************************************************************************
* printBitVal()
*
* Display bit values to terminal screen, output delimited data stream with
* colons at mark and new line at frame start.
******************************************************************************/

void printBitVal() {
 if ((bitVal == 2) && (prevMark == 0)) {
   Serial.print(" : ");
   digitalWrite(ledMarkPin, HIGH);
   prevMark = 1;
 }
 else if ((bitVal == 2) && (prevMark == 1)) {
   Serial.print("\nBit Value: ");
   Serial.print("| ");
   digitalWrite(ledFramePin, HIGH);
   prevMark = 0;
 }
 else {
   Serial.print(bitVal, DEC);
   digitalWrite(ledMarkPin, LOW);
   digitalWrite(ledFramePin, LOW);
   if (bitVal == 0) {
     digitalWrite(ledBitPin, LOW);
   }
   if (bitVal == 1) {
     digitalWrite(ledBitPin, HIGH);
   }
   prevMark = 0;
 }
}

/*****************************************************************************
* Time display functions
*****************************************************************************/

void printTime() {
 Serial.print("?x00?y0?f"); // movie cursor to line 1 char 1, clear screen
}

// LCD routines to initialize LCD and clear screen
void lcdInit() {           // using P H Anderson Serial LCD driver board
 Serial.print("?G216");   // configure driver for 2 x 16 LCD
 delay(300);
 Serial.print("?BDD");    // set backlight brightness
 delay(300);
 Serial.print("?f");      // clear screen
 Serial.print("?c0");     // set cursor off
}


capt.tagon

Day of year to month and day. Needed input, decode day of year and leapyear flag from WWVB.

Code: [Select]

/***************************************************************
* Day of year to Month and Day  by capt.tagon
***************************************************************/

volatile unsigned int  doy;              // day of year
volatile byte lyr;                       // leap year flag


// End of Month - to calculate Month and Day from Day of Year
int eomYear[13][2] = {
 {0,0},      // Begin
 {31,31},    // Jan
 {59,60},    // Feb
 {90,91},    // Mar
 {120,121},  // Apr
 {151,152},  // May
 {181,182},  // Jun
 {212,213},  // Jul
 {243,244},  // Aug
 {273,274},  // Sep
 {304,305},  // Oct
 {334,335},  // Nov
 {365,366}   // Dec
};


 /* DCF77 sends month and day information, we aren't so lucky.
  * WWVB only sends Day of Year, Month and Day will need to be calculated, and 02/29 added for leapyear.
  * We're given Day of Year, compare against month ending day and calculate month and day.
  * Use leapyear flag to add one day to February.
  */
 int eom = 0;  // eom counter used to determine month and day
 while (eomYear[eom][lyr] < doy) {        // calculate month and day for UTC
   day = doy - eomYear[eom][lyr];
   mon = (eom + 1) % 12;
   eom++;
 }



GrendelT

I like it, capt. It's similar to what I came up with, but I like your 2d array better. I just had separate loops that ran based on the boolean leapYear.

capt.tagon

Thanks GrendelT!  :)

One of the major problems is bad signal rx, here's updated edge detect and pulse duration code that keeps track of what's being received and generates a frame reject for bad frames.

Code: [Select]
/**********************************************************************
* Clock WWVB Data Decode Frame Reject  by capt.tagon
*
* WWVB receiver input on digital pin 2
**********************************************************************/

#define wwvbIn      2  // WWVB receiver data input digital pin
#define ledRxPin    4  // WWVB receiver state indicator pin
#define ledFramePin 6  // Data received frame indicator pin
#define ledBitPin   5  // LED data decoded indicator pin
#define ledMarkPin  7  // Data received mark inicator pin

// variable changed by interrupt service routine - volatile
volatile byte wwvbInState;            // store receiver signal level

byte prevWwvbInState;                 // store previous signal level
unsigned int prevEdgeMillis;          // store time signal was read

byte bitVal;                          // bit decoded 0, 1, Mark or Frame
byte badBit             = 0;          // bad bit, noise detected
byte markCount          = 0;          // mark count, 6 pulses per minute
boolean prevMark        = 0;          // store previous mark
byte bitCount           = 0;          // bits, 60 pulses per minute
boolean frameError      = 0;          // set for frame reject
unsigned int errorCount = 0;          // keep count of frame errors

void setup() {
 pinMode(wwvbIn, INPUT);
 pinMode(ledRxPin, OUTPUT);
 pinMode(ledFramePin, OUTPUT);
 pinMode(ledBitPin, OUTPUT);
 pinMode(ledMarkPin, OUTPUT);
 attachInterrupt(0, readLevel, CHANGE); // fire interrupt on edge detected
 Serial.begin(9600);
 lcdInit();
}

void loop() {
 if (wwvbInState != prevWwvbInState) {
   pulseValue();
   prevWwvbInState = wwvbInState;
 }
}

/******************************************************************
* pulseValue()
*
* determine pulse width 200ms = 0, 500ms = 1, 800ms = mark
******************************************************************/

void pulseValue() {
 unsigned int edgeMillis = millis();       // save current time
 if (wwvbInState == 1) {                   // rising edge
   prevEdgeMillis = edgeMillis;            // set previous time to current
 }
 else {                                    // falling edge
   int pulseLength = edgeMillis - prevEdgeMillis; // calculate pulse length millis
   badBit = 0;                             // clear bad bit detected
   digitalWrite(ledMarkPin, LOW);
   digitalWrite(ledFramePin, LOW);
   if (pulseLength < 100) {                // less than 100ms, noise pulses
     badBit = 1;                           // bad bit, signal pulse noise
   }
   else if (pulseLength < 400) {           // 800ms carrier drop mark
     // two sequential marks -> start of frame. If we read 6 marks and 60 bits,
     // we should have received a valid frame
     if ((prevMark == 1) && (markCount == 6) && (bitCount == 60)) {
       bitVal = 3;
       digitalWrite(ledFramePin, HIGH);    // frame received, ready for new frame
       markCount  = 0;                     // start counting marks, 6 per minute
       prevMark   = 0;                     // set bit counter to one
       bitCount   = 1;                     // should be a valid frame
       frameError = 0;                     // set frame error indicator to zero
       errorCount = 0;                     // set frame error count to zero
     }
     else if ((prevMark == 1) && ((markCount != 6) || (bitCount != 60))) { // bad decode-frame reject
       errorCount ++;                      // increment frame error count
       frameReject();                      // reject frame data
       digitalWrite(ledFramePin, HIGH);    // apparent frame, wrong mark and bit count
       markCount  = 0;                     // bad start of frame set mark count to zero
       prevMark   = 0;                     // clear previous to restart frame
       bitCount   = 1;                     // set bit count to one
       frameError = 1;                     // and indicate frame error
     }
     else {                              // 10 second marker
       bitVal = 2;
       markCount ++;                       // increment mark counter, 6 per minute
       digitalWrite(ledMarkPin, HIGH);     // mark received
       prevMark = 1;                       // set mark state to one, following mark indicates frame
       bitCount ++;                        // increment bit counter
     }
   }
   else if (pulseLength < 700) {           // 500ms carrier drop one
     bitVal = 1;
     digitalWrite(ledBitPin, HIGH);        // bit indicator LED on, one received
     prevMark = 0;                         // set mark counter to zero
     bitCount ++;                          // increment bit counter
   }
   else {                                  // 200ms carrier drop zero
     bitVal = 0;
     digitalWrite(ledBitPin, LOW);         // bit indicator LED off, zero received
     prevMark = 0;                         // set mark counter to zero
     bitCount ++;                          // increment bit counter
   }
   if (badBit == 0) {
     timeDateDecode();
   }
 }
}

/******************************************************************************
* readLevel() {
*
* Pin 2 INT0 Interrupt Handler Reads pin state - flashes signal indicator LED
******************************************************************************/

void readLevel() {
 wwvbInState = digitalRead(wwvbIn);   // read signal level
 digitalWrite(ledRxPin, wwvbInState); // flash WWVB receiver indicator pin
}

/******************************************************************************
* timeDateDecode()
*
* Decode function to extract BCD from data stream
******************************************************************************/

void timeDateDecode() {
 // Display bit values to terminal screen, output delimited data stream with
 // colons at mark and new line at frame start.
 if (bitVal == 3) {
   Serial.print("<- Frame Decode\n : ");
 }
 else if (bitVal == 2) {
   Serial.print(" : ");
 }
 else {
   Serial.print(bitVal, DEC);
 }
}

/******************************************************************************
* frameReject()
*
* Reject function for bad frame decode
******************************************************************************/

void frameReject() {
 // Display Frame Reject message, mark count bit count and sequential number
 // of frame errors.
 Serial.print("\n ->Scratch<- Bad Data - Marks: ");
 Serial.print(markCount, DEC);
 Serial.print(" Bits: ");
 if (bitCount < 10) {
   Serial.print("0");
 }
 Serial.print(bitCount, DEC);
 Serial.print(" Frame Errors: ");
 if (errorCount < 10) {
   Serial.print("0");
 }
 Serial.println(errorCount, DEC);  
}


/*****************************************************************************
* Time display functions
*****************************************************************************/

// LCD routines to initialize LCD and clear screen
void lcdInit() {           // using P H Anderson Serial LCD driver board
 Serial.print("?G216");   // configure driver for 2 x 16 LCD
 delay(300);
 Serial.print("?BDD");    // set backlight brightness
 delay(300);
 Serial.print("?f");      // clear screen
 Serial.print("?c0");     // set cursor off
}



It's kind of in debug mode, so it only shows a data stream dump, frame acceptance and a frame reject with a little bit of statistics about why the reject occurred. Data to serial terminal in Aruino IDE.

GrendelT

It's not at all clean, chiefly because I was just writing "cowboy code" (coding as I went)  :D

Like I said, I've meant to clean it up - I just haven't, but if it could help someone else in their endeavors, so be it.

You'll note my copious use of global variables and loose criteria for the timings of the pulses, I did that to account for noise and discrepancies in timing. Works fine so far! (no excuse for global vars!)

Code: [Select]

// WWVB READER
// NOTES: 60bits = frame
// .2s = 0
// .5s = 1
// .8s = marker

#include <stdio.h>

unsigned short data = 4;
unsigned short count = 0;
unsigned long duration = 0;
int hours, minutes, seconds = 0;
char time[6] = "00:00";
int last, curr = 0;
int frame[60];
int dayOfYear, month, day, year = 0;
boolean leapYear = false;
//char* week[] = {"Sun","Mon","Tues","Wed","Thurs","Fri","Sat", "Sun"};

void setup()
{
 Serial.begin(57600);
 pinMode(4, INPUT);
}

void loop()
{
  while ( count <= 60 ){
    duration = pulseIn(data, LOW, 1000000);
    //Serial.print(duration );
    if (duration >= 700000){  //  marker (2)
      if (last == 2){
        count = 61;
        continue;
      }
      last = 2;
      Serial.print("*");
      frame[count] = last;
      count++;
     
    }else if (duration >= 400000){  //  1
      last = 1;
      Serial.print("1");
      frame[count] = last;
      count++;
     
    }else if (duration >= 100000){  //  0
      last = 0;
      Serial.print("0");
      frame[count] = last;
      count++;
     
    }else{
      last = 3;
      Serial.print("_");    //  unknown (3)
      frame[count] = last;
      count++;
    }
  }
  Serial.print("|\t");
  parseTime();
  parseDate();
  Serial.println();
  count = 1;
  frame[0] = 2;
}

void parseTime(){
 for (int foo=0; foo<60; foo++){
   if (frame[foo] == 3){
     Serial.print("BAD FRAME"); //throw out the whole frame, it's not reliable...
     return;
   }
 }
   //  FOR GMT:
   minutes = (frame[1] * 40) + (frame[2] * 20) + (frame[3] * 10) + (frame[5] * 8) + (frame[6] * 4) + (frame[7] * 2) + (frame[8] * 1);
   hours = (frame[12] * 20) + (frame[13] * 10) + (frame[15] * 8) + (frame[16] * 4) + (frame[17] * 2) + (frame[18] * 1);
   
   //  TIME ZONE WORK:
   hours += (frame[57] * 1); //DST check
   hours -= 6; //CST TIME ZONE
   if (hours < 0){
     hours += 24;
   }

   minutes+=1; //hack so clock shows current time, rather than actual time 1 minute ago
   if (minutes == 60){
      minutes = 0;
      hours+=1;
   }

   sprintf(time, "%02d:%02d", hours, minutes);
   Serial.print(time);
   return;
}

void parseDate(){
 for (int foo=0; foo<60; foo++){
   if (frame[foo] == 3){
     return; //throw out the whole frame, it's not reliable...
   }
 }

//parse DAY (since Jan 1)
 dayOfYear = (frame[22] * 200) + (frame[23] * 100) + (frame[25] * 80) + (frame[26] * 40) + (frame[27] * 20) + (frame[28] * 10)
           + (frame[30] * 8) + (frame[31] * 4) + (frame[32] * 2) + (frame[33] * 1);

//parse Leap Year boolean
 leapYear = frame[55];

//parse YEAR (20--)
 year = (frame[45] * 80) + (frame[46] * 40) + (frame[47] * 20) + (frame[48] * 10)
       + (frame[50] * 8) + (frame[51] * 4) + (frame[52] * 2) + (frame[53] * 1);


 if (dayOfYear <= 31){ //  Jan
   month = 1;
   day = dayOfYear;
 }else if (dayOfYear <= 59){ //  Feb (no leap)
   month = 2;
   day = dayOfYear - 31;
 }else if (dayOfYear <= 90){ //  Mar
   month = 3;
   day = dayOfYear - 59;
 }else if (dayOfYear <= 120){ //  Apr
   month = 4;
   day = dayOfYear - 90;
 }else if (dayOfYear <= 151){ //  May
   month = 5;
   day = dayOfYear - 120;
 }else if (dayOfYear <= 181){ //  June
   month = 6;
   day = dayOfYear - 151;
 }else if (dayOfYear <= 212){ //  July
   month = 7;
   day = dayOfYear - 181;
 }else if (dayOfYear <= 243){ //  Aug
   month = 8;
   day = dayOfYear - 212;
 }else if (dayOfYear <= 273){ //  Sept
   month = 9;
   day = dayOfYear - 243;
 }else if (dayOfYear <= 304){ //  Oct
   month = 10;
   day = dayOfYear - 273;
 }else if (dayOfYear <= 334){ //  Nov
   month = 11;
   day = dayOfYear - 304;
 }else if (dayOfYear <= 365){ // Dec
   month = 12;
   day = dayOfYear - 334;
 }else{
   // something got messed up...
   // ERROR?
 }
 
 Serial.print("\t");
//  int weekday = parseDayOfWeek(); // good intentions...
//  Serial.print( week[weekday] );
 Serial.print(" ");
 Serial.print(month);
 Serial.print("/");
 Serial.print(day);
 Serial.print("/");
 Serial.print(year + 2000);
}


While the calculation of time and date from the frame aren't dynamic (or pretty) at all, I would argue that they require less resources to calculate and might be faster, albeit uglier.

capt.tagon

Well, I probably went a bit overboard, but here's my final take on this project. I now need to build it a nice little house to live in so I can get back to h-bridges, PID control and all the other stuff I was tinkering with before the fateful CMMR-6P-60 package arrived from Digikey.  ;)

http://duinolab.blogspot.com/2009/05/c-max-cmmr-6p-60-wwvb-60khz-receiver.html

http://duinolab.blogspot.com/2009/06/arduino-cmmr-6p-60-almost-accurate.html

Mike Mc

Is this module of any use to UK users or just the US?

Go Up