--- /dev/null
+#include <Adafruit_LEDBackpack.h>\r
+\r
+#include <Adafruit_GFX.h>\r
+#include <Adafruit_SPITFT.h>\r
+#include <Adafruit_SPITFT_Macros.h>\r
+#include <gfxfont.h>\r
+\r
+/*************************************************** \r
+ This is a library for our I2C LED Backpacks\r
+\r
+ Designed specifically to work with the Adafruit LED 7-Segment backpacks \r
+ ----> http://www.adafruit.com/products/881\r
+ ----> http://www.adafruit.com/products/880\r
+ ----> http://www.adafruit.com/products/879\r
+ ----> http://www.adafruit.com/products/878\r
+\r
+ These displays use I2C to communicate, 2 pins are required to \r
+ interface. There are multiple selectable I2C addresses. For backpacks\r
+ with 2 Address Select pins: 0x70, 0x71, 0x72 or 0x73. For backpacks\r
+ with 3 Address Select pins: 0x70 thru 0x77\r
+\r
+ Adafruit invests time and resources providing this open source code, \r
+ please support Adafruit and open-source hardware by purchasing \r
+ products from Adafruit!\r
+\r
+ Written by Limor Fried/Ladyada for Adafruit Industries. \r
+ BSD license, all text above must be included in any redistribution\r
+ ****************************************************/\r
+\r
+//#include <Wire.h> // Enable this line if using Arduino Uno, Mega, etc.\r
+#include <Adafruit_GFX.h>\r
+#include "Adafruit_LEDBackpack.h"\r
+\r
+Adafruit_7segment matrix = Adafruit_7segment();\r
+\r
+void setup() {\r
+#ifndef __AVR_ATtiny85__\r
+ Serial.begin(9600);\r
+ Serial.println("7 Segment Backpack Test");\r
+#endif\r
+ matrix.begin(0x70);\r
+}\r
+\r
+void loop() {\r
+ // try to print a number thats too long\r
+ matrix.print(10000, DEC);\r
+ matrix.writeDisplay();\r
+ delay(500);\r
+\r
+ // print a hex number\r
+ matrix.print(0xBEEF, HEX);\r
+ matrix.writeDisplay();\r
+ delay(500);\r
+\r
+ // print a floating point \r
+ matrix.print(12.34);\r
+ matrix.writeDisplay();\r
+ delay(500);\r
+ \r
+ // print with print/println\r
+ for (uint16_t counter = 0; counter < 9999; counter++) {\r
+ matrix.println(counter);\r
+ matrix.writeDisplay();\r
+ delay(10);\r
+ }\r
+\r
+ // method #2 - draw each digit\r
+ uint16_t blinkcounter = 0;\r
+ boolean drawDots = false;\r
+ for (uint16_t counter = 0; counter < 9999; counter ++) {\r
+ matrix.writeDigitNum(0, (counter / 1000), drawDots);\r
+ matrix.writeDigitNum(1, (counter / 100) % 10, drawDots);\r
+ matrix.drawColon(drawDots);\r
+ matrix.writeDigitNum(3, (counter / 10) % 10, drawDots);\r
+ matrix.writeDigitNum(4, counter % 10, drawDots);\r
+ \r
+ blinkcounter+=50;\r
+ if (blinkcounter < 500) {\r
+ drawDots = false;\r
+ } else if (blinkcounter < 1000) {\r
+ drawDots = true;\r
+ } else {\r
+ blinkcounter = 0;\r
+ }\r
+ matrix.writeDisplay();\r
+ delay(10);\r
+ }\r
+}\r
--- /dev/null
+// "The Perfect Clock"
+
+// Copyright (C) 2019 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
+// 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 <Wire.h> // 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();
+}