2009-05-28

Decoding WWVB Time Data -> Frame Reject

Last visit here, our sketch was merrily finding rising and falling edges to the pulse and measuring the duration between the rise and the fall. That then resolved itself into a data stream that sometimes has odd results when noise spikes and signal dropouts occur.

We have few tools to work with here for detecting errors. There are three main features to the signal, (1) a frame reference pulse 1 ppm, 10 second indicators 6 ppm and a complete bit count of 60 bits (tri state, with values of mark, 1 and 0, so not really bit in the binary sense).

Noise works with us in a way, because it usually mangles marks, turns ones into marks and zeros into ones, so if we set up (1) a bit counter, (2) a mark counter, and (3) a previous mark flag, we can sort things out by keeping track of our frame reference, and the number of marks and bits received.

10 second markers are lone 200ms pulses from TCO (Time Clock Out). Bit 59 is a mark, followed by bit 0 which is Pr. This two mark sequence signals our start of a new data frame, starting with bit 0. Here's where our previous mark flag comes into action. Every time a mark is received, the flag is set, the following 0 or 1 unsets it. By counting the marks and bits, we can use the following logic for a frame accept:

if ((prevMark == 1) && (markCount == 6) && (bitCount == 59))

and the following for a frame reject:

else if ((prevMark == 1) && ((markCount != 6) || (bitCount != 59)))

Here's the updated Arduino WWVB clock data decode sketch.

Clock WWVB Data Decode Frame Reject
/**********************************************************************
* 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
}



And some WWVB data stream showing we now have frame rejects instead of getting intermittent garbage that might accidentally get decoded into garbage input.

 : 10100001 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 10100010 : 000000011 : 000100100 : 100100101 : 00110000 : 1 : 100100011 :
->Scratch<- Bad Data - Marks: 7 Bits: 61 Frame Errors: 01

: 10100011 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 10100100 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 10100101 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 10100110 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 10100111 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 10101000 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 10101001 : 000000011 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 00000000 : 000000100 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 00000001 : 000000100 : 000100100 : 1001001 : 11 : 001100000 : 100100011 :
->Scratch<- Bad Data - Marks: 7 Bits: 61 Frame Errors: 01

: 00000010 : 000000100 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 00000011 : 000000100 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode
: 00000100 : 000000100 : 000100100 : 100100101 : 001100000 : 100100011 : <- Frame Decode


Now, to change that into something a human can read!

No comments:

Post a Comment