#include LiquidCrystal_I2C lcd(0x27, 16, 2); #define SERIAL_RATE 115200 #define WWV_SIGNAL_PIN 14 #define PPS_OUTPUT_PIN 16 #define LOSC_INPUT_PIN 0 #define DEBUG1_OUTPUT_PIN 15 #define ONEBIT 1 #define ZEROBIT 2 #define MARKBIT 3 #define NUM_SAMPLES_PER_FRAME 32000 #define CLOCKS_PER_MS 32 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 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() { pinMode(LED_BUILTIN, OUTPUT); pinMode(PPS_OUTPUT_PIN, OUTPUT); pinMode(DEBUG1_OUTPUT_PIN, OUTPUT); pinMode(WWV_SIGNAL_PIN, INPUT); 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 void loop() { digitalWrite(LED_BUILTIN, !wwvbInState); if(timeToTick) { Serial.println("*** TICK in loop()"); timeToTick = 0; TickSecond(); } yield(); if(frameReadyForRead) { Serial.println("*** processFrame() in loop()"); frameReadyForRead = 0; processFrame(); } yield(); PPSLowIfRequired(); yield(); if (displayUpdateRequired) { Serial.println("*** updateDisplay() in loop()"); sprintf(pb,"*** secondCounter=%d\n", secondCounter); Serial.print(pb); 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\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]; /* 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 > 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) { 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 (frameStart) { // don't do anything if we are currently sampling. return; } 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); } }