#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 // we sample once per ms: #define NUM_SAMPLES_PER_FRAME 700 #define CLOCKS_PER_MS 10 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 timeToTick = 0; volatile unsigned int oldLastLowMillis = 0; volatile unsigned int lastLowMillis = 0; volatile unsigned int oldLastHighMillis = 0; volatile unsigned int lastHighMillis = 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(); #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.print(F("\nStarting 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")); } //attachInterrupt(digitalPinToInterrupt(LOSC_INPUT_PIN), OSCEdge, RISING); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("booting"); ppsActivationTime = millis(); } void TimerHandler() { //Serial.print(F("in timer OK, millis() = ")); Serial.println(millis()); clockCounter++; if(clockCounter > CLOCKS_PER_MS) { clockCounter -= CLOCKS_PER_MS; // ***************************************************************************** // LOW LATENCY HACK to respond in 100us 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 && (wwvbInState != lastWWVBInState)) { // we are on the rising edge, last low was 1ms ago oldLastLowMillis = lastLowMillis; lastLowMillis = millisSinceBoot - 1; } if(!wwvbInState && (wwvbInState != lastWWVBInState)) { // we are on the falling edge, last high was 1ms ago oldLastHighMillis = lastHighMillis; lastHighMillis = millisSinceBoot - 1; } if(wwvbInState != lastWWVBInState) { // we are on an edge stateStableMillis = 0; } else { // nothing happening stateStableMillis++; } if(wwvbInState && (stateStableMillis > 180) && (stateStableMillis < 2000)) { // if we are high but for less than 2s // main screen turn on lossOfSignal = 0; frameSearch = 1; } 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(!frameStart && frameSearch && wwvbInState) { // if we have been high for 180ms (frameSearch) we are ready to start a new frame on the mark frameHigh = 0; frameLow = 0; frameReadyToStart = 1; digitalWrite(DEBUG1_OUTPUT_PIN, 0); return; } // frameReadyToStart -> frameStart (and PPS) happens in a 100us // ISR above. if (frameStart && (frameSamples < NUM_SAMPLES_PER_FRAME)) { frameSearch = 0; //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]; void loop() { digitalWrite(LED_BUILTIN, !wwvbInState); if(timeToTick) { timeToTick = 0; TickSecond(); } yield(); if(frameReadyForRead) { frameReadyForRead = 0; processFrame(); } yield(); PPSLowIfRequired(); yield(); if (displayUpdateRequired) { updateDisplay(); displayUpdateRequired = 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(); } } void TickSecond() { char buf[255]; sprintf(buf, "*** TICK(%d): WWVB going low after %d ms high (EDGE)\n", frameCounter, millisSinceBoot - lastLowMillis); 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 / ( (float)frameHighReadOut + (float)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]; /* 20% - marker 50% - one bit 80% - zero bit our cutoff points will be 50% and 80% */ char bitbuf[20]; int output = 0; output = ZEROBIT; sprintf(bitbuf, "ZERO"); if (rawVal < 800) { output = ONEBIT; sprintf(bitbuf, "ONE"); } if (rawVal < 680) { output = MARKBIT; sprintf(bitbuf, "MARK"); } 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) { 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"); } 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", 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,"frameReadyToStart=%d\n",frameReadyToStart); Serial.print(buf); sprintf(buf,"beginFrameSearch=%d\n",beginFrameSearch); Serial.print(buf); sprintf(buf,"frameSearch=%d\n",frameSearch); Serial.print(buf); sprintf(buf,"frameStart=%d\n",frameStart); Serial.print(buf); sprintf(buf,"frameStartTime=%d\n",frameStartTime); 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,"frameHigh=%d\n",frameHigh); Serial.print(buf); sprintf(buf,"frameLow=%d\n",frameLow); Serial.print(buf); }