2009-05-26

Decoding WWVB Time Data -> Edge Triggered

Well, they say that by making mistakes, you learn what not to do.

The previous attempt at using pulseIn() to help decode WWVB time was successful if all you wanted was a string of bits, but we really do need loop() to function and not be blocked by the interrupt handler going out to a working lunch where it merrily does mostly what we want, but doesn't let us do anything else. Back to the Arduino IDE.

Let's work on getting a string of bits by a slightly different method. First, the detection needs to be edge triggered. This time we want attachInterrupt() to notify us of both the rising and falling edges of our input pulse.

attachInterrupt(0, readLevel, CHANGE); // fire interrupt on edge detected

This calls our interrupt handler which has two functions, read the level on the pin and flash an LED to show receiver activity.

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


At which point the interrupt handler readLevel() drops out of the picture until another edge is detected. Everything now gets handed off to loop() which checks to see if the previous wwvbInState is different from our current wwvbInState and if there has been a change, runs the pulseValue() function.

In pulseValue(), we come to the table with two facts. A change and a high wwvbInState is a leading edge to a pulse, a change and a low wwvbInState is its following edge. The leading edge saves the current time, the following edge subtracts saved time from the current time to find out how long the pulse lasted in milliseconds. From there a few conditional branches tell us if we have noise, a marker, a one or a zero bit. And now for the code:


Clock WWV Edge Triggered

/**********************************************************************
* Clock WWV 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
}



And here's a sample of the data stream coming out of the Arduino to be displayed on the terminal screen:

Bit Value: | 01101000 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 : 
Bit Value: | 01101001 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000000 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000001 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000010 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000011 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000100 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000101 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000110 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 10000111 : 000000100 : 01 : 0100100 : 011100101 : 001100000 : 100100011 :
Bit Value: | 101 : 01000 : 000000100 : 00010010 : 1 : 01 :
Bit Value: | 100101 : 001100000 : 1001000 :
Bit Value: | 1 :
Bit Value: | 10001001 : 000000100 : 000100100 : 011100101 : 001100000 : 100100011 :


As you can see, there was once again a bad patch with four minutes of data unreadable. If this is to drive a clock, we don't want the displayed time jumping erratically. Next time we'll see if we can get some simple frame rejection upon the more egregious receive errors.

No comments:

Post a Comment