From 12e279aa4623865444f03d5d4626767ce8989648 Mon Sep 17 00:00:00 2001 From: Art Cancro Date: Wed, 22 Jun 2022 18:43:56 -0400 Subject: [PATCH] The calibration loop now uses the average of an array of ten samples. --- the_perfect_clock.ino | 226 ++++++++++++++++++++++-------------------- 1 file changed, 118 insertions(+), 108 deletions(-) diff --git a/the_perfect_clock.ino b/the_perfect_clock.ino index 4805067..f120ad1 100644 --- a/the_perfect_clock.ino +++ b/the_perfect_clock.ino @@ -1,6 +1,6 @@ // "The Perfect Clock" -// Copyright (C) 2019-2020 by Art Cancro +// Copyright (C) 2019-2022 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 @@ -19,12 +19,13 @@ // 5. Under no circumstances may you use this program and also maintain a Facebook account. // Aside from these conditions, the program is made available to you under the terms of the GNU General Public License. -const uint8_t wwvb = 9; // pin on which WWVB signal will be received -const uint8_t greenled = 2; // An LED attached to this pin will illuminate if the time has been set within the last 24 hours -const uint8_t yellowled = 3; // An LED attached to this pin will illuminate if we are currently receiving a clean frame -const uint8_t redled = 4; // An LED attached to this pin will pulse for 1 ms every second -const uint8_t photocell = A0; // Attach a photocell with a 10K voltage divider 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) +const uint8_t wwvb = 9; // pin on which WWVB signal will be received +const uint8_t greenled = 2; // An LED attached to this pin will illuminate if the time has been set within the last 24 hours +const uint8_t yellowled = 3; // An LED attached to this pin will illuminate if we are currently receiving a clean frame +const uint8_t redled = 4; // An LED attached to this pin will pulse for 1 ms every second +const uint8_t boardled = LED_BUILTIN; +const uint8_t photocell = A0; // Attach a photocell with a 10K voltage divider 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) long millis_per_minute = 60000; // Nominally 60000; adjust if your board runs fast or slow @@ -32,47 +33,46 @@ long millis_per_minute = 60000; // Nominally 60000; adjust if your board runs fa 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 to drive the HT16K33 display +#include // I2C library to drive the HT16K33 display int hour = 0; int minute = 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 (1 bit per second) -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() -{ +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 (1 bit per second) +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); // This LED will illuminate if the time has been set within the last 24 hours - pinMode(yellowled, OUTPUT); // This LED will illuminate if we are currently receiving a clean frame - pinMode(redled, OUTPUT); // This LED pulses for 1 ms every second - pinMode(wwvb, INPUT); // Input pin for WWVB receiver signal - pinMode(photocell, INPUT); // Input pin for photocell + pinMode(boardled, OUTPUT); // The built-in LED will display the raw WWVB signal pulses + pinMode(greenled, OUTPUT); // This LED will illuminate if the time has been set within the last 24 hours + pinMode(yellowled, OUTPUT); // This LED will illuminate if we are currently receiving a clean frame + pinMode(redled, OUTPUT); // This LED pulses for 1 ms every second + pinMode(wwvb, INPUT); // Input pin for WWVB receiver signal + pinMode(photocell, INPUT); // Input pin for photocell - Wire.begin(); // Initialize I2C + Wire.begin(); // Initialize I2C Wire.beginTransmission(addr); - Wire.write(0x21); // turn on oscillator + Wire.write(0x21); // turn on oscillator Wire.endTransmission(); Wire.beginTransmission(addr); - Wire.write(0xE1); // brightness (max is 15) + Wire.write(0xE1); // brightness (max is 15) Wire.endTransmission(); Wire.beginTransmission(addr); - Wire.write(0x81); // no blinking or blanking + Wire.write(0x81); // no blinking or blanking Wire.endTransmission(); displayBuffer[0] = 0; @@ -86,10 +86,9 @@ void setup() // 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() -{ +void loop() { unsigned long m = millis(); - digitalWrite(redled, (m%1000) ? LOW : HIGH); + digitalWrite(redled, (m % 1000) ? LOW : HIGH); if (m != previous_millis) { millisecond += (m - previous_millis); if (millisecond >= millis_per_minute) { @@ -107,44 +106,47 @@ void loop() 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 + int signal = digitalRead(wwvb); // is the input high or low right now? + digitalWrite(boardled, signal); // use the onboard LED to show the signal - if (signal && (!previous_signal)) { // leading edge of pulse detected + if (signal && (!previous_signal)) { // leading edge of pulse detected start_of_pulse = millis(); } - else if ((!signal) && (previous_signal)) { // trailing edge of pulse detected + else if ((!signal) && (previous_signal)) { // trailing edge of pulse detected pulse_length = millis() - start_of_pulse; - if (pulse_length > 150 && pulse_length < 250) { // "0" bit ~= 200 ms (represented as "0") + if (pulse_length > 175 && pulse_length < 225) { // "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") + } + else if (pulse_length > 475 && pulse_length < 525) { // "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") + } + else if (pulse_length > 775 && pulse_length < 825) { // 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 + } + 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 ((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! + set_the_time(); // We have a whole good frame. Set the clock! + } + else if ((!framesync) && (time_is_set)) { + snap_to_zero(); // We don't have a whole frame, but we know it's :00 seconds now. } - else if ((!framesync) && (time_is_set)) { - snap_to_zero(); // We don't have a whole frame, but we know it's :00 seconds now. - } framesync = 1; position_in_frame = 0; - calibrate(); // calibrate the software timer + calibrate(); // calibrate the software timer } - if (framesync) { // yellow LED = we currently have frame sync - analogWrite(yellowled, 10); // (we run it at a low intensity) + if (framesync) { // yellow LED = we currently have frame sync + analogWrite(yellowled, 10); // (we run it at a low intensity) } else { digitalWrite(yellowled, LOW); @@ -165,17 +167,18 @@ void loop() if (time_is_set && (minute != previous_minute)) { previous_minute = minute; - int h12 = (hour % 12) ; - if (h12 == 0) h12 = 12; + 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[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 + if ((m - last_sync) < 86400000) { // green LED = got a good sync in the last 24 hours digitalWrite(greenled, HIGH); } else { @@ -185,9 +188,8 @@ void loop() // Write the display buffer to the display -void show() -{ - // display the time +void show() { + // display the time Wire.beginTransmission(addr); Wire.write(0x00); // start at address 0x0 for (int i = 0; i < 5; i++) { @@ -195,30 +197,29 @@ void show() Wire.write(displayBuffer[i] >> 8); } Wire.endTransmission(); - - // set the brightness - int light_level = analogRead(photocell) / 64; - if (light_level < 1) { - light_level = 1; - } - if (light_level > 15) { - light_level = 15; - } - Wire.beginTransmission(addr); - Wire.write(0xE0 + light_level); // set the display brightness - Wire.endTransmission(); + + // set the brightness + int light_level = analogRead(photocell) / 64; + if (light_level < 1) { + light_level = 1; + } + if (light_level > 15) { + light_level = 15; + } + Wire.beginTransmission(addr); + Wire.write(0xE0 + light_level); // set the display brightness + Wire.endTransmission(); } // Set the software clock to the WWVB time currently in the buffer -void set_the_time() -{ +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) { + for (i = 0; i < 6; ++i) { if (framebuf[markers[i]] != 2) { return; } @@ -231,7 +232,7 @@ void set_the_time() newhour += (framebuf[17] ? 2 : 0); newhour += (framebuf[18] ? 1 : 0); if ((newhour < 0) || (newhour > 23)) { - return; // reject impossible hours + return; // reject impossible hours } newminute = (framebuf[1] ? 40 : 0); @@ -242,7 +243,7 @@ void set_the_time() newminute += (framebuf[7] ? 2 : 0); newminute += (framebuf[8] ? 1 : 0); if ((newminute < 0) || (newminute > 59)) { - return; // reject impossible minutes + return; // reject impossible minutes } // advance 1 minute because WWVB gives the *previous* minute @@ -258,22 +259,22 @@ void set_the_time() // 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) + 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 1: // dst ends today (adjust if local hour < 2) - if (newhour < 2) { - ++newhour; - } - break; + } + break; + case 3: // dst is in effect (always adjust) + ++newhour; + break; + case 1: // dst ends today (adjust if local hour < 2) + if (newhour < 2) { + ++newhour; + } + break; } // If we went back to the previous day, adjust so that hour > 0 @@ -295,26 +296,35 @@ void set_the_time() // Adjust the time to :00.8 seconds at the nearest minute. -void snap_to_zero() -{ - if ((millisecond > 0) && (millisecond < 15000)) { // If the second is from :00.0 to :15.0 - millisecond = 800; // snap back to :00.8 - } - else if (millisecond > 45000) { // If the second is :45.0 or above - millisecond = millis_per_minute + 800; // snap forward to :00.8 (minute will advance automatically) - } +void snap_to_zero() { + if ((millisecond > 0) && (millisecond < 15000)) { // If the second is from :00.0 to :15.0 + millisecond = 800; // snap back to :00.8 + } + else if (millisecond > 45000) { // If the second is :45.0 or above + millisecond = millis_per_minute + 800; // snap forward to :00.8 (minute will advance automatically) + } } // By determining how many timer ticks elapsed between two minute markers, we can calibrate our software clock. // Nominally it is 60000 milliseconds, but the software clock tends to drift. -void calibrate() -{ - static unsigned long last_calib = -86398000; - unsigned long m = millis(); - unsigned long mm = m - last_calib; - if ((mm > 50000) && (mm < 70000)) { - millis_per_minute = mm; - } - last_calib = m; +// So we start with an array of all 60000 ms, and we keep ten calibrations and average them. +void calibrate() { + + static unsigned long mpm_array[10] = { 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000 }; + static int mpm = 0; // next one to update + + static unsigned long last_calib = -86398000; + unsigned long m = millis(); + unsigned long mm = m - last_calib; + if ((mm > 50000) && (mm < 70000)) { + mpm_array[mpm++] = mm; + if (mpm >= 10) { + mpm = 0; + } + millis_per_minute = (mpm_array[0] + mpm_array[1] + mpm_array[2] + mpm_array[3] + + mpm_array[4] + mpm_array[5] + mpm_array[6] + mpm_array[7] + + mpm_array[8] + mpm_array[9]) / 10; + } + last_calib = m; } -- 2.30.2