872 lines
21 KiB
Arduino
872 lines
21 KiB
Arduino
|
/* WWVB Clock v 1.0
|
||
|
*
|
||
|
* A WWVB (NIST Time Code Broadcast) receiving clock powered by an
|
||
|
* Arduino (ATMega328), a DS1307 Real Time Clock, and a C-Max
|
||
|
* C-Max CMMR-6P-60 WWVB receiver module.
|
||
|
*
|
||
|
* This code builds on Capt.Tagon's code at duinolab.blogspot.com
|
||
|
* which was a great reference. Specifically, I used almost without change
|
||
|
* his struct / unsigned long long overlay for the received WWVB Buffer,
|
||
|
* which apparently in turn draws on DCF77 decoding code by Rudi Niemeijer,
|
||
|
* Mathias Dalheimer, and "Captain."
|
||
|
*
|
||
|
* The RTC (DS1307) interface code builds in part on code by
|
||
|
* Jon McPhalen (www.jonmcphalen.com)
|
||
|
*
|
||
|
* There you have it.
|
||
|
*
|
||
|
* This code supports the "Atomic Clock" article in the April 2010 issue
|
||
|
* of Popular Science. There is also a schematic for this project. There
|
||
|
* is also WWVB signal simulator code, to facilitate debugging and
|
||
|
* hacking on this project when the reception of the WWVB signal
|
||
|
* itself is less than stellar.
|
||
|
*
|
||
|
* The code for both the clock and the WWVB simulator, and the schematic
|
||
|
* are available online at:
|
||
|
* http://www.popsci.com/diy/article/2010-03/build-clock-uses-atomic-timekeeping
|
||
|
*
|
||
|
* and on GitHub at: http://github.com/vinmarshall/WWVB-Clock
|
||
|
*
|
||
|
* Version 1.0
|
||
|
* Notes:
|
||
|
* - No timezone support in this version. UTC only.
|
||
|
* - No explicit leapsecond (3 frame marker bits) support in this version.
|
||
|
* - 24 hour mode only
|
||
|
*
|
||
|
*
|
||
|
* Copyright (c) 2010 Vin Marshall (vlm@2552.com, www.2552.com)
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person
|
||
|
* obtaining a copy of this software and associated documentation
|
||
|
* files (the "Software"), to deal in the Software without
|
||
|
* restriction, including without limitation the rights to use,
|
||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
* copies of the Software, and to permit persons to whom the
|
||
|
* Software is furnished to do so, subject to the following
|
||
|
* conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be
|
||
|
* included in all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#define SERIAL_RATE 115200
|
||
|
#define WWV_SIGNAL_PIN 14
|
||
|
#define PPS_OUTPUT_PIN 16
|
||
|
#define DEBUG1_OUTPUT_PIN 15
|
||
|
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <LiquidCrystal_I2C.h>
|
||
|
LiquidCrystal_I2C lcd(0x27, 16, 2);
|
||
|
|
||
|
void ICACHE_RAM_ATTR wwvbChange();
|
||
|
|
||
|
//
|
||
|
// Initializations
|
||
|
|
||
|
// Debugging mode - prints debugging information on the Serial port.
|
||
|
boolean debug = true;
|
||
|
|
||
|
// Front Panel Light
|
||
|
int lightPin = LED_BUILTIN;
|
||
|
|
||
|
// WWVB Receiver Pin
|
||
|
int wwvbInPin = WWV_SIGNAL_PIN;
|
||
|
|
||
|
// LCD init
|
||
|
//LiquidCrystal lcd(12, 11, 6, 5, 4, 3);
|
||
|
|
||
|
// RTC init
|
||
|
// RTC uses pins Analog4 = SDA, Analog5 = SCL
|
||
|
|
||
|
// RTC I2C Slave Address
|
||
|
#define DS1307 0xD0 >> 1
|
||
|
|
||
|
// RTC Memory Registers
|
||
|
#define RTC_SECS 0
|
||
|
#define RTC_MINS 1
|
||
|
#define RTC_HRS 2
|
||
|
#define RTC_DAY 3
|
||
|
#define RTC_DATE 4
|
||
|
#define RTC_MONTH 5
|
||
|
#define RTC_YEAR 6
|
||
|
#define RTC_SQW 7
|
||
|
|
||
|
// Month abbreviations
|
||
|
char *months[12] = {
|
||
|
"Jan",
|
||
|
"Feb",
|
||
|
"Mar",
|
||
|
"Apr",
|
||
|
"May",
|
||
|
"Jun",
|
||
|
"Jul",
|
||
|
"Aug",
|
||
|
"Sep",
|
||
|
"Oct",
|
||
|
"Nov",
|
||
|
"Dec"
|
||
|
};
|
||
|
|
||
|
// Day of Year to month translation (thanks to Capt.Tagon)
|
||
|
// End of Month - to calculate Month and Day from Day of Year
|
||
|
int eomYear[14][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
|
||
|
{366,367} // overflow
|
||
|
};
|
||
|
|
||
|
|
||
|
//
|
||
|
// Globals
|
||
|
|
||
|
// Timing and error recording
|
||
|
unsigned long pulseStartTime = 0;
|
||
|
unsigned long pulseEndTime = 0;
|
||
|
unsigned long frameEndTime = 0;
|
||
|
unsigned long lastRtcUpdateTime = 0;
|
||
|
boolean bitReceived = false;
|
||
|
boolean wasMark = false;
|
||
|
int framePosition = 0;
|
||
|
int bitPosition = 1;
|
||
|
char lastTimeUpdate[17];
|
||
|
char lastBit = ' ';
|
||
|
int errors[10] = { 1,1,1,1,1,1,1,1,1,1 };
|
||
|
int errIdx = 0;
|
||
|
int bitError = 0;
|
||
|
boolean frameError = false;
|
||
|
|
||
|
// RTC clock variables
|
||
|
byte second = 0x00;
|
||
|
byte minute = 0x00;
|
||
|
byte hour = 0x00;
|
||
|
byte day = 0x00;
|
||
|
byte date = 0x01;
|
||
|
byte month = 0x01;
|
||
|
byte year = 0x00;
|
||
|
byte ctrl = 0x00;
|
||
|
|
||
|
// WWVB time variables
|
||
|
byte wwvb_hour = 0;
|
||
|
byte wwvb_minute = 0;
|
||
|
byte wwvb_day = 0;
|
||
|
byte wwvb_year = 0;
|
||
|
|
||
|
|
||
|
/* WWVB time format struct - acts as an overlay on wwvbRxBuffer to extract time/date data.
|
||
|
* This points to a 64 bit buffer wwvbRxBuffer that the bits get inserted into as the
|
||
|
* incoming data stream is received. (Thanks to Capt.Tagon @ duinolab.blogspot.com)
|
||
|
*/
|
||
|
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
|
||
|
};
|
||
|
|
||
|
struct wwvbBuffer * wwvbFrame;
|
||
|
unsigned long long receiveBuffer;
|
||
|
unsigned long long lastFrameBuffer;
|
||
|
|
||
|
|
||
|
/*
|
||
|
* setup()
|
||
|
*
|
||
|
* uC Initialization
|
||
|
*/
|
||
|
|
||
|
void setup() {
|
||
|
Serial.begin(SERIAL_RATE);
|
||
|
Serial.println("*****************************************");
|
||
|
Serial.println("*****************************************");
|
||
|
Serial.println("*****************************************");
|
||
|
Serial.print("*** ESP8266 Ready.\n");
|
||
|
// Initialize the I2C Two Wire Communication for the RTC
|
||
|
//Wire.begin();
|
||
|
|
||
|
lcd.init();
|
||
|
lcd.backlight();
|
||
|
lcd.setCursor(0, 0);
|
||
|
lcd.print("booting");
|
||
|
|
||
|
|
||
|
// Setup the light pin
|
||
|
pinMode(lightPin, OUTPUT);
|
||
|
digitalWrite(lightPin, LOW);
|
||
|
pinMode(DEBUG1_OUTPUT_PIN, OUTPUT);
|
||
|
|
||
|
// Print a message to the LCD.
|
||
|
lcd.clear();
|
||
|
lcd.setCursor(0, 0);
|
||
|
lcd.print("WWVB Clock v 1.0");
|
||
|
lcd.setCursor(0, 1);
|
||
|
lcd.print("PopSci Apr. 2010");
|
||
|
delay(5000);
|
||
|
|
||
|
// Front panel light lights for 10 seconds on a successful frame rcpt.
|
||
|
digitalWrite(lightPin, HIGH);
|
||
|
|
||
|
// Setup the WWVB Signal In Handling
|
||
|
pinMode(wwvbInPin, INPUT);
|
||
|
attachInterrupt(0, wwvbChange, CHANGE);
|
||
|
|
||
|
// Setup the WWVB Buffer
|
||
|
lastFrameBuffer = 0;
|
||
|
receiveBuffer = 0;
|
||
|
wwvbFrame = (struct wwvbBuffer *) &lastFrameBuffer;
|
||
|
|
||
|
Serial.println("*** setup() complete");
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* loop()
|
||
|
*
|
||
|
* Main program loop
|
||
|
*/
|
||
|
|
||
|
void loop() {
|
||
|
|
||
|
// If we've received another bit, process it
|
||
|
if (bitReceived == true) {
|
||
|
Serial.println("*** bitReceived==true");
|
||
|
processBit();
|
||
|
}
|
||
|
|
||
|
// Read from the RTC and update the display 4x per second
|
||
|
if (millis() - lastRtcUpdateTime > 250) {
|
||
|
|
||
|
// Snag the RTC time and store it locally
|
||
|
//getRTC();
|
||
|
|
||
|
// And record the time of this last update.
|
||
|
lastRtcUpdateTime = millis();
|
||
|
|
||
|
// Update RTC if there has been a successfully received WWVB Frame
|
||
|
if (frameEndTime != 0) {
|
||
|
//updateRTC();
|
||
|
frameEndTime = 0;
|
||
|
}
|
||
|
|
||
|
// Update the display
|
||
|
updateDisplay();
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* updateDisplay()
|
||
|
*
|
||
|
* Update the 2 line x 16 char LCD display
|
||
|
*/
|
||
|
|
||
|
void updateDisplay() {
|
||
|
|
||
|
// Turn off the front panel light marking a successfully
|
||
|
// received frame after 10 seconds of being on.
|
||
|
if (bcd2dec(second) >= 10) {
|
||
|
digitalWrite(lightPin, HIGH);
|
||
|
}
|
||
|
|
||
|
// Update the LCD
|
||
|
lcd.clear();
|
||
|
|
||
|
// Update the first row
|
||
|
lcd.setCursor(0,0);
|
||
|
char *time = buildTimeString();
|
||
|
lcd.print(time);
|
||
|
|
||
|
// Update the second row
|
||
|
// Cycle through our list of status messages
|
||
|
lcd.setCursor(0,1);
|
||
|
int cycle = bcd2dec(second) / 10; // This gives us 6 slots for messages
|
||
|
char msg[17]; // 16 chars per line on display
|
||
|
|
||
|
switch (cycle) {
|
||
|
|
||
|
// Show the Date
|
||
|
case 0:
|
||
|
{
|
||
|
sprintf(msg, "%s %0.2i 20%0.2i",
|
||
|
months[bcd2dec(month)-1], bcd2dec(date), bcd2dec(year));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Show the WWVB signal strength based on the # of recent frame errors
|
||
|
case 1:
|
||
|
{
|
||
|
int signal = (10 - sumFrameErrors()) / 2;
|
||
|
sprintf(msg, "WWVB Signal: %i", signal);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Show LeapYear and LeapSecond Warning bits
|
||
|
case 2:
|
||
|
{
|
||
|
const char *leapyear = ( ((byte) wwvbFrame->Leapyear) == 1)?"Yes":"No";
|
||
|
const char *leapsec = ( ((byte) wwvbFrame->Leapsec) == 1)?"Yes":"No";
|
||
|
sprintf(msg, "LY: %s LS: %s", leapyear, leapsec);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Show our Daylight Savings Time status
|
||
|
case 3:
|
||
|
{
|
||
|
switch((byte)wwvbFrame->Dst) {
|
||
|
case 0:
|
||
|
sprintf(msg, "DST: No");
|
||
|
break;
|
||
|
case 1:
|
||
|
sprintf(msg, "DST: Ending");
|
||
|
break;
|
||
|
case 2:
|
||
|
sprintf(msg, "DST: Starting");
|
||
|
break;
|
||
|
case 3:
|
||
|
sprintf(msg, "DST: Yes");
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Show the UT1 correction sign and amount
|
||
|
case 4:
|
||
|
{
|
||
|
char sign;
|
||
|
if ((byte)wwvbFrame->OffSign == 2) {
|
||
|
sign = '-';
|
||
|
} else if ((byte)wwvbFrame->OffSign == 5) {
|
||
|
sign = '+';
|
||
|
} else {
|
||
|
sign = '?';
|
||
|
}
|
||
|
sprintf(msg, "UT1 %c 0.%i", sign, (byte) wwvbFrame->OffVal);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Show the time and date of the last successfully received
|
||
|
// wwvb frame
|
||
|
case 5:
|
||
|
{
|
||
|
sprintf(msg, "[%s]", lastTimeUpdate);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lcd.print(msg);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* buildTimeString
|
||
|
*
|
||
|
* Prepare the string for displaying the time on line 1 of the LCD
|
||
|
*/
|
||
|
|
||
|
char* buildTimeString() {
|
||
|
char rv[255];
|
||
|
sprintf(rv,"%0.2i:%0.2i:%0.2i UTC %c",
|
||
|
bcd2dec(hour),
|
||
|
bcd2dec(minute),
|
||
|
bcd2dec(second),
|
||
|
lastBit);
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* getRTC
|
||
|
*
|
||
|
* Read data from the DS1307 RTC over the I2C 2 wire interface.
|
||
|
* Data is stored in the uC's global clock variables.
|
||
|
*/
|
||
|
/*
|
||
|
void getRTC() {
|
||
|
|
||
|
// Begin the Transmission
|
||
|
Wire.beginTransmission(DS1307);
|
||
|
|
||
|
// Point the request at the first register (seconds)
|
||
|
Wire.write(RTC_SECS);
|
||
|
|
||
|
// End the Transmission and Start Listening
|
||
|
Wire.endTransmission();
|
||
|
Wire.requestFrom(DS1307, 8);
|
||
|
second = Wire.receive();
|
||
|
minute = Wire.receive();
|
||
|
hour = Wire.receive();
|
||
|
day = Wire.receive();
|
||
|
date = Wire.receive();
|
||
|
month = Wire.receive();
|
||
|
year = Wire.receive();
|
||
|
ctrl = Wire.receive();
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
|
||
|
/*
|
||
|
* setRTC
|
||
|
*
|
||
|
* Set the DS1307 RTC over the I2C 2 wire interface.
|
||
|
* Data is read from the uC's global clock variables.
|
||
|
*/
|
||
|
/*
|
||
|
void setRTC() {
|
||
|
|
||
|
// Begin the Transmission
|
||
|
Wire.beginTransmission(DS1307);
|
||
|
|
||
|
// Start at the beginning
|
||
|
Wire.write(RTC_SECS);
|
||
|
|
||
|
// Send data for each register in order
|
||
|
Wire.write(second);
|
||
|
Wire.write(minute);
|
||
|
Wire.write(hour);
|
||
|
Wire.send(day);
|
||
|
Wire.send(date);
|
||
|
Wire.send(month);
|
||
|
Wire.send(year);
|
||
|
Wire.send(ctrl);
|
||
|
|
||
|
// End the transmission
|
||
|
Wire.endTransmission();
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* updateRTC
|
||
|
*
|
||
|
* Update the time stored in the RTC to match the received WWVB frame.
|
||
|
*/
|
||
|
/*
|
||
|
void updateRTC() {
|
||
|
|
||
|
// Find out how long since the frame's On Time Marker (OTM)
|
||
|
// We'll need this adjustment when we set the time.
|
||
|
unsigned int timeSinceFrame = millis() - frameEndTime;
|
||
|
byte secondsSinceFrame = timeSinceFrame / 1000;
|
||
|
if (timeSinceFrame % 1000 > 500) {
|
||
|
secondsSinceFrame++;
|
||
|
}
|
||
|
|
||
|
// The OTM for a minute comes at the beginning of the frame, meaning that
|
||
|
// the WWVB time we have is now 1 minute + `secondsSinceFrame` seconds old.
|
||
|
incrementWwvbMinute();
|
||
|
|
||
|
// Set up data for the RTC clock write
|
||
|
second = secondsSinceFrame;
|
||
|
minute = ((byte) wwvbFrame->MinTen << 4) + (byte) wwvbFrame->MinOne;
|
||
|
hour = ((byte) wwvbFrame->HourTen << 4) + (byte) wwvbFrame->HourOne;
|
||
|
day = 0; // we're not using day of week at this time.
|
||
|
|
||
|
// Translate wwvb day of year into a month and a day of month
|
||
|
// This routine is courtesy of Capt.Tagon
|
||
|
int doy = ((byte) wwvbFrame->DayHun * 100) +
|
||
|
((byte) wwvbFrame->DayTen * 10) +
|
||
|
((byte) wwvbFrame->DayOne);
|
||
|
|
||
|
int i = 0;
|
||
|
byte isLeapyear = (byte) wwvbFrame->Leapyear;
|
||
|
while ( (i < 14) && (eomYear[i][isLeapyear] < doy) ) {
|
||
|
i++;
|
||
|
}
|
||
|
if (i>0) {
|
||
|
date = dec2bcd(doy - eomYear[i-1][isLeapyear]);
|
||
|
month = dec2bcd((i > 12)?1:i);
|
||
|
}
|
||
|
|
||
|
year = ((byte) wwvbFrame->YearTen << 4) + (byte) wwvbFrame->YearOne;
|
||
|
|
||
|
// And write the update to the RTC
|
||
|
//setRTC();
|
||
|
|
||
|
// Store the time of update for the display status line
|
||
|
sprintf(lastTimeUpdate, "%0.2i:%0.2i %0.2i/%0.2i/%0.2i",
|
||
|
bcd2dec(hour), bcd2dec(minute), bcd2dec(month),
|
||
|
bcd2dec(date), bcd2dec(year));
|
||
|
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* processBit()
|
||
|
*
|
||
|
* Decode a received pulse. Pulses are decoded according to the
|
||
|
* length of time the pulse was in the low state.
|
||
|
*/
|
||
|
|
||
|
void processBit() {
|
||
|
Serial.println("*** in processBit()");
|
||
|
char buf[255];
|
||
|
|
||
|
// Clear the bitReceived flag, as we're processing that bit.
|
||
|
bitReceived = false;
|
||
|
|
||
|
// determine the width of the received pulse
|
||
|
unsigned int pulseWidth = pulseEndTime - pulseStartTime;
|
||
|
|
||
|
sprintf(buf,"got bit with pulseWidth=%d\n",pulseWidth);
|
||
|
Serial.print(buf);
|
||
|
|
||
|
// Attempt to decode the pulse into an Unweighted bit (0),
|
||
|
// a Weighted bit (1), or a Frame marker.
|
||
|
|
||
|
// Pulses < 0.2 sec are an error in reception.
|
||
|
if (pulseWidth < 100) {
|
||
|
buffer(-2);
|
||
|
bitError++;
|
||
|
wasMark = false;
|
||
|
|
||
|
// 0.2 sec pulses are an Unweighted bit (0)
|
||
|
} else if (pulseWidth < 400) {
|
||
|
buffer(0);
|
||
|
wasMark = false;
|
||
|
|
||
|
// 0.5 sec pulses are a Weighted bit (1)
|
||
|
} else if (pulseWidth < 700) {
|
||
|
buffer(1);
|
||
|
wasMark = false;
|
||
|
|
||
|
// 0.8 sec pulses are a Frame Marker
|
||
|
} else if (pulseWidth < 900) {
|
||
|
|
||
|
// Two Frame Position markers in a row indicate an
|
||
|
// end/beginning of frame marker
|
||
|
if (wasMark) {
|
||
|
|
||
|
// For the display update
|
||
|
lastBit = '*';
|
||
|
Serial.println("*** Frame Start");
|
||
|
|
||
|
// Verify that our position data jives with this being
|
||
|
// a Frame start/end marker
|
||
|
if ( (framePosition == 6) &&
|
||
|
(bitPosition == 60) &&
|
||
|
(bitError == 0)) {
|
||
|
|
||
|
// Process a received frame
|
||
|
frameEndTime = pulseStartTime;
|
||
|
lastFrameBuffer = receiveBuffer;
|
||
|
digitalWrite(lightPin, LOW);
|
||
|
logFrameError(false);
|
||
|
|
||
|
debugPrintFrame();
|
||
|
|
||
|
} else {
|
||
|
frameError = true;
|
||
|
}
|
||
|
|
||
|
// Reset the position counters
|
||
|
framePosition = 0;
|
||
|
bitPosition = 1;
|
||
|
wasMark = false;
|
||
|
bitError = 0;
|
||
|
receiveBuffer = 0;
|
||
|
|
||
|
// Otherwise, this was just a regular frame position marker
|
||
|
} else {
|
||
|
buffer(-1);
|
||
|
wasMark = true;
|
||
|
}
|
||
|
|
||
|
// Pulses > 0.8 sec are an error in reception
|
||
|
} else {
|
||
|
buffer(-2);
|
||
|
bitError++;
|
||
|
wasMark = false;
|
||
|
}
|
||
|
|
||
|
// Reset everything if we have more than 60 bits in the frame. This means
|
||
|
// the frame markers went missing somewhere along the line
|
||
|
if (frameError == true || bitPosition > 60) {
|
||
|
|
||
|
// Debugging
|
||
|
Serial.println("*** Frame Error");
|
||
|
|
||
|
// Reset all of the frame pointers and flags
|
||
|
frameError = false;
|
||
|
logFrameError(true);
|
||
|
framePosition = 0;
|
||
|
bitPosition = 1;
|
||
|
wasMark = false;
|
||
|
bitError = 0;
|
||
|
receiveBuffer = 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* logFrameError
|
||
|
*
|
||
|
* Log the error in the buffer that is a window on the past 10 frames.
|
||
|
*/
|
||
|
|
||
|
void logFrameError(boolean err) {
|
||
|
|
||
|
// Add a 1 to log errors to the buffer
|
||
|
int add = err?1:0;
|
||
|
errors[errIdx] = add;
|
||
|
|
||
|
// and move the buffer loop-around pointer
|
||
|
if (++errIdx >= 10) {
|
||
|
errIdx = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* sumFrameErrors
|
||
|
*
|
||
|
* Turn the errors in the frame error buffer into a number useful to display
|
||
|
* the quality of reception of late in the status messages. Sums the errors
|
||
|
* in the frame error buffer.
|
||
|
*/
|
||
|
|
||
|
int sumFrameErrors() {
|
||
|
|
||
|
// Sum all of the values in our error buffer
|
||
|
int i, rv;
|
||
|
for (i=0; i< 10; i++) {
|
||
|
rv += errors[i];
|
||
|
}
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* debugPrintFrame
|
||
|
*
|
||
|
* Print the decoded frame over the seriail port
|
||
|
*/
|
||
|
|
||
|
void debugPrintFrame() {
|
||
|
char time[255];
|
||
|
byte minTen = (byte) wwvbFrame->MinTen;
|
||
|
byte minOne = (byte) wwvbFrame->MinOne;
|
||
|
byte hourTen = (byte) wwvbFrame->HourTen;
|
||
|
byte hourOne = (byte) wwvbFrame->HourOne;
|
||
|
byte dayHun = (byte) wwvbFrame->DayHun;
|
||
|
byte dayTen = (byte) wwvbFrame->DayTen;
|
||
|
byte dayOne = (byte) wwvbFrame->DayOne;
|
||
|
byte yearOne = (byte) wwvbFrame->YearOne;
|
||
|
byte yearTen = (byte) wwvbFrame->YearTen;
|
||
|
|
||
|
byte wwvb_minute = (10 * minTen) + minOne;
|
||
|
byte wwvb_hour = (10 * hourTen) + hourOne;
|
||
|
byte wwvb_day = (100 * dayHun) + (10 * dayTen) + dayOne;
|
||
|
byte wwvb_year = (10 * yearTen) + yearOne;
|
||
|
|
||
|
sprintf(time, "\nFrame Decoded: %0.2i:%0.2i %0.3i 20%0.2i\n",
|
||
|
wwvb_hour, wwvb_minute, wwvb_day, wwvb_year);
|
||
|
Serial.print(time);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* buffer
|
||
|
*
|
||
|
* Places the received bits in the receive buffer in the order they
|
||
|
* were recived. The first received bit goes in the highest order
|
||
|
* bit of the receive buffer.
|
||
|
*/
|
||
|
|
||
|
void buffer(int bit) {
|
||
|
|
||
|
// Process our bits
|
||
|
if (bit == 0) {
|
||
|
lastBit = '0';
|
||
|
if (debug) { Serial.println("0"); }
|
||
|
|
||
|
} else if (bit == 1) {
|
||
|
lastBit = '1';
|
||
|
if (debug) { Serial.println("1"); }
|
||
|
|
||
|
} else if (bit == -1) {
|
||
|
lastBit = 'M';
|
||
|
if (debug) { Serial.println("M"); }
|
||
|
framePosition++;
|
||
|
|
||
|
} else if (bit == -2) {
|
||
|
lastBit = 'X';
|
||
|
if (debug) { Serial.println("X"); }
|
||
|
}
|
||
|
|
||
|
// Push the bit into the buffer. The 0s and 1s are the only
|
||
|
// ones we care about.
|
||
|
if (bit < 0) { bit = 0; }
|
||
|
receiveBuffer = receiveBuffer | ( (unsigned long long) bit << (64 - bitPosition) );
|
||
|
|
||
|
// And increment the counters that keep track of where we are
|
||
|
// in the frame.
|
||
|
bitPosition++;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* incrementWwvbMinute
|
||
|
*
|
||
|
* The frame On Time Marker occurs at the beginning of the frame. This means
|
||
|
* that the time in the frame is one minute old by the time it has been fully
|
||
|
* received. Adding one to the minute can be somewhat complicated, in as much
|
||
|
* as it can roll over the successive hours, days, and years in just the right
|
||
|
* circumstances. This handles that.
|
||
|
*/
|
||
|
|
||
|
void incrementWwvbMinute() {
|
||
|
|
||
|
// Increment the Time and Date
|
||
|
if (++(wwvbFrame->MinOne) == 10) {
|
||
|
wwvbFrame->MinOne = 0;
|
||
|
wwvbFrame->MinTen++;
|
||
|
}
|
||
|
|
||
|
if (wwvbFrame->MinTen == 6) {
|
||
|
wwvbFrame->MinTen = 0;
|
||
|
wwvbFrame->HourOne++;
|
||
|
}
|
||
|
|
||
|
if (wwvbFrame->HourOne == 10) {
|
||
|
wwvbFrame->HourOne = 0;
|
||
|
wwvbFrame->HourTen++;
|
||
|
}
|
||
|
|
||
|
if ( (wwvbFrame->HourTen == 2) && (wwvbFrame->HourOne == 4) ) {
|
||
|
wwvbFrame->HourTen = 0;
|
||
|
wwvbFrame->HourOne = 0;
|
||
|
wwvbFrame->DayOne++;
|
||
|
}
|
||
|
|
||
|
if (wwvbFrame->DayOne == 10) {
|
||
|
wwvbFrame->DayOne = 0;
|
||
|
wwvbFrame->DayTen++;
|
||
|
}
|
||
|
|
||
|
if (wwvbFrame->DayTen == 10) {
|
||
|
wwvbFrame->DayTen = 0;
|
||
|
wwvbFrame->DayHun++;
|
||
|
}
|
||
|
|
||
|
if ( (wwvbFrame->DayHun == 3) &&
|
||
|
(wwvbFrame->DayTen == 6) &&
|
||
|
(wwvbFrame->DayOne == (6 + (int) wwvbFrame->Leapyear)) ) {
|
||
|
// Happy New Year.
|
||
|
wwvbFrame->DayHun = 0;
|
||
|
wwvbFrame->DayTen = 0;
|
||
|
wwvbFrame->DayOne = 1;
|
||
|
wwvbFrame->YearOne++;
|
||
|
}
|
||
|
|
||
|
if (wwvbFrame->YearOne == 10) {
|
||
|
wwvbFrame->YearOne = 0;
|
||
|
wwvbFrame->YearTen++;
|
||
|
}
|
||
|
|
||
|
if (wwvbFrame->YearTen == 10) {
|
||
|
wwvbFrame->YearTen = 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* wwvbChange
|
||
|
*
|
||
|
* This is the interrupt servicing routine. Changes in the level of the
|
||
|
* received WWVB pulse are recorded here to be processed in processBit().
|
||
|
*/
|
||
|
|
||
|
void wwvbChange() {
|
||
|
digitalWrite(DEBUG1_OUTPUT_PIN, HIGH);
|
||
|
|
||
|
int signalLevel = digitalRead(wwvbInPin);
|
||
|
|
||
|
// Determine if this was triggered by a rising or a falling edge
|
||
|
// and record the pulse low period start and stop times
|
||
|
|
||
|
//FIXME this might need to be HIGH
|
||
|
if (signalLevel == HIGH) {
|
||
|
pulseStartTime = millis();
|
||
|
} else {
|
||
|
pulseEndTime = millis();
|
||
|
bitReceived = true;
|
||
|
}
|
||
|
|
||
|
digitalWrite(DEBUG1_OUTPUT_PIN, LOW);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* bcd2dec
|
||
|
*
|
||
|
* Utility function to convert 2 bcd coded hex digits into an integer
|
||
|
*/
|
||
|
|
||
|
int bcd2dec(int bcd) {
|
||
|
return ( (bcd>>4) * 10) + (bcd % 16);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* dec2bcd
|
||
|
*
|
||
|
* Utility function to convert an integer into 2 bcd coded hex digits
|
||
|
*/
|
||
|
|
||
|
int dec2bcd(int dec) {
|
||
|
return ( (dec/10) << 4) + (dec % 10);
|
||
|
}
|
||
|
|
||
|
|