SparkFun Forums 

Where electronics enthusiasts find answers.

Have questions about a SparkFun product or board? This is the place to be.
By blanham
#72919
I'm trying to build an atomic clock using a CMAX CMMR-6 module from digikey and an arduino. This module outputs the raw WWVB data. Does anyone have experience decoding this data with Arduino?
By mvcl
#72920
Did you see this stuff?
The max site has a pretty sparse spec sheet.
There is a reference to a Circuit Cellar article by Nickels.
The file is called circuit when you download:
http://www.c-max-time.com/downloads/getFile.php?id=566
-rename the file "circuit" to "circuit.pdf", then open it.
The article contains a link to a .zip with what it claims are all its sources:
ftp://ftp.circuitcellar.com/pub/Circuit ... ls-220.zip
I briefly checked: it looks like the decode routines are there.
By kwan3217
#73295
blanham wrote:I'm trying to build an atomic clock using a CMAX CMMR-6 module from digikey and an arduino. This module outputs the raw WWVB data. Does anyone have experience decoding this data with Arduino?
Yes. I have a sketch which listens to WWVB and decodes it, then uses the Arduino internal clock to synthesize its own time code which is then put onto one of the digital out pins. The internal clock keeps on running even when WWVB is not received. I use this exact receiver, in fact.

What is a good way to post a sketch?
By capt.tagon
#73821
This piece of code is intended as the front end to an actual clock. It detects the pulse rising and falling edges, measures duration of the pulse, finds frame start, decodes mark, zero and one, provides for frame reject. The output at this point is the WWVB data stream, the BCD needs to be extracted for display and to update the clock.

The CMMR-6P-60 needs a little filtration on the power (10 uF and 0.1 uF capacitors), and I added an LED indicator to TCON to monitor activity (good vs bad signal). Hook the TCO data to your Arduino Data Input Pin 2.
Code: Select all
/**********************************************************************
 * 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
boolean 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 < 350) {           // 800ms carrier drop mark
      // two sequential marks -> start of frame. If we read 6 marks and 60 bits
      // (0-59), we should have received a valid frame
      if ((prevMark == 1) && (markCount == 6) && (bitCount == 59)) { 
        bitVal = 3;
        frameAccept();                      // data decoded, accept frame
        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   = 0;                     // 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 != 59))) {
        errorCount ++;                      // increment frame error count
        frameReject();                      // bad decode, 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   = 0;                     // 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 < 650) {           // 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) {                      // reject noise
      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. DEBUG ROUTINES
  if (bitVal == 3) {
    Serial.print("<- Frame Decode\n : ");
  } 
  else if (bitVal == 2) {
    Serial.print(" : ");
  } 
  else {
    Serial.print(bitVal, DEC);
  }
}

/******************************************************************************
 * frameAccept()
 *
 * Accept function for completed frame decode
 ******************************************************************************/

void frameAccept() {
  // future BCD decode here
}

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

void frameReject() {
  // Display Frame Reject message, mark count bit count and sequential number
  // of frame errors.  DEBUG ROUTINES
  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
}
[/code]
By epedersen
#74995
Do you have the schematic of your project, I am trying to get one of the c-max devices working with your code and I am having troubles.
Any assistance would be appreciated.
Thanks.
By jesrandall
#74998
I have in fact worked with this exact module. Signal sensitivity is good, however, you will have to add additional components to the digikey module. Several capacitors and an inductor are lacking in the completed version. This allows the user to fine tune the device since capacitor tolerances are often low and can be costly depending on how sensitive you need it to be.

Now, since you are building a clock. Here's something to look out for. When I first designed it I used a multiplexing display to keep prices low. But, the multiplexing frequency SEVERELY interfered with the atomic radio reception. I then proceeded to the next logical step, spread spectrum clocking. This WILL dramatically improve reception, but you still must use all possible precautions to keep noise down. Mount the entire module and ferrite antenna away from the multiplexing display. I was building a large size clock with 7 watt power so the noise was very prominent. Spread spectrum, in theory (mathematically) can reduce effective power transmission on a given frequency to ZERO, but in practical terms this cannot be achieved.

The programming for the reception can get a little complicated too, especially if you want the clock to be able to receive signal during the day. You will need to write pattern recognition to understand (more humanly) what is valid data and what isn't it.

In the end the CMMR module is a good choice and will work. As with anything, though, to make it flawless, you'll have to put in a bit of your own ingenuity! Good luck with your projects.
By capt.tagon
#74999
epedersen wrote:Do you have the schematic of your project, I am trying to get one of the c-max devices working with your code and I am having troubles.
Any assistance would be appreciated.
Thanks.
Here's a schematic for the interface between the CMax CMMR-6P-60 board and the Arduino that I'm currently using.

http://duinolab.blogspot.com/2009/05/c- ... eiver.html
By capt.tagon
#75002
jesrandall wrote:I have in fact worked with this exact module. Signal sensitivity is good, however, you will have to add additional components to the digikey module. Several capacitors and an inductor are lacking in the completed version. This allows the user to fine tune the device since capacitor tolerances are often low and can be costly depending on how sensitive you need it to be.
Please provide a schematic... Is this for filtering and smoothing the power?
CMMR-6P Data sheet: http://www.c-max-time.com/downloads/getFile.php?id=542
jesrandall wrote:Now, since you are building a clock. Here's something to look out for. When I first designed it I used a multiplexing display to keep prices low. But, the multiplexing frequency SEVERELY interfered with the atomic radio reception. I then proceeded to the next logical step, spread spectrum clocking. This WILL dramatically improve reception, but you still must use all possible precautions to keep noise down. Mount the entire module and ferrite antenna away from the multiplexing display. I was building a large size clock with 7 watt power so the noise was very prominent. Spread spectrum, in theory (mathematically) can reduce effective power transmission on a given frequency to ZERO, but in practical terms this cannot be achieved.
Multiplexed LED displays... I remember using an old TI-30/33/55 (not sure on model no?) calculator as a receiver live check. If you didn't hear the buzz... Any websites to educate myself on spread spectrum clocking/multiplexed displays?

Currently I'm using an LCD 16x2 display, which is probably contributing radio hash, but had planned on running a shielded cable out to the WWVB receiver so it is mounted well away from the microprocessor and display.
By capt.tagon
#75013
epedersen wrote:Do you have the schematic of your project, I am trying to get one of the c-max devices working with your code and I am having troubles.
Any assistance would be appreciated.
Thanks.
Indicator LEDs
http://duinolab.blogspot.com/2009/06/cl ... tches.html

The LED indicators match up for the sketch posted to this forum, ignore the switches.

From the previous post, the CMMR-6P interface board's OUT terminal goes to Arduino Pin 2, and (G)round and +5Vdc go to their respective Arduino pins.