Pages: [1] 2   Go Down
Author Topic: Arduino and atomic clock (WWVB) receiver  (Read 8007 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 7
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
Logged

Global Moderator
UK
Offline Offline
Brattain Member
*****
Karma: 289
Posts: 25697
I don't think you connected the grounds, Dave.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

"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.

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
// 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:
/************************************************************************************************
 * 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:
 // 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:
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)
« Last Edit: May 18, 2009, 08:53:54 am by Sean » Logged

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@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.
Logged

Global Moderator
UK
Offline Offline
Brattain Member
*****
Karma: 289
Posts: 25697
I don't think you connected the grounds, Dave.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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!
Logged

"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.

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

8 != 9, 9 being 1001 BCD
Logged

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
/*****************************************************************************************
 * 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
« Last Edit: May 19, 2009, 12:12:01 pm by Sean » Logged

Longview, TX
Offline Offline
Newbie
*
Karma: 0
Posts: 3
Why not?
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
Logged

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here's an edge triggered method for decoding the pulses

Code:
/**********************************************************************
 * 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
}

Logged

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
/***************************************************************
 * 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++;
  }

Logged

Longview, TX
Offline Offline
Newbie
*
Karma: 0
Posts: 3
Why not?
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks GrendelT!  smiley

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:
/**********************************************************************
 * 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.
Logged

Longview, TX
Offline Offline
Newbie
*
Karma: 0
Posts: 3
Why not?
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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:
// 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.
Logged

Orygun
Offline Offline
Full Member
***
Karma: 0
Posts: 195
Don't let the smoke out!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.  smiley-wink

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
Logged

London, England
Offline Offline
Edison Member
*
Karma: 4
Posts: 1026
Go! Go! Arduinoooo !!!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Pages: [1] 2   Go Up
Jump to: