/* * WWVB time signal - Integrates ideas and code examples, as cited below. * This is Rev. 0.3 Version 0.1 and 0.2 included debug and test code. * * References: * Generating RF as MPU PWM signal and using same to set WWVB clock: * https://sites.google.com/site/wayneholder/controlling-time * Arduino tone: * https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/ * WWVB time code: * https://www.nist.gov/pml/time-and-frequency-division/radio-stations/wwvb/wwvb-time-code-format * Daylight saving time (definition): * https://www.nist.gov/pml/time-and-frequency-division/popular-links/daylight-saving-time-dst * Daylight saving time (algorithm): * https://stackoverflow.com/questions/5590429/calculating-daylight-saving-time-from-only-date * Leap year (definition): * https://www.mathsisfun.com/leap-years.html * Day number (of year): * https://www.geeksforgeeks.org/find-the-day-number-in-the-current-year-for-the-given-date/ * Day of week: * https://cs.uwaterloo.ca/~alopez-o/math-faq/node73.html * Tiny GPS plus: * http://arduiniana.org/libraries/tinygpsplus/ * Teensy hardware serial: * https://www.pjrc.com/teensy/td_uart.html * * Lloyd Milligan June 2020 */ #include #include "RTClib.h" // For DateTime type, used to compute day of the week (part of DST) const boolean PTT_MODE = false; // Continuous carrier, gated. Set false for tone/noTone control. // Prefix ptt means NAND gate control, while prefix key means // carrier control, i.e. tone() on/off. Terms are arbitrary.. #define VERSION 0.3 #define LED_BUILTIN 13 #define DTIME 500 // Use as indicator of successful load #define PTT 7 // Gate RF to simulate reduced/full power (optional) #define TONE_PIN 8 #define FREQ_HZ 60000 #define ONESEC 1000 TinyGPSPlus gps; // Instantiate GPS // Teensy hardware Serial4 is analogous to gpsSerial(31, 32), where MPU Rx pin 31 is connected to GPS Tx // MPU Tx to GPS Rx is NOT used. Substitute software serial for non-Teensy implementation. #define gpsSerial Serial4 static const uint32_t GPS_BAUD = 9600; const boolean WEIGHTED = true; const boolean UNWEIGHTED = false; const int CENTURY = 2000; boolean validGPS = false; boolean newGPSdata = false; // In case GPS is in and out boolean frmSecond = false; boolean leapYear = true; // Will be computed boolean leapSecond = false; // End of month (may be ignored) boolean DST = true; // Computed in newDay() boolean start_DST = false; // Ditto boolean end_DST = false; // Ditto // Prefix 'g' to avoid possible collision with keywords byte gMonth, gDay, gHour, gMinute, gSecond, hundredths; int gDOY, gYear, yearNN; int currentDay = 0; // Detect when day changes int currentYear = 0; // Ditto for year const int DUT1 = -2; // Signed integer tenths of second (6/12/2020) // Time code array const int FRAME_LENGTH = 60; boolean tFrame[FRAME_LENGTH]; void setup() { gpsSerial.begin(GPS_BAUD); // Start GPS communication pinMode(LED_BUILTIN, OUTPUT); pinMode(PTT, OUTPUT); pinMode(TONE_PIN, OUTPUT); tone(TONE_PIN, FREQ_HZ); digitalWrite(PTT, LOW); // Controlled by transmitter for (int i=0; i= 8) { tFrame[40] = WEIGHTED; d -= 8; } else tFrame[40] = UNWEIGHTED; if (d >= 4) { tFrame[41] = WEIGHTED; d -= 4; } else tFrame[41] = UNWEIGHTED; if (d >= 2) { tFrame[42] = WEIGHTED; d -= 2; } else tFrame[42] = UNWEIGHTED; if (d >= 1) { tFrame[43] = WEIGHTED; d -= 1; } else tFrame[43] = UNWEIGHTED; // if (leapSecond) // Leap second is ignored in this implementation (always false) tFrame[56] = WEIGHTED; else tFrame[56] = UNWEIGHTED; } void loadYearData() { // When year changes byte b = yearNN; if (b >= 80) { tFrame[45] = WEIGHTED; b -= 80; } else tFrame[45] = UNWEIGHTED; if (b >= 40) { tFrame[46] = WEIGHTED; b -= 40; } else tFrame[46] = UNWEIGHTED; if (b >= 20) { tFrame[47] = WEIGHTED; b -= 20; } else tFrame[47] = UNWEIGHTED; if (b >= 10) { tFrame[48] = WEIGHTED; b -= 10; } else tFrame[48] = UNWEIGHTED; // bit 49 marker if (b >= 8) { tFrame[50] = WEIGHTED; b -= 8; } else tFrame[50] = UNWEIGHTED; if (b >= 4) { tFrame[51] = WEIGHTED; b -= 4; } else tFrame[51] = UNWEIGHTED; if (b >= 2) { tFrame[52] = WEIGHTED; b -= 2; } else tFrame[52] = UNWEIGHTED; if (b >= 1) { tFrame[53] = WEIGHTED; b -= 1; } else tFrame[53] = UNWEIGHTED; } void loadDayData() { // When day changes, check for DST and possible year change if (leapYear) tFrame[55] = WEIGHTED; else tFrame[55] = UNWEIGHTED; // if (start_DST) { tFrame[57] = WEIGHTED; tFrame[58] = UNWEIGHTED; } else if (end_DST) { tFrame[57] = UNWEIGHTED; tFrame[58] = WEIGHTED; } else if (DST) { tFrame[57] = WEIGHTED; tFrame[58] = WEIGHTED; } else { tFrame[57] = UNWEIGHTED; tFrame[58] = UNWEIGHTED; } // int n = gDOY; // Load day of year data if (n >= 200) { tFrame[22] = WEIGHTED; n -= 200; } else tFrame[22] = UNWEIGHTED; if (n >= 100) { tFrame[23] = WEIGHTED; n -= 100; } else tFrame[23] = UNWEIGHTED; // bit 24 pseudo constant if (n >= 80) { tFrame[25] = WEIGHTED; n -= 80; } else tFrame[25] = UNWEIGHTED; if (n >= 40) { tFrame[26] = WEIGHTED; n -= 40; } else tFrame[26] = UNWEIGHTED; if (n >= 20) { tFrame[27] = WEIGHTED; n -= 20; } else tFrame[27] = UNWEIGHTED; if (n >= 10) { tFrame[28] = WEIGHTED; n -= 10; } else tFrame[28] = UNWEIGHTED; // bit 29 marker if (n >= 8) { tFrame[30] = WEIGHTED; n -= 8; } else tFrame[30] = UNWEIGHTED; if (n >= 4) { tFrame[31] = WEIGHTED; n -= 4; } else tFrame[31] = UNWEIGHTED; if (n >= 2) { tFrame[32] = WEIGHTED; n -= 2; } else tFrame[32] = UNWEIGHTED; if (n >= 1) { tFrame[33] = WEIGHTED; n -= 1; } else tFrame[33] = UNWEIGHTED; } void loadTimeData() { // The most dynamic part (changes each minute) byte b = gMinute; if (b >= 40) { tFrame[1] = WEIGHTED; b -= 40; } else tFrame[1] = UNWEIGHTED; if (b >= 20) { tFrame[2] = WEIGHTED; b -= 20; } else tFrame[2] = UNWEIGHTED; if (b >= 10) { tFrame[3] = WEIGHTED; b -= 10; } else tFrame[3] = UNWEIGHTED; // bit 4 pseudo constant if (b >= 8) { tFrame[5] = WEIGHTED; b -= 8; } else tFrame[5] = UNWEIGHTED; if (b >= 4) { tFrame[6] = WEIGHTED; b -= 4; } else tFrame[6] = UNWEIGHTED; if (b >= 2) { tFrame[7] = WEIGHTED; b -= 2; } else tFrame[7] = UNWEIGHTED; if (b >= 1) { tFrame[8] = WEIGHTED; b -= 1; } else tFrame[8] = UNWEIGHTED; b = gHour; if (b >= 20) { tFrame[12] = WEIGHTED; b -= 20; } else tFrame[12] = UNWEIGHTED; if (b >= 10) { tFrame[13] = WEIGHTED; b -= 10; } else tFrame[13] = UNWEIGHTED; // bit 14 pesudo constant if (b >= 8) { tFrame[15] = WEIGHTED; b -= 8; } else tFrame[15] = UNWEIGHTED; if (b >= 4) { tFrame[16] = WEIGHTED; b -= 4; } else tFrame[16] = UNWEIGHTED; if (b >= 2) { tFrame[17] = WEIGHTED; b -= 2; } else tFrame[17] = UNWEIGHTED; if (b >= 1) { tFrame[18] = WEIGHTED; b -= 1; } else tFrame[18] = UNWEIGHTED; } void pttFrame() { // Gated carrier mode (Optional) tone(TONE_PIN,FREQ_HZ); // Make double-sure that carrier is on for (int iSec=0; iSec<60; iSec++) { if ((iSec == 0) || (iSec % 10 == 9)) pttMark(); else { if (tFrame[iSec] == WEIGHTED) pttWeighted(); else pttUnweighted(); } } } void keyFrame() { // Carrier on/off mode (Normal) digitalWrite(PTT, HIGH); // Make double-sure that gate is enabled for (int iSec=0; iSec<60; iSec++) { if ((iSec == 0) || (iSec % 10 == 9)) keyMark(); else { if (tFrame[iSec] == WEIGHTED) keyWeighted(); else keyUnweighted(); } } } // Utilities void myDelay(long duration) { unsigned long startAt = millis(); while (millis() - startAt < duration) { while (gpsSerial.available()) // 'Milk' it gps.encode(gpsSerial.read()); } } // Gate the transmitter void pttMark() { // Marker // Set low for 0.8 sec // Reduced carrier / no carrier digitalWrite(PTT, LOW); myDelay(800); // Set high for 0.2 sec // Full carrier digitalWrite(PTT, HIGH); myDelay(200); digitalWrite(PTT, LOW); return; } void pttWeighted() { // Bit 1 'weighted' // Set low for 0.5 sec digitalWrite(PTT, LOW); myDelay(500); // Set high for 0.5 sec digitalWrite(PTT, HIGH); myDelay(500); digitalWrite(PTT, LOW); return; } void pttUnweighted() { // Bit 0 'unweighted' // Set low for 0.2 sec digitalWrite(PTT, LOW); myDelay(200); // Set high for 0.8 sec digitalWrite(PTT, HIGH); myDelay(800); digitalWrite(PTT, LOW); return; } // Bypass gate and key the carrier void keyMark() { // Marker // Set low for 0.8 sec // Reduced carrier / no carrier noTone(TONE_PIN); myDelay(800); // Set high for 0.2 sec // Full carrier tone(TONE_PIN,FREQ_HZ); myDelay(200); noTone(TONE_PIN); return; } void keyWeighted() { // Bit 1 'weighted' // Set low for 0.5 sec noTone(TONE_PIN); myDelay(500); // Set high for 0.5 sec tone(TONE_PIN,FREQ_HZ); myDelay(500); noTone(TONE_PIN); return; } void keyUnweighted() { // Bit 0 'unweighted' // Set low for 0.2 sec noTone(TONE_PIN); myDelay(200); // Set high for 0.8 sec tone(TONE_PIN,FREQ_HZ); myDelay(800); noTone(TONE_PIN); return; } // Generic utilities // Day number adapted from reference cited at top int dayNumber(int iMonth, int iDay) { int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // If current year is a leap year and the date // given is after the 28th of February then // it must include the 29th February if (iMonth > 2 && leapYear) { ++iDay; } // Add the days in the previous months while (iMonth-- > 0) { iDay = iDay + days[iMonth - 1]; } return iDay; } int myDayOfWeek() { // Disambiguate // Assumes valid values for the parameters named below DateTime ut ((int) gYear,(int) gMonth, (int) gDay, (int) gHour, (int) gMinute, 0); // Valued in processGPS() return ut.dayOfTheWeek(); } // DST status adapted from reference cited at top boolean dstStartsToday() { if (gMonth == 3) { if (gDay - 1 - myDayOfWeek() == 7) return true; } else return false; } boolean dstEndsToday() { if (gMonth == 11) // Must be here return gDay - 1 - myDayOfWeek() == 0; else return false; } boolean isDST() { // Not 'starts today' or 'ends today' just yes or no // Assumes valid data from processGPS() if ((gMonth < 3) || (gMonth > 11)) return false; if ((gMonth > 3) && (gMonth < 11)) return true; // Additional logic for March and November - if (gMonth == 3) { if (gDay - myDayOfWeek() >= 8) return true; else return false; } if (gMonth == 11) // Must be here return gDay - myDayOfWeek() <= 0; } // Leap year definition boolean isLeapYear() { int y = gYear; if (y % 4 == 0 && ((y % 100 != 0) || (y % 400 == 0))) return true; return false; } // Development / Debug void flashLED() { digitalWrite(LED_BUILTIN, HIGH); myDelay(ONESEC/8); digitalWrite(LED_BUILTIN, LOW); myDelay(ONESEC/8); return; } void flashLED(int n) { for (int i=0; i