/* WWVB Translator A C Backus 8/18 To provide a digital signal to key a low power 60 kHz transmitter to simulate the WWVB time code signal, taking the time and date from the GPS system. Code is for Adafruit GPS modules using MTK3329/MTK3339 driver. */ #include // must have downloaded this library #include // Connect the GPS TX pad to Digital 8 // Connect the GPS RX pad to Digital 7 SoftwareSerial mySerial(8, 7); // start soft serial and set pins for I/O // set data rates: #define PMTK_SET_NMEA_UPDATE_01HZ "$PMTK220,10000*2F" // 0.1 Hz, 10 sec wait #define PMTK_SET_NMEA_UPDATE_02HZ "$PMTK220,5000*1B" // 0.2 Hz, 5 sec wait #define PMTK_SET_NMEA_UPDATE_1HZ "$PMTK220,1000*1F" #define PMTK_SET_NMEA_UPDATE_5HZ "$PMTK220,200*2C" #define PMTK_SET_NMEA_UPDATE_10HZ "$PMTK220,100*2F" // set baud rates for GPS: #define PMTK_SET_NMEA_BAUDRATE_38400 "PMTK251,38400*27" #define PMTK_SET_NMEA_BAUDRATE_9600 "$PMTK251,9600*17" #define PMTK_SET_NMEA_BAUDRATE_4800 "PMTK251,4800*14" // turn on only RMC sentence: #define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" // turn on RMC and GGA sentences: #define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" // turn on ALL THE DATA: #define PMTK_SET_NMEA_OUTPUT_ALLDATA "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28" // turn off output: #define PMTK_SET_NMEA_OUTPUT_OFF "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" // get hardware version: #define PMTK_Q_RELEASE "$PMTK605*31" // turn off the default antenna report: #define PGCMD_SET_NMEA_ANTRPTOFF "$PGCMD,33,0*6D" char c = 'A'; // dummy character variable int n = 0; // dummy counting variable int x = 0; // dummy counting variable unsigned long timestamp = 0; // time stamp for data char sentence[75]; // data sentence int dst = 0; // 1 for DST; 0 for standard int dstwarn = 0; // changes one day before DST change int dayDSTon = 0; // day number(of the year) when DST goes on int dayDSToff = 0; // day number(of the year) when DST goes off int year = 0; // two-digit year, beginning 2000 int month = 0; // 1-12 int dayyear = 0; // day number within the year int daytemp = 0; // temp storage of day number int daymonth = 0; // day number within the month int hour = 0; // 0-23 int minute = 0; // 0-59 int second = 0; // 0-59 int msecond = 0; // milliseconds void setup() { pinMode(2,OUTPUT); // HIGH turns on 60 kHz carrier digitalWrite(2, HIGH); // default for signal is ON Serial.begin(115200); // start serial connection over USB mySerial.begin(9600); // start soft serial connection at default GPS rate mySerial.println(PMTK_SET_NMEA_BAUDRATE_9600); // set GPS baud rate to 9600 mySerial.println(PMTK_SET_NMEA_UPDATE_10HZ); // set data rate at 10 Hz mySerial.println(PGCMD_SET_NMEA_ANTRPTOFF); // turn off antenna report mySerial.println(PMTK_SET_NMEA_OUTPUT_RMCONLY); // turn output on only for RMC delay(2000); // wait for GPS to send ack sentences } void loop() { getdata(); // get 74 data characters starting after '$' (final is a null) getdata(); // do again to be sure everything was synced up while(int(sentence[10])==53 || timestamp > 4294907296) { // check if sentence too close to the end of the minute (at 50+ sec) getdata(); // or the millis() max bits rollover (within 60 seconds) } // if either, get another sentence parsedata(); // read charater data into variables dayyearcalc(); // calculate day number of the year from date and month minuteincrement(); // set variables for the next minute dstset(); // calculate and set DST flags // calulate delay before starting transmit on the next minute (with final correction): delay(60000 - (1000*second + msecond) - (millis() - timestamp) - 300); Serial.print("Year: "); Serial.println(year); Serial.print("Day: "); Serial.println(dayyear); Serial.print("Hour: "); Serial.println(hour); Serial.print("Minute: "); Serial.println(minute); Serial.println(); // run five minutes before refreshing data: for(n=0;n>=5;n++) { transmit(); minuteincrement(); } } // getdata() reads the serial output from the GPS, first clearing out the receive buffer, // then looking for a '$', which marks the beginnning of the RMC sentence -- then // reading the next 74 characters into the sentence[75] character array: void getdata() { while(mySerial.available()) // clear out receive buffer { c = mySerial.read(); } c = 'x'; while(c != '$') // look for '$' { if(mySerial.available()) { c = mySerial.read(); timestamp = millis(); // time stamp the sentence } } n = 0; while(n < 74) // collect the next 74 characters { if(mySerial.available()) { sentence[n] = mySerial.read(); n = n + 1; } } } // parsedata() reads the character data from the sentence[75] character array and // populates the appropriate variables with corresponding integer data, converting // from ASCII character numbers for the digits to the digits themselves by // subtracting 48, the ASCII decimal code for zero: void parsedata() { // the time data appear first in the RMC sentence, six characters beyond the start: hour = 10*(int(sentence[6])-48) + int(sentence[7])-48; minute = 10*(int(sentence[8])-48) + int(sentence[9])-48; second = 10*(int(sentence[10])-48) + int(sentence[11])-48; msecond = 100*(int(sentence[13])-48) + 10*(int(sentence[14])-48) + int(sentence[15])-48; // the date data appear near the end, just before the * signifying that the error // correction bytes follow -- whether or not there is a GPS fix: for (n=15;sentence[n]!='*';n++) // find '*', looking beyond the time data { x = n - 9; } daymonth = 10*(int(sentence[x])-48) + int(sentence[x+1])-48; month = 10*(int(sentence[x+2])-48) + int(sentence[x+3])-48; year = 10*(int(sentence[x+4])-48) + int(sentence[x+5]-48); } void dayyearcalc() // determine the dayyear from the month and daymonth { x = 0; if(year%4 == 0) // set x to 1 if leap year { x = 1; } if(month == 1) { dayyear = daymonth; } if(month == 2) { dayyear = daymonth + 31; } if(month == 3) { dayyear = daymonth + 59 + x; } if(month == 4) { dayyear = daymonth + 90 + x; } if(month == 5) { dayyear = daymonth + 120 + x; } if(month == 6) { dayyear = daymonth + 151 + x; } if(month == 7) { dayyear = daymonth + 181 + x; } if(month == 8) { dayyear = daymonth + 212 + x; } if(month == 9) { dayyear = daymonth + 243 + x; } if(month == 10) { dayyear = daymonth + 273 + x; } if(month == 11) { dayyear = daymonth + 304 + x; } if(month == 12) { dayyear = daymonth + 334 + x; } } // dstset() determines the dayyear for the start and end of DST then sets or unsets // the flags for the 57th and 58th seconds in the WWVB time code: void dstset() { daytemp = dayyear; // store current dayyear month = 3; // determine dayyear of 2nd Sunday in March daymonth = 1; dayyearcalc(); while((5 + dayyear + year*365 + (year+3)/4)%7 != 0) // Sunday == 0; find the first { dayyear++; } dayDSTon = dayyear + 7; // set DST-on variable to second Sunday month = 11; // determine dayyear of 1st Sunday in November daymonth = 1; dayyearcalc(); while((5 + dayyear + year*365 + (year+3)/4)%7 != 0) // a Sunday == 0; find the first { dayyear++; } dayDSToff = dayyear; // set DST-off variable to 1st Sunday in November dayyear = daytemp; // return dayyear to current value dstwarn = 0; // set warn flag to zero for beginning of the year dst = 0; // set change flag to zero for the beginning of the year if(dayyear >= (dayDSTon - 1)) // set warning flag if it is the day before change or later { dstwarn = 1; } if(dayyear >= dayDSTon) // set change flag if it is the change day or later { dst = 1; } if(dayyear >= (dayDSToff - 1)) // unset warning flag if it is the day before change or later { dstwarn = 0; } if(dayyear >= dayDSToff) // unset change flag if it is the change day or later { dst = 0; } } // minuteincrement() increases the minute variable by one and then makes the // subsequent resulting changes in other time variables: void minuteincrement() { minute = minute + 1; if(minute == 60) { minute = 0; hour++; if(hour == 24) { hour = 0; dayyear++; if(year%4 == 0) { if(dayyear == 367) { year++; } } else { if(dayyear == 366) { year++; } } } } } // transmit() determines from the time and date variables the appropriate BCD code elements // to be sent at each second of the minute of code transmission; in addition to zeros and ones, // there is a mark element sent every ten seconds: void transmit() { mark(); //second 00 -- marks the beginning of the minute x = minute; //second 01 -- beginning of MINUTE code if(x/40 == 1) { one(); x = x-40; } else { zero(); } if(x/20 == 1) //second 02 { one(); x = x-20; } else { zero(); } if(x/10 == 1) //second 03 { one(); x = x-10; } else { zero(); } zero(); //second 04 if(x/8 == 1) //second 05 { one(); x = x-8; } else { zero(); } if(x/4 == 1) //second 06 { one(); x = x-4; } else { zero(); } if(x/2 == 1) //second 07 { one(); x = x-2; } else { zero(); } if(x/1 == 1) //second 08 { one(); x = x-1; } else { zero(); } mark(); //second 09 zero(); //second 10 zero(); //second 11 x = hour; //second 12 -- beginning of HOUR code if(x/20 == 1) { one(); x = x-20; } else { zero(); } if(x/10 == 1) //second 13 { one(); x = x-10; } else { zero(); } zero(); //second 14 if(x/8 == 1) //second 15 { one(); x = x-8; } else { zero(); } if(x/4 == 1) //second 16 { one(); x = x-4; } else { zero(); } if(x/2 == 1) //second 17 { one(); x = x-2; } else { zero(); } if(x/1 == 1) //second 18 { one(); x = x-1; } else { zero(); } mark(); //second 19 zero(); //second 20 zero(); //second 21 x = dayyear; //second 22 -- beginning of DAY code if(x/200 == 1) { one(); x = x-200; } else { zero(); } if(x/100 == 1) //second 23 { one(); x = x-100; } else { zero(); } zero(); //second 24 if(x/80 == 1) //second 25 { one(); x = x-80; } else { zero(); } if(x/40 == 1) //second 26 { one(); x = x-40; } else { zero(); } if(x/20 == 1) //second 27 { one(); x = x-20; } else { zero(); } if(x/10 == 1) //second 28 { one(); x = x-10; } else { zero(); } mark(); //second 29 if(x/8 == 1) //second 30 { one(); x = x-8; } else { zero(); } if(x/4 == 1) //second 31 { one(); x = x-4; } else { zero(); } if(x/2 == 1) //second 32 { one(); x = x-2; } else { zero(); } if(x/1 == 1) //second 33 { one(); x = x-1; } else { zero(); } zero(); //second 34 zero(); //second 35 one(); //second 36 -- UT1 correction positive zero(); //second 37 -- UT1 correction positive one(); //second 38 -- UT1 correction positive mark(); //second 39 zero(); //second 40 -- zero UT1 correction zero(); //second 41 -- zero UT1 correction zero(); //second 42 -- zero UT1 correction zero(); //second 43 -- zero UT1 correction zero(); //second 44 x = year; //second 45 -- beginning of YEAR code if(x/80 == 1) { one(); x = x-80; } else { zero(); } if(x/40 == 1) //second 46 { one(); x = x-40; } else { zero(); } if(x/20 == 1) //second 47 { one(); x = x-20; } else { zero(); } if(x/10 == 1) //second 48 { one(); x = x-10; } else { zero(); } mark(); //second 49 if(x/8 == 1) //second 50 { one(); x = x-8; } else { zero(); } if(x/4 == 1) //second 51 { one(); x = x-4; } else { zero(); } if(x/2 == 1) //second 52 { one(); x = x-2; } else { zero(); } if(x/1 == 1) //second 53 { one(); x = x-1; } else { zero(); } zero(); //second 54 if(year%4 == 0) //second 55 -- one if leap year, zero if not { one(); } else { zero(); } zero(); //second 56 -- no leap second warning if( dstwarn == 1) { //second 57 -- DST change warning one(); } else { zero(); } if( dst == 1) { //second 58 -- make DST change one(); } else { zero(); } mark(); //second 59 } void mark() // constructs and sends the BCD code element MARK { digitalWrite(2,LOW); delay(800); digitalWrite(2,HIGH); delay(200); } void zero() // constructs and sends the BCD code element ZERO { digitalWrite(2,LOW); delay(200); digitalWrite(2,HIGH); delay(800); } void one() // constructs and sends the BCD code element ONE { digitalWrite(2,LOW); delay(500); digitalWrite(2,HIGH); delay(500); }