406 lines
9.8 KiB
C++
406 lines
9.8 KiB
C++
#include <LiquidCrystal_I2C.h>
|
|
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);
|
|
}
|
|
|
|
}
|