The calibration loop now uses the average of an array of ten samples.
authorArt Cancro <ajc@citadel.org>
Wed, 22 Jun 2022 22:43:56 +0000 (18:43 -0400)
committerArt Cancro <ajc@citadel.org>
Wed, 22 Jun 2022 22:43:56 +0000 (18:43 -0400)
the_perfect_clock.ino

index 48050670dffc522ee099456ce8bb1f0316e419be..f120ad15fe7b3944adec79193e4cb246882ce93d 100644 (file)
@@ -1,6 +1,6 @@
 // "The Perfect Clock" 
 
-// Copyright (C) 2019-2020 by Art Cancro <ajc@citadel.org>
+// Copyright (C) 2019-2022 by Art Cancro <ajc@citadel.org>
 
 // 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
 // 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 <Wire.h>                              // I2C library to drive the HT16K33 display
+#include <Wire.h>              // 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;
 }