#include LiquidCrystal_I2C lcd(0x27, 16, 2); #define SERIAL_RATE 115200 #define WWV_SIGNAL_PIN 14 #define PPS_OUTPUT_PIN 16 #define DEBUG1_OUTPUT_PIN 15 #define ONEBIT 1 #define ZEROBIT 2 #define MARKBIT 3 #define DEBUG false #define CLOCKS_PER_MS 10 #define PPS_PULSEWIDTH_MS 100 char statusString[25] = "LOS"; volatile unsigned int lowLatencyInState; volatile unsigned int wwvbInState; 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 highFor = 0; volatile unsigned int lowFor = 0; volatile unsigned int lastBitHighMS = 0; volatile unsigned int timeToTick = 0; volatile unsigned int frameSamples = 0; volatile unsigned int lossOfSignal = 1; volatile unsigned int displayUpdateRequired = 1; volatile unsigned int waitingForSecond = 0; volatile unsigned int frameStart = 0; volatile unsigned int frameStartTime = 0; volatile unsigned int frameCounter = 0; volatile unsigned int bitReadyForRead = 0; volatile unsigned int lastBitReceived = 0; volatile unsigned int millisSinceBoot = 0; volatile unsigned int ppsActivationTime = 0; volatile unsigned int millisSinceSignalStateChange = 0; volatile unsigned int minuteSync = 0; void ICACHE_RAM_ATTR TimerHandler(); void ICACHE_RAM_ATTR MilliEdge(); void ICACHE_RAM_ATTR SecondEdge(); #include "ESP8266TimerInterrupt.h" ESP8266Timer ITimer; void setup() { Serial.begin(SERIAL_RATE); while (!Serial); delay(200); Serial.print("\n\n\n"); Serial.println("************************************************"); Serial.println("************************************************"); Serial.println("*** ESP8266 Ready."); Serial.println("************************************************"); Serial.println("************************************************"); Serial.print("\n\n\n"); Serial.print(F("*** Starting WWVB on ")); Serial.println(ARDUINO_BOARD); Serial.println(ESP8266_TIMER_INTERRUPT_VERSION); Serial.print(F("*** CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); pinMode(LED_BUILTIN, OUTPUT); pinMode(PPS_OUTPUT_PIN, OUTPUT); pinMode(DEBUG1_OUTPUT_PIN, OUTPUT); pinMode(WWV_SIGNAL_PIN, INPUT); // 100us timer: if (ITimer.attachInterruptInterval(100, TimerHandler)) { Serial.print(F("Starting ITimer OK, millis() = ")); Serial.println(millis()); } else { Serial.println( F("Can't set ITimer. Select another freq. or timer") ); } lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("booting"); } void TimerHandler() { clockCounter++; // ***************************************************************** // ***************************************************************** // LOW LATENCY HACK to respond in 100us to a falling start-of-second // edge respond really fast to a falling edge if in the waitingForSecond // state //lowLatencyInState = digitalRead(WWV_SIGNAL_PIN); wwvbInState = digitalRead(WWV_SIGNAL_PIN); // ***************************************************************** // ***************************************************************** if(clockCounter > CLOCKS_PER_MS) { clockCounter -= CLOCKS_PER_MS; MilliEdge(); } } void SecondEdge() { secondCounter++; displayUpdateRequired = 1; } void MilliEdge() { milliCounter++; millisSinceBoot++; wwvbInState = digitalRead(WWV_SIGNAL_PIN); if(waitingForSecond && !wwvbInState) { // TICK! falling edge, beginning of a new frame and second // FIXME reenable pps //digitalWrite(PPS_OUTPUT_PIN, 1); waitingForSecond = 0; timeToTick = 1; lastBitHighMS = highFor; bitReadyForRead = 1; return; } if(milliCounter > 1000) { milliCounter -= 1000; SecondEdge(); } if(wwvbInState != lastWWVBInState) { // any edge indicates we have some signal lossOfSignal = 0; } if(wwvbInState && (wwvbInState != lastWWVBInState)) { // we are on the rising edge, last low was 1ms ago highFor = 0; } if(!wwvbInState && (wwvbInState != lastWWVBInState)) { // we are on the falling edge, last high was 1ms ago lowFor = 0; } if(wwvbInState == lastWWVBInState) { if(wwvbInState) { highFor++; } else { lowFor++; } } if(wwvbInState && (highFor > 80) && !lossOfSignal) { // if we are high for at least 80ms but no LOS // main screen turn on // this enables the falling edge low latency detector // that happens on a tick waitingForSecond = 1; } if(wwvbInState && (highFor > 2000) && !lossOfSignal) { // we have received nothing for 2 seconds, loss of signal: lossOfSignal = 1; waitingForSecond = 1; frameCounter = 0; minuteSync = 0; } lastWWVBInState = wwvbInState; // copy/save for next loop } char pb[255]; void loop() { digitalWrite(LED_BUILTIN, !wwvbInState); if(timeToTick) { timeToTick = 0; TickSecond(); } if(bitReadyForRead) { bitReadyForRead = 0; readBit(); } PPSLowIfRequired(); if (displayUpdateRequired) { updateDisplay(); displayUpdateRequired = 0; } } void SetPPSHigh() { digitalWrite(PPS_OUTPUT_PIN, 1); } void SetPPSLow() { digitalWrite(PPS_OUTPUT_PIN, 0); } void SendPPS() { ppsActivationTime = millisSinceBoot; SetPPSHigh(); } void PPSLowIfRequired() { if ((millisSinceBoot - ppsActivationTime) > PPS_PULSEWIDTH_MS) { SetPPSLow(); } } void TickSecond() { char buf[255]; sprintf(buf, "*** TICK(%d): WWVB going low after %d ms high (EDGE)\n", frameCounter, lastBitHighMS); Serial.print(buf); SendPPS(); } void readBit() { //char buf[255]; //sprintf(buf, "*** carrier was high for %sms befor \n", lastBitHighMS); //Serial.print(buf); displayUpdateRequired++; registerBit(convertBit(lastBitHighMS)); } int convertBit(unsigned int ms) { char buf[255]; char bitbuf[20]; int output = 0; output = ZEROBIT; sprintf(bitbuf, "ZERO"); if (ms < 800) { output = ONEBIT; sprintf(bitbuf, "ONE"); } if (ms < 400) { output = MARKBIT; sprintf(bitbuf, "MARK"); } sprintf(buf, "frame prevHighMs=%s, bit=%s\n", ms, 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) { char buf[255]; sprintf(buf, "ERROR: %d bit incorrect for framing, loss of sync!\n", errorFrame); Serial.print(buf); minuteSync = 0; displayUpdateRequired++; } void sanityCheckFrame(int doot) { 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); } } void updateDisplay() { //Serial.print("updateDisplay()\n"); if (lossOfSignal) { sprintf(statusString, "LOS"); } else { 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", secondCounter); lcd.print(msg); lcd.setCursor(0, 1); sprintf(msg, "s:%s", statusString); lcd.print(msg); if (minuteSync) { lcd.setCursor(10, 0); lcd.print(d); } if(DEBUG) { if(secondCounter % 10 == 0) { serialDebug(); } } } void fastSerialDebug() { char buf[255]; sprintf(buf,"stateStableMillis=%d\n",stateStableMillis); Serial.print(buf); } void serialDebug() { char buf[255]; sprintf(buf,"lossOfSignal=%d\n",lossOfSignal); Serial.print(buf); sprintf(buf,"waitingForSecond=%d\n",waitingForSecond); Serial.print(buf); sprintf(buf,"millisSinceBoot=%d\n",millisSinceBoot); Serial.print(buf); sprintf(buf,"frameStartAge=%d\n",millisSinceBoot-frameStartTime); Serial.print(buf); sprintf(buf,"secondCounter=%d\n",secondCounter); Serial.print(buf); sprintf(buf,"timeToTick=%d\n",timeToTick); Serial.print(buf); sprintf(buf,"highFor=%d\n",highFor); Serial.print(buf); sprintf(buf,"lowFor=%d\n",lowFor); Serial.print(buf); }