This commit is contained in:
Jeffrey Paul 2022-01-10 04:58:59 -08:00
parent ac052d190a
commit ae9c16e405
4 changed files with 2493 additions and 84 deletions

853
arduino/wwvb-ref/wwvb.ino Normal file
View File

@ -0,0 +1,853 @@
/* 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.
*
*/
#include <stdio.h>
#include <LiquidCrystal.h>
#include <Wire.h>
//
// Initializations
// Debugging mode - prints debugging information on the Serial port.
boolean debug = true;
// Front Panel Light
int lightPin = 13;
// WWVB Receiver Pin
int wwvbInPin = 2;
// 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() {
// Debugging information prints to the serial line
if (debug) { Serial.begin(9600); }
if (debug) { Serial.println("Ready."); }
// Initialize the I2C Two Wire Communication for the RTC
Wire.begin();
// Set up the LCD's number of rows and columns:
lcd.begin(16, 2);
// Setup the light pin
pinMode(lightPin, OUTPUT);
digitalWrite(lightPin, HIGH);
// 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, LOW);
// 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;
}
/*
* loop()
*
* Main program loop
*/
void loop() {
// If we've received another bit, process it
if (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, LOW);
}
// 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.send(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.send(RTC_SECS);
// Send data for each register in order
Wire.send(second);
Wire.send(minute);
Wire.send(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() {
// Clear the bitReceived flag, as we're processing that bit.
bitReceived = false;
// determine the width of the received pulse
unsigned int pulseWidth = pulseEndTime - pulseStartTime;
// 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 = '*';
if (debug) { Serial.print("*"); }
// 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, HIGH);
logFrameError(false);
if (debug) {
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
if (debug) { Serial.print(" Frame Error\n"); }
// 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.print("0"); }
} else if (bit == 1) {
lastBit = '1';
if (debug) { Serial.print("1"); }
} else if (bit == -1) {
lastBit = 'M';
if (debug) { Serial.print("M"); }
framePosition++;
} else if (bit == -2) {
lastBit = 'X';
if (debug) { Serial.print("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() {
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
if (signalLevel == LOW) {
pulseStartTime = millis();
} else {
pulseEndTime = millis();
bitReceived = true;
}
}
/*
* 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);
}

175
arduino/wwvb/floats.csv Normal file
View File

@ -0,0 +1,175 @@
94.34
39.62
67.36
48.64
71.57
78.93
86.80
37.59
41.87
76.45
65.54
70.19
77.87
73.15
70.13
33.27
27.52
87.68
74.66
49.75
74.90
68.83
69.74
58.10
49.23
61.75
94.63
85.94
46.70
68.18
48.62
75.80
50.17
52.28
66.22
68.36
70.89
46.68
71.96
90.99
74.44
97.34
22.63
25.14
54.39
47.70
80.27
75.28
73.23
63.09
71.75
73.39
29.64
60.99
78.13
73.29
52.33
68.42
71.71
60.31
54.13
76.60
58.99
75.99
85.64
74.30
77.69
79.76
79.61
60.93
54.99
45.50
81.65
74.05
67.01
85.79
83.92
76.03
72.04
32.79
33.62
67.26
71.17
78.02
63.84
67.86
58.04
71.88
72.92
72.11
68.45
68.71
93.77
65.92
78.33
63.78
58.41
48.74
71.87
53.10
80.33
58.18
72.19
79.94
85.44
78.55
82.78
71.12
63.79
62.94
63.66
58.44
50.59
91.02
89.28
68.35
86.34
31.90
69.33
65.45
76.41
83.79
73.95
73.08
60.52
51.60
59.42
82.91
82.62
64.72
69.13
67.46
71.94
78.55
73.33
70.58
80.87
77.23
75.79
87.83
88.32
79.55
60.92
71.68
69.79
83.03
89.25
76.60
55.45
67.67
82.52
77.72
65.99
60.15
73.46
68.14
61.96
85.54
64.17
83.86
61.66
79.66
64.02
45.70
76.10
63.10
64.77
94.71
76.37
75.78
76.00
35.66
65.96
68.23
91.41
1 94.34
2 39.62
3 67.36
4 48.64
5 71.57
6 78.93
7 86.80
8 37.59
9 41.87
10 76.45
11 65.54
12 70.19
13 77.87
14 73.15
15 70.13
16 33.27
17 27.52
18 87.68
19 74.66
20 49.75
21 74.90
22 68.83
23 69.74
24 58.10
25 49.23
26 61.75
27 94.63
28 85.94
29 46.70
30 68.18
31 48.62
32 75.80
33 50.17
34 52.28
35 66.22
36 68.36
37 70.89
38 46.68
39 71.96
40 90.99
41 74.44
42 97.34
43 22.63
44 25.14
45 54.39
46 47.70
47 80.27
48 75.28
49 73.23
50 63.09
51 71.75
52 73.39
53 29.64
54 60.99
55 78.13
56 73.29
57 52.33
58 68.42
59 71.71
60 60.31
61 54.13
62 76.60
63 58.99
64 75.99
65 85.64
66 74.30
67 77.69
68 79.76
69 79.61
70 60.93
71 54.99
72 45.50
73 81.65
74 74.05
75 67.01
76 85.79
77 83.92
78 76.03
79 72.04
80 32.79
81 33.62
82 67.26
83 71.17
84 78.02
85 63.84
86 67.86
87 58.04
88 71.88
89 72.92
90 72.11
91 68.45
92 68.71
93 93.77
94 65.92
95 78.33
96 63.78
97 58.41
98 48.74
99 71.87
100 53.10
101 80.33
102 58.18
103 72.19
104 79.94
105 85.44
106 78.55
107 82.78
108 71.12
109 63.79
110 62.94
111 63.66
112 58.44
113 50.59
114 91.02
115 89.28
116 68.35
117 86.34
118 31.90
119 69.33
120 65.45
121 76.41
122 83.79
123 73.95
124 73.08
125 60.52
126 51.60
127 59.42
128 82.91
129 82.62
130 64.72
131 69.13
132 67.46
133 71.94
134 78.55
135 73.33
136 70.58
137 80.87
138 77.23
139 75.79
140 87.83
141 88.32
142 79.55
143 60.92
144 71.68
145 69.79
146 83.03
147 89.25
148 76.60
149 55.45
150 67.67
151 82.52
152 77.72
153 65.99
154 60.15
155 73.46
156 68.14
157 61.96
158 85.54
159 64.17
160 83.86
161 61.66
162 79.66
163 64.02
164 45.70
165 76.10
166 63.10
167 64.77
168 94.71
169 76.37
170 75.78
171 76.00
172 35.66
173 65.96
174 68.23
175 91.41

1086
arduino/wwvb/output.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +1,405 @@
/* #include <LiquidCrystal_I2C.h>
ESP8266 Blink by Simon Peter LiquidCrystal_I2C lcd(0x27, 16, 2);
Blink the blue LED on the ESP-01 module
This example code is in the public domain
The blue LED on the ESP-01 module is connected to GPIO1
(which is also the TXD pin; so we cannot use Serial.print() at the same time)
Note that this sketch uses LED_BUILTIN to find the pin with the internal LED
*/
#define SERIAL_RATE 115200
#define WWV_SIGNAL_PIN 14 #define WWV_SIGNAL_PIN 14
#define PPS_OUTPUT_PIN 16
#define LOSC_INPUT_PIN 0
#define DEBUG1_OUTPUT_PIN 15
void ICACHE_RAM_ATTR readLevel(); #define ONEBIT 1
#define ZEROBIT 2
#define MARKBIT 3
volatile byte wwvbInState; // store receiver signal level #define NUM_SAMPLES_PER_FRAME 32000
#define CLOCKS_PER_MS 32
byte prevWwvbInState; // store previous signal level char statusString[25] = "LOS";
unsigned int prevEdgeMillis; // store time signal was read
byte bitVal; // bit decoded 0, 1 or Mark volatile unsigned int lowLatencyInState;
byte badBit; // bad bit, noise detected volatile unsigned int wwvbInState;
byte prevMark; // store previous mark bit volatile unsigned int lastWWVBInState;
volatile unsigned int stateStableCounter = 0;
volatile unsigned int stateStableMillis = 0;
volatile unsigned int lastStateStableMillis = 0;
volatile unsigned int secondCounter = 0;
volatile unsigned int milliCounter = 0;
volatile unsigned int clockCounter = 0;
volatile unsigned int timeToTick = 0;
volatile unsigned int frameReadyToStart = 0;
volatile unsigned int beginFrameSearch = 0;
volatile unsigned int frameSamples = 0;
volatile unsigned int lossOfSignal = 1;
volatile unsigned int displayUpdateRequired = 1;
volatile unsigned int frameSearch = 0;
volatile unsigned int frameStart = 0;
volatile unsigned int frameStartTime = 0;
volatile unsigned int frameHigh = 0;
volatile unsigned int frameHighReadOut = 0;
volatile unsigned int frameLow = 0;
volatile unsigned int frameLowReadOut = 0;
volatile unsigned int frameCounter = 0;
volatile unsigned int frameReadyForRead = 0;
volatile unsigned int lastBitReceived = 0;
volatile unsigned int millisSinceBoot = 0;
volatile unsigned int ppsActivationTime;
volatile unsigned int millisSinceSignalStateChange = 0;
volatile unsigned int minuteSync = 0;
void ICACHE_RAM_ATTR WWVFallingEdge();
void ICACHE_RAM_ATTR OSCEdge();
void ICACHE_RAM_ATTR MilliEdge();
void ICACHE_RAM_ATTR SecondEdge();
void setup() { void setup() {
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output pinMode(LED_BUILTIN, OUTPUT);
pinMode(WWV_SIGNAL_PIN, INPUT); pinMode(PPS_OUTPUT_PIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(WWV_SIGNAL_PIN), readLevel, CHANGE); // fire interrupt on edge detected pinMode(DEBUG1_OUTPUT_PIN, OUTPUT);
Serial.begin(9600); pinMode(WWV_SIGNAL_PIN, INPUT);
Serial.print("herro booted\n"); pinMode(LOSC_INPUT_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(LOSC_INPUT_PIN), OSCEdge, RISING);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("booting");
Serial.begin(SERIAL_RATE);
Serial.print("\n\n************\n\nherro booted\n");
ppsActivationTime = millis();
} }
void OSCEdge() {
clockCounter++;
if(clockCounter > CLOCKS_PER_MS) {
clockCounter -= CLOCKS_PER_MS;
// *****************************************************************************
// LOW LATENCY HACK to respond in 1/32nd of a ms to a falling
// start-of-second edge
// respond really fast to a falling edge if in the frameReadyToStart
// state
lowLatencyInState = digitalRead(WWV_SIGNAL_PIN);
if(!lowLatencyInState && frameReadyToStart && !lossOfSignal && !frameStart) {
// TICK!
// falling edge, beginning of a new frame and second
digitalWrite(PPS_OUTPUT_PIN, 1);
timeToTick = 1;
frameStart = 1;
frameStartTime = millisSinceBoot;
frameReadyToStart = 0;
frameSearch = 0;
}
// *****************************************************************************
MilliEdge();
}
}
void SecondEdge() {
secondCounter++;
displayUpdateRequired = 1;
}
void MilliEdge() {
digitalWrite(DEBUG1_OUTPUT_PIN, 1);
wwvbInState = digitalRead(WWV_SIGNAL_PIN);
milliCounter++;
millisSinceBoot++;
if(milliCounter > 1000) {
milliCounter -= 1000;
SecondEdge();
}
if(wwvbInState == lastWWVBInState) {
stateStableMillis++;
if(stateStableMillis > 1000) {
stateStableMillis = 1000;
}
} else {
//edge of some sort.
lastStateStableMillis = stateStableMillis;
// if it's a falling edge
if(!wwvbInState && (stateStableMillis > 100) && (stateStableMillis < 2000)) {
// main screen turn on
lossOfSignal = 0;
frameSearch = 1;
}
stateStableMillis = 0;
}
lastWWVBInState = wwvbInState; // copy/save for next loop
if((stateStableMillis > 2000) && !lossOfSignal) {
// we have received nothing for 2 seconds, loss of signal:
lossOfSignal = 1;
frameStart = 0;
frameCounter = 0;
minuteSync = 0;
digitalWrite(DEBUG1_OUTPUT_PIN, 0);
return;
}
if(frameSearch && wwvbInState) {
// if we have been high for 100ms (frameSearch) we are ready to start a new frame on the mark
frameHigh = 0;
frameLow = 0;
frameReadyToStart = 1;
digitalWrite(DEBUG1_OUTPUT_PIN, 0);
return;
}
if (frameStart && (frameSamples < NUM_SAMPLES_PER_FRAME)) {
//begin sampling
if (wwvbInState) {
frameHigh++;
} else {
frameLow++;
}
frameSamples++;
digitalWrite(DEBUG1_OUTPUT_PIN, 0);
return;
}
if(frameStart && (frameSamples >= NUM_SAMPLES_PER_FRAME)) {
frameReadyForRead = 1;
frameHighReadOut = frameHigh;
frameLowReadOut = frameLow;
frameStart = 0;
frameHigh = 0;
frameLow = 0;
frameSamples = 0;
}
digitalWrite(DEBUG1_OUTPUT_PIN, 0);
}
char pb[255];
// the loop function runs over and over again forever // the loop function runs over and over again forever
void loop() { void loop() {
if (wwvbInState != prevWwvbInState) { digitalWrite(LED_BUILTIN, !wwvbInState);
pulseValue();
prevWwvbInState = wwvbInState;
}
yield();
}
void pulseValue() { if(timeToTick) {
unsigned int edgeMillis = millis(); // save current time Serial.println("*** TICK in loop()");
badBit = 0; // set noise counter to zero timeToTick = 0;
if (wwvbInState == 1) { // rising edge TickSecond();
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; yield();
if(frameReadyForRead) {
Serial.println("*** processFrame() in loop()");
frameReadyForRead = 0;
processFrame();
} }
else if (pulseLength < 700) { // 500ms carrier drop one
bitVal = 1; yield();
PPSLowIfRequired();
yield();
if (displayUpdateRequired) {
Serial.println("*** updateDisplay() in loop()");
sprintf(pb,"*** secondCounter=%d\n", secondCounter);
Serial.print(pb);
updateDisplay();
displayUpdateRequired = 0;
} }
else { // 200ms carrier drop zero }
bitVal = 0;
void SetPPSHigh() {
digitalWrite(PPS_OUTPUT_PIN, 1);
}
void SetPPSLow() {
digitalWrite(PPS_OUTPUT_PIN, 0);
}
void SendPPS() {
unsigned int tickInterval = millisSinceBoot - ppsActivationTime;
ppsActivationTime = millisSinceBoot;
SetPPSHigh();
}
void PPSLowIfRequired() {
if ((millisSinceBoot - ppsActivationTime) > 500) {
SetPPSLow();
} }
if (badBit == 0) { }
printBitVal();
void TickSecond() {
char buf[255];
sprintf(buf, "TICK(%d): WWVB going low after %d ms high\n", frameCounter, lastStateStableMillis);
SendPPS();
Serial.print(buf);
}
void processFrame() {
char buf[255];
sprintf(buf, "end of frame summary: frameHigh: %d, frameLow: %d\n", frameHighReadOut, frameLowReadOut);
Serial.print(buf);
float rawVal = (float)frameHighReadOut / frameLowReadOut;
rawVal *= 1000;
unsigned int intRawVal = (int)rawVal;
if (intRawVal > 100000) {
intRawVal = 100000;
} }
} displayUpdateRequired++;
registerBit(convertDutyCycleToBit(intRawVal));
} }
int convertDutyCycleToBit(unsigned int rawVal) {
char buf[255];
/*
void readLevel() { 20% - marker
wwvbInState = digitalRead(WWV_SIGNAL_PIN); // read signal level 50% - one bit
digitalWrite(LED_BUILTIN, !wwvbInState); // flash WWVB receiver indicator pin 80% - zero bit
yield(); our cutoff points will be 50% and 80%
*/
char bitbuf[20];
int output = 0;
output = ZEROBIT;
sprintf(bitbuf, "ZERO");
if (rawVal > 20000) {
output = MARKBIT;
sprintf(bitbuf, "MARK");
}
if (rawVal > 30000) {
output = ONEBIT;
sprintf(bitbuf, "ONE");
}
sprintf(buf, "frame rawVal=%d, bit=%s\n", rawVal, bitbuf);
Serial.print(buf);
return output;
}
void registerBit(int doot) {
if (minuteSync) {
frameCounter++;
}
if (doot == MARKBIT) {
if (lastBitReceived == MARKBIT) {
// two mark bits in a row means we are in the first second of the minute
frameCounter = 0;
minuteSync = 1;
}
}
if (!minuteSync) {
frameCounter = 0;
}
sanityCheckFrame(doot);
lastBitReceived = doot;
logBit(doot);
}
void logBit(int doot) {
} }
void lossOfSync(int errorFrame) {
void printBitVal() { char buf[255];
if ((bitVal == 2) && (prevMark == 0)) { sprintf(buf, "ERROR: %d bit incorrect for framing, loss of sync!\n", errorFrame);
Serial.print(" : "); Serial.print(buf);
minuteSync = 0;
prevMark = 1; displayUpdateRequired++;
}
else if ((bitVal == 2) && (prevMark == 1)) {
Serial.print("\nBit Value: ");
Serial.print("| ");
prevMark = 0;
}
else {
Serial.print(bitVal, DEC);
prevMark = 0;
}
} }
/*****************************************************************************
* Time display functions
*****************************************************************************/
void printTime() { void sanityCheckFrame(int doot) {
Serial.print("?x00?y0?f"); // movie cursor to line 1 char 1, clear screen if (
(
(frameCounter == 9)
||
(frameCounter == 19)
||
(frameCounter == 29)
||
(frameCounter == 39)
||
(frameCounter == 49)
)
&& doot != MARKBIT) {
lossOfSync(frameCounter);
}
if (
(doot == MARKBIT)
&&
(
(frameCounter != 0)
&&
(frameCounter != 9)
&&
(frameCounter != 19)
&&
(frameCounter != 29)
&&
(frameCounter != 39)
&&
(frameCounter != 49)
)
) {
lossOfSync(frameCounter);
}
} }
// LCD routines to initialize LCD and clear screen void updateDisplay() {
void lcdInit() { // using P H Anderson Serial LCD driver board //Serial.print("updateDisplay()\n");
Serial.print("?G216"); // configure driver for 2 x 16 LCD
delay(300); if (frameStart) {
Serial.print("?BDD"); // set backlight brightness // don't do anything if we are currently sampling.
delay(300); return;
Serial.print("?f"); // clear screen }
Serial.print("?c0"); // set cursor off
if (lossOfSignal) {
sprintf(statusString, "LOS");
}
if (millisSinceBoot - frameStartTime < 10000) {
sprintf(statusString, "RX(syncing)");
}
if (minuteSync) {
sprintf(statusString, "RX(bit %d)", frameCounter);
}
char d[20];
if (lastBitReceived == MARKBIT) {
sprintf(d, "%d=MARK", frameCounter);
}
if (lastBitReceived == ONEBIT) {
sprintf(d, "%d=ONE ", frameCounter);
}
if (lastBitReceived == ZEROBIT) {
sprintf(d, "%d=ZERO", frameCounter);
}
lcd.clear();
lcd.setCursor(0, 0);
char msg[20];
sprintf(msg, "up:%03d", millisSinceBoot/1000);
lcd.print(msg);
lcd.setCursor(0, 1);
sprintf(msg, "s:%s", statusString);
lcd.print(msg);
if (minuteSync) {
lcd.setCursor(10, 0);
lcd.print(d);
}
} }