X-Git-Url: https://code.citadel.org/?p=the_perfect_clock.git;a=blobdiff_plain;f=old_version_with_seconds%2Fthe_perfect_clock-oldversionwithseconds.ino;fp=old_version_with_seconds%2Fthe_perfect_clock-oldversionwithseconds.ino;h=3e1c7bccefb1355b23cd3a259cebe6352e15b4e6;hp=0000000000000000000000000000000000000000;hb=467e2f9c96396bf20a756a38e7be988d9bf30edd;hpb=addce46764744c46daae043cd598b8228aa9e060 diff --git a/old_version_with_seconds/the_perfect_clock-oldversionwithseconds.ino b/old_version_with_seconds/the_perfect_clock-oldversionwithseconds.ino new file mode 100644 index 0000000..3e1c7bc --- /dev/null +++ b/old_version_with_seconds/the_perfect_clock-oldversionwithseconds.ino @@ -0,0 +1,271 @@ +// "The Perfect Clock" + +// Copyright (C) 2019 by Art Cancro + +// My perfect clock has no buttons and cannot be set manually. This version uses a WWVB receiver module +// attached to pin D9 of the Arduino, and sets the clock any time it receives a complete frame. The clock +// is kept without an RTC, simply using the millis() timer. When time is set, it is displayed on +// a 7-segment array connected using an HT16K33 decoder/driver (yes, an Adafruit backpack). + +// The clock is hard coded to use US Eastern time with DST in effect whenever WWVB is announcing it. + +const uint8_t wwvb = 9; // pin on which WWVB signal will be received +const uint8_t greenled = 2; // Attach a green LED to this pin +const uint8_t yellowled = 3; // Attach a yellow LED to this pin +const uint8_t redled = 4; // Attach a red LED to this pin +const uint8_t addr = 0x70; // I2C address of HT16K33 (using Adafruit backpack with digits on 0,1,3,4; dots on 2) + +#define MILLISECONDS_PER_SECOND 1002 // Nominally 1000, but the timer on my Nano runs fast and we don't have an RTC + +// This is a simple BCD-to-7-segment font. It includes 0x0A through 0x0F even though they're not needed for a time clock. +const uint8_t sevensegfont[] = { 63, 6, 91, 79, 102, 109, 125, 7, 127, 111, 119, 124, 57, 94, 121, 113 }; +const uint8_t firstcolfont[] = { 0, 6, 91 }; // this version of the font is for the first position + +#include // I2C library + +int hour = 0; +int minute = 0; +int second = 0; +unsigned long millisecond = 0; +unsigned long previous_millis = 0; +unsigned long last_sync = -86398000; +uint16_t displayBuffer[8]; // Digit buffer for HT16K33 +int previous_minute = 61; // What the minute was previously; we use this to detect whether an update is needed +int this_pulse = 0; // Value of the current pulse received +int previous_pulse = 0; // Value of the previous pulse received (two "mark" bits == new frame) +int start_of_pulse = 0; // The value of the millis() timer when the current pulse began +uint8_t framebuf[60]; // We store the entire 60-bit frame here +uint8_t framesync = 0; // Nonzero if we've received all good pulses since the start of the frame +int position_in_frame = 0; // Where we are in the frame (this happens to also be the second of the minute) +int previous_signal = 0; // "high" or "low" received on the previous cycle (so we can do edge detection) +int time_is_set = 0; // nonzero when time has been set at least once + +void setup() +{ + int i; + + pinMode(LED_BUILTIN, OUTPUT); // The built-in LED will display the raw WWVB signal pulses + pinMode(greenled, OUTPUT); + pinMode(yellowled, OUTPUT); + pinMode(redled, OUTPUT); + pinMode(wwvb, INPUT); // Input pin for WWVB receiver signal + + Wire.begin(); // Initialize I2C + + Wire.beginTransmission(addr); + Wire.write(0x21); // turn on oscillator + Wire.endTransmission(); + + Wire.beginTransmission(addr); + Wire.write(0xE1); // brightness (max is 15) + Wire.endTransmission(); + + Wire.beginTransmission(addr); + Wire.write(0x81); // no blinking or blanking + Wire.endTransmission(); + + displayBuffer[0] = 0; + displayBuffer[1] = 0; + displayBuffer[2] = 16; + displayBuffer[3] = 0; + displayBuffer[4] = 0; + show(); +} + + +// Note: only write to the display when the readout needs to be updated. +// Speaking I2C on every loop iteration jams the WWVB receiver. +void loop() +{ + unsigned long m = millis(); + digitalWrite(redled, (m%1000) ? LOW : HIGH); + if (m != previous_millis) { + millisecond += (m - previous_millis); + if (millisecond >= MILLISECONDS_PER_SECOND) { + millisecond -= MILLISECONDS_PER_SECOND; + ++second; + if (second > 59) { + second = 0; + ++minute; + if (minute > 59) { + minute = 0; + ++hour; + if (hour > 23) { + hour = 0; + } + } + } + } + } + previous_millis = m; + + int pulse_length; + int signal = digitalRead(wwvb); // is the input high or low right now? + digitalWrite(LED_BUILTIN, signal); // use the onboard LED to show the signal + + if (signal && (!previous_signal)) { + start_of_pulse = millis(); + } + else if ((!signal) && (previous_signal)) { + pulse_length = millis() - start_of_pulse; + + if (pulse_length > 150 && pulse_length < 250) { // "0" bit ~= 200 ms (represented as "0") + this_pulse = 0; + } else if (pulse_length > 450 && pulse_length < 550) { // "1" bit ~= 500 ms (represented as "1") + this_pulse = 1; + } else if (pulse_length > 750 && pulse_length < 850) { // marker bit ~= 800 ms (represented as "2") + this_pulse = 2; + } else { + this_pulse = 15; // bad pulse (represented as "15") + framesync = 0; // throw the whole frame away + } + + // BEGIN -- THINGS TO DO AT THE END OF A PULSE + + if ((this_pulse == 2) && (previous_pulse == 2)) { // start of a new frame! + + if (framesync == 1) { + set_the_time(); // We have a whole good frame. Set the clock! + } + + framesync = 1; + position_in_frame = 0; + } + + if (framesync) { // yellow LED = we currently have frame sync + analogWrite(yellowled, 10); // (we run it at a low intensity) + } + else { + digitalWrite(yellowled, LOW); + } + + if ((framesync) && (position_in_frame < 60)) { + framebuf[position_in_frame++] = this_pulse; + } + + previous_pulse = this_pulse; + + // END -- THINGS TO DO AT THE END OF A PULSE + } + + previous_signal = signal; + + // Update the display only if it's a new minute. + + if (time_is_set && (minute != previous_minute)) { + previous_minute = minute; + int h12 = (hour % 12) ; + if (h12 == 0) h12 = 12; + displayBuffer[0] = firstcolfont[h12 / 10]; + displayBuffer[1] = sevensegfont[h12 % 10]; + displayBuffer[2] = (hour<12) ? 0x06 : 0x0a; // AM or PM dot , colon always on + displayBuffer[3] = sevensegfont[minute / 10]; + displayBuffer[4] = sevensegfont[minute % 10]; + show(); + } + + if ((m - last_sync) < 86400000) { // green LED = got a good sync in the last 24 hours + digitalWrite(greenled, HIGH); + } + else { + digitalWrite(greenled, LOW); + } +} + + +// Write the display buffer to the display +void show() +{ + Wire.beginTransmission(addr); + Wire.write(0x00); // start at address 0x0 + for (int i = 0; i < 5; i++) { + Wire.write(displayBuffer[i] & 0xFF); + Wire.write(displayBuffer[i] >> 8); + } + Wire.endTransmission(); +} + + +// Set the software clock to the WWVB time currently in the buffer +void set_the_time() +{ + int i, newhour, newminute, dst; + + // These six positions MUST contain marker bits. + // If any of them do not, we are looking at a corrupt frame. + int markers[] = { 0, 9, 19, 39, 49, 59 }; + for (i=0; i<6; ++i) { + if (framebuf[markers[i]] != 2) { + return; + } + } + + newhour = (framebuf[12] ? 20 : 0); + newhour += (framebuf[13] ? 10 : 0); + newhour += (framebuf[15] ? 8 : 0); + newhour += (framebuf[16] ? 4 : 0); + newhour += (framebuf[17] ? 2 : 0); + newhour += (framebuf[18] ? 1 : 0); + if ((newhour < 0) || (newhour > 23)) { + return; // reject impossible hours + } + + newminute = (framebuf[1] ? 40 : 0); + newminute += (framebuf[2] ? 20 : 0); + newminute += (framebuf[3] ? 10 : 0); + newminute += (framebuf[5] ? 8 : 0); + newminute += (framebuf[6] ? 4 : 0); + newminute += (framebuf[7] ? 2 : 0); + newminute += (framebuf[8] ? 1 : 0); + if ((newminute < 0) || (newminute > 59)) { + return; // reject impossible minutes + } + + // advance 60 seconds because WWVB gives the *previous* minute + newminute += 1; + if (newminute >= 60) { + newminute = newminute % 60; + newhour += 1; + } + + // US Eastern time (FIXME make this adjustable) + newhour -= 5; + + // DST (FIXME make this adjustable) + dst = (framebuf[57] ? 2 : 0); + dst += (framebuf[58] ? 1 : 0); + switch(dst) { + case 0: // dst not in effect (make no adjustments) + break; + case 2: // dst begins today (adjust if local hour > 2) + if (newhour > 2) { + ++newhour; + } + break; + case 3: // dst is in effect (always adjust) + ++newhour; + break; + case 1: // dst ends today (adjust if local hour < 2) + if (hour < 2) { + ++newhour; + } + break; + } + + // If we went back to the previous day, adjust so that hour > 0 + if (newhour < 0) { + newhour += 24; + } + + // Set the software clock: + // * We have decoded the hour and minute from the signal + // * This function always gets called *after* the first pulse at :00, so we set the second to :00 and millisecond to 800 + hour = newhour; + minute = newminute; + second = 0; + millisecond = 800; + time_is_set = 1; + + // Let's remember the last time we synced the clock + last_sync = millis(); +}