From db4934d1bc04f497e1421f20a9d3590505e80e88 Mon Sep 17 00:00:00 2001 From: hdrlux <121645096+hdrlux@users.noreply.github.com> Date: Mon, 24 Jul 2023 10:38:11 +0200 Subject: [PATCH] Add files via upload --- Font_data.h | 152 +++++++++++++++++++++++++++++++++++++++++++ GPS_RTC_Clock.cpp | 69 ++++++++++++++++++++ GPS_RTC_Clock.h | 58 +++++++++++++++++ GPS_RTC_Clock_33.ino | 96 +++++++++++++++++++++++++++ GPS_com.cpp | 119 +++++++++++++++++++++++++++++++++ GPS_com.h | 49 ++++++++++++++ ISOWeekNumber.cpp | 35 ++++++++++ ISOWeekNumber.h | 22 +++++++ LED_96x8_matrix.cpp | 146 +++++++++++++++++++++++++++++++++++++++++ LED_96x8_matrix.h | 42 ++++++++++++ LocalDateTime.cpp | 74 +++++++++++++++++++++ LocalDateTime.h | 25 +++++++ Local_names.cpp | 37 +++++++++++ Local_names.h | 20 ++++++ RTC_com.cpp | 70 ++++++++++++++++++++ RTC_com.h | 34 ++++++++++ 16 files changed, 1048 insertions(+) create mode 100644 Font_data.h create mode 100644 GPS_RTC_Clock.cpp create mode 100644 GPS_RTC_Clock.h create mode 100644 GPS_RTC_Clock_33.ino create mode 100644 GPS_com.cpp create mode 100644 GPS_com.h create mode 100644 ISOWeekNumber.cpp create mode 100644 ISOWeekNumber.h create mode 100644 LED_96x8_matrix.cpp create mode 100644 LED_96x8_matrix.h create mode 100644 LocalDateTime.cpp create mode 100644 LocalDateTime.h create mode 100644 Local_names.cpp create mode 100644 Local_names.h create mode 100644 RTC_com.cpp create mode 100644 RTC_com.h diff --git a/Font_data.h b/Font_data.h new file mode 100644 index 0000000..41ef94c --- /dev/null +++ b/Font_data.h @@ -0,0 +1,152 @@ +/* + Font_data.h + + MIT License + Copyright (c) 2023 hdrlux + + This is the data file for custom fonts, as alternative for the sysfont defined in MD_MAX72xx_font.cpp + 'fontClock' only has chars 32..126, saving a few bytes of memory + The first number of every line is the width of the char, the number of columns + The following numbers are the columns, each byte for 8 LED on/off +*/ + +#ifndef Font_data_H +#define Font_data_H + +//fontClock is adapted sysfont for better time/date display in Dutch language +MD_MAX72XX::fontType_t fontClock[] PROGMEM = +{ + 'F', 2, 0, 0, 0, 126, 8, + 0, // 0 + 0, // 1 + 0, // 2 + 0, // 3 + 0, // 4 + 0, // 5 + 0, // 6 + 0, // 7 + 0, // 8 + 0, // 9 + 0, // 10 + 0, // 11 + 0, // 12 + 0, // 13 + 0, // 14 + 0, // 15 + 0, // 16 + 0, // 17 + 0, // 18 + 0, // 19 + 0, // 20 + 0, // 21 + 0, // 22 + 0, // 23 + 0, // 24 + 0, // 25 + 0, // 26 + 0, // 27 + 0, // 28 + 0, // 29 + 0, // 30 + 0, // 31 + 2, 0, 0, // 32 - 'Space' + 2, 0, 95, // 33 - '!' + 3, 7, 0, 7, // 34 - '"' + 5, 20, 127, 20, 127, 20, // 35 - '#' + 5, 68, 74, 255, 74, 50, // 36 - '$' + 5, 99, 19, 8, 100, 99, // 37 - '%' + 5, 54, 73, 73, 54, 72, // 38 - '&' + 1, 7, // 39 - ''' + 3, 62, 65, 65, // 40 - '(' + 3, 65, 65, 62, // 41 - ')' + //5, 8, 42, 28, 42, 8, // 42 - '*' + 3, 2, 5, 2, // 42 - '°' Degree symbol, replacing * char for easy of use' + 5, 8, 8, 62, 8, 8, // 43 - '+' + 2, 96, 224, // 44 - ',' + 2, 8, 8, // 45 - '-' + 1, 64, // 46 - '.' width 1 + 5, 96, 16, 8, 4, 3, // 47 - '/' + 5, 62, 81, 73, 69, 62, // 48 - '0' + 5, 0, 4, 2, 127, 0, // 49 - '1' - width 5 + 5, 113, 73, 73, 73, 70, // 50 - '2' + 5, 65, 73, 73, 73, 54, // 51 - '3' + 5, 15, 8, 8, 8, 127, // 52 - '4' + 5, 79, 73, 73, 73, 49, // 53 - '5' + 5, 62, 73, 73, 73, 48, // 54 - '6' + 5, 1, 1, 121, 5, 3, // 55 - european style '7' + 5, 54, 73, 73, 73, 54, // 56 - '8' + 5, 6, 73, 73, 73, 62, // 57 - '9' + 1, 36, // 58 - colon ':' - width 1 + 2, 108, 236, // 59 - ';' + 3, 8, 20, 34, // 60 - '<' + 4, 20, 20, 20, 20, // 61 - '=' + 3, 34, 20, 8, // 62 - '>' + 5, 1, 89, 9, 9, 6, // 63 - '?' + 5, 62, 65, 93, 89, 78, // 64 - '@' + 5, 126, 9, 9, 9, 126, // 65 - 'A' + 5, 127, 73, 73, 73, 54, // 66 - 'B' + 5, 62, 65, 65, 65, 65, // 67 - 'C' + 5, 127, 65, 65, 65, 62, // 68 - 'D' + 5, 127, 73, 73, 73, 65, // 69 - 'E' + 5, 127, 9, 9, 9, 1, // 70 - 'F' + 5, 62, 65, 65, 73, 121, // 71 - 'G' + 5, 127, 8, 8, 8, 127, // 72 - 'H' + 3, 65, 127, 65, // 73 - 'I' + 5, 48, 65, 65, 65, 63, // 74 - 'J' + 5, 127, 8, 20, 34, 65, // 75 - 'K' + 5, 127, 64, 64, 64, 64, // 76 - 'L' + 5, 127, 2, 12, 2, 127, // 77 - 'M' + 5, 127, 4, 8, 16, 127, // 78 - 'N' + 5, 62, 65, 65, 65, 62, // 79 - 'O' + 5, 127, 9, 9, 9, 6, // 80 - 'P' + 5, 62, 65, 65, 97, 126, // 81 - 'Q' + 5, 127, 9, 25, 41, 70, // 82 - 'R' + 5, 70, 73, 73, 73, 49, // 83 - 'S' + 5, 1, 1, 127, 1, 1, // 84 - 'T' + 5, 63, 64, 64, 64, 63, // 85 - 'U' + 5, 31, 32, 64, 32, 31, // 86 - 'V' + 5, 63, 64, 56, 64, 63, // 87 - 'W' + 5, 99, 20, 8, 20, 99, // 88 - 'X' + 5, 3, 4, 120, 4, 3, // 89 - 'Y' + 5, 97, 81, 73, 69, 67, // 90 - 'Z' + 3, 127, 65, 65, // 91 - '[' + 5, 3, 4, 8, 16, 96, // 92 - '\' + 3, 65, 65, 127, // 93 - ']' + 5, 4, 2, 1, 2, 4, // 94 - '^' + 4, 128, 128, 128, 128, // 95 - '_' + 3, 1, 2, 4, // 96 - '`' + 4, 56, 68, 68, 124, // 97 - 'a' + 4, 127, 68, 68, 56, // 98 - 'b' + 4, 56, 68, 68, 68, // 99 - 'c' + 4, 56, 68, 68, 127, // 100 - 'd' + 4, 56, 84, 84, 88, // 101 - 'e' + 4, 4, 126, 5, 1, // 102 - 'f' + 4, 24, 164, 164, 124, // 103 - 'g' + 4, 127, 4, 4, 120, // 104 - 'h' + 3, 0, 125, 0, // 105 - 'i' width 3 + 3, 132, 133, 124, // 106 - 'j' + 4, 127, 16, 40, 68, // 107 - 'k' + 3, 127, 0, 0, // 108 - 'l' width 3 + 5, 124, 4, 120, 4, 120, // 109 - 'm' + 4, 124, 4, 4, 120, // 110 - 'n' + 4, 56, 68, 68, 56, // 111 - 'o' + 4, 252, 36, 36, 24, // 112 - 'p' + 4, 24, 36, 36, 252, // 113 - 'q' + 4, 124, 4, 4, 8, // 114 - 'r' + 4, 88, 84, 84, 52, // 115 - 's' + 4, 4, 127, 68, 0, // 116 - 't' width 4 + 4, 60, 64, 64, 124, // 117 - 'u' + 4, 28, 32, 64, 124, // 118 - 'v' + 5, 60, 64, 48, 64, 60, // 119 - 'w' + 4, 108, 16, 16, 108, // 120 - 'x' + 5, 125, 0, 132, 133, 124, // 121 - 'ij' dutch 'y' + 4, 100, 84, 84, 76, // 122 - 'z' + 4, 8, 54, 65, 65, // 123 - '{' + 1, 127, // 124 - '|' + 4, 65, 65, 54, 8, // 125 - '}' + 4, 2, 1, 2, 1, // 126 - '~' +}; + +// option: add multiple fonts definitions + +#endif // Font_data_H \ No newline at end of file diff --git a/GPS_RTC_Clock.cpp b/GPS_RTC_Clock.cpp new file mode 100644 index 0000000..180ee9c --- /dev/null +++ b/GPS_RTC_Clock.cpp @@ -0,0 +1,69 @@ +/* + GPS_RTC_Clock.cpp + + MIT License + Copyright (c) 2023 hdrlux +*/ + +#include // for all the 'standard' Arduino stuff +#include "GPS_RTC_Clock.h" +#include "RTC_com.h" +#include "GPS_com.h" +#include "LocalDateTime.h" + +bool NewSec = false; +bool NewMin = false; +bool NewHour = false; +time_t Loc_t = 0; +byte last_min = -1; +byte last_5min = -1; +byte last_hour = -1; +bool ResetMCU = true; + +void GPS_RTC_Clock_setup() { + RTC_LED_setup(); + RTC_setup(); + GPS_setup(); + TZ_setup(); +} + +void GPS_RTC_Clock_loop() { + GPS_loop(); + RTC_loop(); +} + +void Sec_Flip(time_t t) { // called from RTC_com + Loc_t = TZ_Sec(t); // convert UTC to Local Time + NewSec = true; // set "Ready to print" + if (ResetMCU) { // set init time + ResetMCU = false; + last_5min = minute(Loc_t) / 5; + last_hour = hour(Loc_t); + } + if (last_min != minute(Loc_t)) { // Check new minute + MinFlip(); + } +} + +void MinFlip() { + last_min = minute(Loc_t); + if (last_5min != last_min / 5) { // Check new 5-min + Min5Flip(); + } + if (last_hour != hour(Loc_t)) { // Check new hour + HourFlip(); + } + NewMin = true; // set "Ready to print" +} + +void Min5Flip() { + last_5min = last_min / 5; + GPS_ON(); // try sync RTC-GPS +} + +void HourFlip() { // do something on the flip of the RTC hour [UTC time] + last_hour = hour(Loc_t); + NewHour = true; // set "Ready to print" +} + +//End \ No newline at end of file diff --git a/GPS_RTC_Clock.h b/GPS_RTC_Clock.h new file mode 100644 index 0000000..9855932 --- /dev/null +++ b/GPS_RTC_Clock.h @@ -0,0 +1,58 @@ +/* + GPS_RTC_Clock.h + + MIT License + Copyright (c) 2023 hdrlux + + main local Lib with Clock control, creating 'long term ±0.1 sec accurate Arduino clock' + + tested hardware: + -Arduino Nano 5V ATmega328P + -see local *.h files for other hardware + + main functions: + - Sync RTC with GPS in UTC using interrupt + - LED blinking in sync with GPS PPS, to check accuracy + - LED ON if RTC synced in last hours + - Convert to Local Date/Time + - Setting flag for printing new time to display, using interrupt +*/ + +/***** Notice ******************************************* + !! GPS & USB use the same serial port !! + you will have to disconnect the Arduino RX pin 0 from + the GPS TX pin, to upload a new sketch over USB + ->remove GPS PCB or Nano PCB from main PCB/breadboard +********************************************************/ + +#ifndef GPS_RTC_Clock_H +#define GPS_RTC_Clock_H + +#include "ISOWeekNumber.h" // pass to main ino +#include "Local_names.h" // pass to main ino +#include "RTC_com.h" + +extern bool NewSec; // pass to main ino = second ready to print +extern bool NewMin; // pass to main ino = minute ready to print +extern bool NewHour; // pass to main ino = Hour ready to print +extern time_t Loc_t; // pass to main ino = Local timestamp + +// call from main ino, GPS_RTC_Clock.h +void GPS_RTC_Clock_setup(); + +// call from main ino, GPS_RTC_Clock.h +void GPS_RTC_Clock_loop(); + +// process sec from RTC +void Sec_Flip(time_t t); + +// do something on reboot & the flip of the RTC minute [timezone independent] +void MinFlip(); + +// do something every 5 minutes +void Min5Flip(); + +// do something every hour, on the hour +void HourFlip(); + +#endif // GPS_RTC_Clock_H \ No newline at end of file diff --git a/GPS_RTC_Clock_33.ino b/GPS_RTC_Clock_33.ino new file mode 100644 index 0000000..9d60ac7 --- /dev/null +++ b/GPS_RTC_Clock_33.ino @@ -0,0 +1,96 @@ +/* GPS_RTC_Clock + + MIT License + Copyright (c) 2023 hdrlux + + https://www.instructables.com/01-Sec-Accurate-RTC-GPS-Wall-Clock-Arduino-96x8-LE/ + https://github.com/hdrlux/GPS_RTC_Clock +*/ + +#include "GPS_RTC_Clock.h" +#include "LED_96x8_matrix.h" +char TempBuf[5] = "99.9"; // demo value +char HumiBuf[3] = "99"; // demo value + + +void setup() { // the setup function runs once when you press reset or power the board + GPS_RTC_Clock_setup(); // first in setup + Matrix_setup(); // LED display + Serial.begin(9600); // = 9600, must be same as GPS for debug + Serial.println(); // flush serial + Serial.println("-Arduino Reboot-"); // debug +} + +void loop() { // the loop function runs over and over again forever + GPS_RTC_Clock_loop(); // first in loop + PrintSec(); + PrintHour(); +} + +void PrintSec() { // print time if new second + if (NewSec) { // + NewSec = false; // remove flag, do only once every sec + char startmarker = '<'; + char endmarker = '>'; + Serial.print(startmarker); // for remote display via RS485 + Serial.print(Loc_t); // for remote display via RS485 + Serial.println(endmarker); // for remote display via RS485 + + char TimeBuf[9]; // time string buffer, max n-1 char + snprintf(TimeBuf, sizeof(TimeBuf), "%.2u:%.2u:%.2u", // https://cplusplus.com/reference/cstdio/printf/ + hour(Loc_t), minute(Loc_t), second(Loc_t)); // time 24h format + Serial.print("Time: "); // debug + Serial.println(TimeBuf); // debug + Print_time_zone(TimeBuf); // print to LED Matrix + PrintMin(); + } +} + +void PrintMin() { + char TextBuf[12]; // date string buffer, max n-1 char + if (!DIP_Scroll()) { // print date only or 'scrolling' text + snprintf(TextBuf, sizeof(TextBuf), "%s %.2u %s", + dayShortStrLoc(weekday(Loc_t)), day(Loc_t), monthShortStrLoc(month(Loc_t))); // weekday, day, month + } else { // print 'scrolling' + /*** HowTo ****************************************************************** + print different info every few seconds, because of limited display space + define the amount of different text lines, must be at least 1 + define the amount of seconds each textline is displayed, must be at least 1 + define each text line, this may be duplicates of other text lines + *****************************************************************************/ + byte text_lines = 3; // amount of different text lines printed + byte text_timer = 5; // amount of seconds to next text + byte text_counter(((Loc_t / text_timer) % text_lines) + 1); // range = 1..n + if (text_counter == 1) { + snprintf(TextBuf, sizeof(TextBuf), "%.2u%s%u", + day(Loc_t), monthShortStrLoc(month(Loc_t)), year(Loc_t)); // day, month, year + } else if (text_counter == 2) { + snprintf(TextBuf, sizeof(TextBuf), "%s W%.2u", + dayShortStrLoc(weekday(Loc_t)), ISOWeekNumber(Loc_t)); // weekday, week + } else if (text_counter == 3) { + snprintf(TextBuf, sizeof(TextBuf), "%s*C %s%%", TempBuf, HumiBuf); // * = ° degrees char in fontClock + } else { + snprintf(TextBuf, sizeof(TextBuf), " Err "); + } + } + Print_date_zone(TextBuf); // print to LED Matrix + + if (NewMin) { // print date if new minute + NewMin = false; // remove flag, do only once every min + char DateBuf[21]; // date string buffer, max n-1 char + // long date version + snprintf(DateBuf, sizeof(DateBuf), "%s %.2u-%s-%u W%.2u", + dayShortStrLoc(weekday(Loc_t)), day(Loc_t), monthShortStrLoc(month(Loc_t)), year(Loc_t), ISOWeekNumber(Loc_t)); + Serial.print("Date long: "); // debug + Serial.println(DateBuf); // debug + } +} + +void PrintHour() { // do if new hour + if (NewHour) { // + NewHour = false; // remove flag, do only once every hour + Serial.println("bring out the Cuckoo ;-)"); // debug + } +} + +//End \ No newline at end of file diff --git a/GPS_com.cpp b/GPS_com.cpp new file mode 100644 index 0000000..05ae789 --- /dev/null +++ b/GPS_com.cpp @@ -0,0 +1,119 @@ +/* + GPS_com.cpp + + MIT License + Copyright (c) 2023 hdrlux +*/ + +#include // for all the 'standard' Arduino stuff +#include // https://github.com/SlashDevin/NeoGPS +#include // https://github.com/PaulStoffregen/Time +#include "GPS_com.h" +#include "RTC_com.h" // to set the RTC + +// add the static modifier to limit visibility of these variables to just this file +static byte GPS_PPS_PIN = 2; // Pin 2 = NANO INT0, GPS PPS interrupt signal on this Pin +static bool gps_on; // flag for GPS ON/OFF +static int pulse_count = 0; +static time_t gps_seconds_t = 0; // GPS time +static byte gps_sats = 0; +volatile bool GPS_sec = false; // flag for GPS-PPS + +// set interrupt flag +void gps_interrupt() { + GPS_sec = true; +} + +NMEAGPS gps; // This parses the GPS characters +gps_fix fix; // This holds on to the latest values +//#include // use only for soft-serial +#define gpsPort Serial +#define GPS_PORT_NAME "Serial" +#define DEBUG_PORT Serial + +/***** Notice ******************************************** + Edit file \Arduino\libraries\NeoGPS\src\NMEAGPS_cfg.h + UnComment line //#define NMEAGPS_PARSE_ZDA + only process the NMEA sentences GGA, RMC en ZDA +*********************************************************/ + +/****** Notice ******************************************* + Edit file \Arduino\libraries\NeoGPS\src\NeoTime.h + change both instances of the const name 'DAYS_PER_WEEK' + to something else, they conflict with TimeLib.h +*********************************************************/ + +// Compile check processing for both RMC & ZDA message [both contain date & time] +// Less dependent on GPS chip config +#ifndef NMEAGPS_PARSE_RMC +#error You must define NMEAGPS_PARSE_RMC in NMEAGPS_cfg.h! +#endif +#ifndef NMEAGPS_PARSE_ZDA +#error You must define NMEAGPS_PARSE_ZDA in NMEAGPS_cfg.h! +#endif +#ifndef NMEAGPS_PARSE_GGA +#error You must define NMEAGPS_PARSE_GGA in NMEAGPS_cfg.h! +#endif +const long POSIX_Y2K_offset = 946684800; // = 10957 days = 30 years, NeoTime (GPS) & TimeLib (RTC) library use different Epoch year, 2000 & 1970 +const long offset_28y = 883612800; // = 10227 days = 28 years, simple test for 2038 bug +const long offset_26w = 15724800; // = 182 days = 26 weeks, simple test for timezone + +void GPS_setup() { + pinMode(GPS_PPS_PIN, INPUT_PULLUP); // enable pullup on interrupt pin + attachInterrupt(digitalPinToInterrupt(GPS_PPS_PIN), gps_interrupt, RISING); // 100ms HIGH at start of second + GPS_sec = false; + gpsPort.begin(9600); // set PC to same baudrate for debug messages + GPS_ON(); +} + +void GPS_loop() { + if (GPS_sec) { // do after GPS PPS interrupt + GPS_sec = false; // clear flag + GPS_PPS(); + } + GPS_read_seconds(); // continue reading buffer +} + +void GPS_PPS() { // do something on the flip of the GPS second + if (gps_on) { // do only when needed + pulse_count += 1; + if (pulse_count > 2) { // skip first PPS-Pulses, to make shure time is from satellite + if (gps_seconds_t != 0) { // do only if value is set + // gps_seconds_t += offset_28y; // debug & testing only! + // gps_seconds_t += offset_26w; // debug & testing only! + SetRTC(gps_seconds_t); // sync RTC with GPS + GPS_OFF(); + } + } + } +} + +void GPS_read_seconds() { + while (gps.available(gpsPort)) { + fix = gps.read(); + if (fix.valid.time && fix.valid.date) { + gps_seconds_t = fix.dateTime + POSIX_Y2K_offset; // convert for different epoch year + } + if (fix.valid.satellites) { + gps_sats = fix.satellites; + } + } +} + +void GPS_ON() { + if (!gps_on) { // only if NOT on + gps_on = true; + gps_seconds_t = 0; // make shure GPS serial is alive before setting + pulse_count = 0; + Serial.println("GPS: ON"); // debug + } +} + +void GPS_OFF() { + if (gps_on) { // only if NOT off + gps_on = false; + Serial.println("GPS: OFF"); // debug + } +} + +//End \ No newline at end of file diff --git a/GPS_com.h b/GPS_com.h new file mode 100644 index 0000000..37be536 --- /dev/null +++ b/GPS_com.h @@ -0,0 +1,49 @@ +/* + GPS_com.h + + MIT License + Copyright (c) 2023 hdrlux + + all GPS time functions, tested hardware: + - Ublox Neo M8N + - External L1-antenna + - set Pin: PPS Interrupt in file GPS_com.cpp [default: 2 = NANO INT0] +*/ + +/***** Notice ******************************************** + Edit file \Arduino\libraries\NeoGPS\src\NMEAGPS_cfg.h + UnComment line //#define NMEAGPS_PARSE_ZDA + only process the NMEA sentences GGA, RMC en ZDA +*********************************************************/ + +/****** Notice ******************************************* + Edit file \Arduino\libraries\NeoGPS\src\NeoTime.h + change both instances of the const 'DAYS_PER_WEEK' + to something else, they conflict with TimeLib.h +*********************************************************/ + +#ifndef GPS_com_H +#define GPS_com_H + +// handle interrupt +void gps_interrupt(); + +// setup hardware & interrupt from PseudoPPS +void GPS_setup(); + +// main GPS loop, GPS_com +void GPS_loop(); + +// do something on the flip of the GPS second +void GPS_PPS(); + +// get time & amount of satts in FIX +void GPS_read_seconds(); + +// turn GPS on +void GPS_ON(); + +// no hardware OFF, just ignore +void GPS_OFF(); + +#endif // GPS_com_H \ No newline at end of file diff --git a/ISOWeekNumber.cpp b/ISOWeekNumber.cpp new file mode 100644 index 0000000..12d3c33 --- /dev/null +++ b/ISOWeekNumber.cpp @@ -0,0 +1,35 @@ +/* + ISOWeekNumber.cpp + + MIT License + Copyright (c) 2023 hdrlux +*/ + +#include // for all the 'standard' Arduino stuff +#include "ISOWeekNumber.h" + +byte ISOdayOfWeek(long days) { // TimeLib.h deviates from ISO8601 with Sunday = 1 + // ONLY valid for Epoch year 1970 + return (((days + 3) % DAYS_PER_WEEK) + 1); // Mon=1..Sun=7 +} + +byte ISOWeekNumber(time_t t) { // uses TimeLib.h - valid until 2099 + // weeknumber ONLY valid for Mon=1..Sun=7 datelibrary + // calculations valid for ANY Epoch year, only relative dates + long NowDay = elapsedDays(t); // days_since_epoch: now + long CloseThu = NowDay - ISOdayOfWeek(NowDay) + 4; // days_since_epoch: closest Thursday from now + int ISOYear = year(CloseThu * SECS_PER_DAY); // Year for closest Thursday, may be different in first or last week + tmElements_t tm; + tm.Hour = 0; + tm.Minute = 0; + tm.Second = 0; + tm.Day = 4; // 4th day according ISO8601 + tm.Month = 1; // Jan + tm.Year = CalendarYrToTm(ISOYear); + time_t JanFour = makeTime(tm); // date: Year-Jan-4 + long JanFourDay = elapsedDays(JanFour); // days_since_epoch: Year-Jan-4 + int ISOYearDay = NowDay - JanFourDay + ISOdayOfWeek(JanFourDay); // Day number in the ISOYear, range [1..371] + return (((ISOYearDay - 1) / 7) + 1); // Convert Day number in the ISOYear to ISOWeek number, range [1..53] +} + +//End \ No newline at end of file diff --git a/ISOWeekNumber.h b/ISOWeekNumber.h new file mode 100644 index 0000000..da46950 --- /dev/null +++ b/ISOWeekNumber.h @@ -0,0 +1,22 @@ +/* + ISOWeekNumber.h + + MIT License + Copyright (c) 2023 hdrlux + + function: + - provides weeknumber from Unix time, according ISO8601 +*/ + +#ifndef ISOWeekNumber_H +#define ISOWeekNumber_H + +#include // https://github.com/PaulStoffregen/Time valid until year 2099, no 2038 bug ! + +// ISO8601 compliant, 1 = Monday +byte ISOdayOfWeek(long days); + +// ISO8601 compliant, ISOWeek number, range [1..53] +byte ISOWeekNumber(time_t t); + +#endif // ISOWeekNumber_H \ No newline at end of file diff --git a/LED_96x8_matrix.cpp b/LED_96x8_matrix.cpp new file mode 100644 index 0000000..1ecaef9 --- /dev/null +++ b/LED_96x8_matrix.cpp @@ -0,0 +1,146 @@ +/* + LED_96x8_matrix.cpp + + MIT License + Copyright (c) 2023 hdrlux +*/ + +#include // for all the 'standard' Arduino stuff +#include // https://github.com/MajicDesigns/MD_MAX72XX +#include "LED_96x8_matrix.h" +#include "Font_data.h" +#include "RTC_com.h" // for Sync_Err + +// Define the number of devices we have in the chain and the hardware interface +#define MAX_DEVICES 12 // total amount of 8x8 LED segments +#define LEFT_DEVICES 5 // amount of 8x8 LED segments in left zone +#define HARDWARE_TYPE MD_MAX72XX::FC16_HW // check if you get strange pixel print +#define CLK_PIN 13 // or SCK [is also onboard LED on Nano] +#define DATA_PIN 11 // or MOSI +#define CS_PIN 10 // or SS +#define INIT_BRIGHT 0 // inital intensity + +#define DIP1 6 // Scroll DIP switch +#define DIP2 7 // Bright DIP switch + +// SPI hardware interface +MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); + +// Text parameters +#define CHAR_SPACING 1 // pixels between characters + +void Matrix_setup() { + mx.begin(); + mx.setFont(fontClock); // custom font in Font_data.h + delay(100); // wait init + Bright_time_zone(INIT_BRIGHT); + Bright_date_zone(INIT_BRIGHT); + pinMode(DIP1, INPUT_PULLUP); + pinMode(DIP2, INPUT_PULLUP); +} + +void Bright_date_zone(byte value) { // set range 0..15 + mx.control(0, MAX_DEVICES - LEFT_DEVICES - 1, MD_MAX72XX::INTENSITY, value); +} + +void Bright_time_zone(byte value) { // set range 0..15 + mx.control(MAX_DEVICES - LEFT_DEVICES, MAX_DEVICES - 1, MD_MAX72XX::INTENSITY, value); +} + +void DIP_Bright() { + if (digitalRead(DIP2) == LOW) { + Bright_time_zone(3); + Bright_date_zone(3); + } else { + Bright_time_zone(INIT_BRIGHT); + Bright_date_zone(INIT_BRIGHT); + } +} + +bool DIP_Scroll() { + if (digitalRead(DIP1) == LOW) { + return true; + } + return false; +} + +void Print_date_zone(char *my_text) { + char TextBuf[13]; // string buffer, max n-1 char + char err_char; + if (SyncErr) { + err_char = '!'; // sync warning on display + } else { + err_char = ' '; // space + } + snprintf(TextBuf, sizeof(TextBuf), "%c%s", err_char, my_text); // add char before text, 'between' zones + printText(0, MAX_DEVICES - LEFT_DEVICES - 1, TextBuf); // right zone, from input side +} + +void Print_time_zone(char *my_text) { // assuming max 8 chars + DIP_Bright(); + printText(MAX_DEVICES - LEFT_DEVICES, MAX_DEVICES - 1, my_text); // left zone +} + +void printText(uint8_t modStart, uint8_t modEnd, char *pMsg) +/***** HowTo ********************************************** + Simple text printing, uses minimal memory + Print the text chars to the LED matrix modules specified. + Message area is padded with blank columns after printing. +**********************************************************/ +{ + uint8_t state = 0; + uint8_t curLen; + uint16_t showLen; + uint8_t cBuf[8]; + int16_t col = ((modEnd + 1) * COL_SIZE) - 1; + + mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); + + do // finite state machine to print the characters in the space available + { + switch (state) { + case 0: // Load the next character from the font table + // if we reached end of message, reset the message pointer + if (*pMsg == '\0') { + showLen = col - (modEnd * COL_SIZE); // padding characters + state = 2; + break; + } + + // retrieve the next character form the font file + showLen = mx.getChar(*pMsg++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf); + curLen = 0; + state++; + // !! deliberately fall through to next state to start displaying + + case 1: // display the next part of the character + mx.setColumn(col--, cBuf[curLen++]); + + // done with font character, now display the space between chars + if (curLen == showLen) { + showLen = CHAR_SPACING; + state = 2; + } + break; + + case 2: // initialize state for displaying empty columns + curLen = 0; + state++; + // fall through + + case 3: // display inter-character spacing or end of message padding (blank columns) + mx.setColumn(col--, 0); + curLen++; + if (curLen == showLen) + state = 0; + break; + + default: + col = -1; // this definitely ends the do loop + } + } while (col >= (modStart * COL_SIZE)); + + mx.control(modStart, modEnd, MD_MAX72XX::UPDATE, MD_MAX72XX::ON); +} + +//End \ No newline at end of file diff --git a/LED_96x8_matrix.h b/LED_96x8_matrix.h new file mode 100644 index 0000000..278d4fb --- /dev/null +++ b/LED_96x8_matrix.h @@ -0,0 +1,42 @@ +/* + LED_96x8_matrix.h + + MIT License + Copyright (c) 2023 hdrlux + + all LED Display functions, specific hardware: + - MAX7219 with 8x8 LED segment 32x32mm + - unit FC16 = 4 segments on one PCB + - 3 units in series = chain of 12 segments, making a 96x8 display + - if NOT wired correct, ALL LED's are ON in HIGH brightness, using 9W power -> use 10W 5V/2A power source for project safety + - normal operation uses 0,5W for LED display + 0,5W for control board +*/ + +#ifndef LED_96x8_matrix_H +#define LED_96x8_matrix_H + +// main display setup +void Matrix_setup(); + +// intensity right +void Bright_date_zone(byte value); + +// intensity left +void Bright_time_zone(byte value); + +// Check DIP switch for brightness control +void DIP_Bright(); + +// Check DIP switch for text scrolling +bool DIP_Scroll(); + +// print right +void Print_date_zone(char *my_text); + +// print left +void Print_time_zone(char *my_text); + +// print chars to specific segment range, NO animations +void printText(uint8_t modStart, uint8_t modEnd, char *pMsg); + +#endif // LED_96x8_matrix_H \ No newline at end of file diff --git a/LocalDateTime.cpp b/LocalDateTime.cpp new file mode 100644 index 0000000..61ae94b --- /dev/null +++ b/LocalDateTime.cpp @@ -0,0 +1,74 @@ +/* + LocalDateTime.cpp + + MIT License + Copyright (c) 2023 hdrlux +*/ + +#include // for all the 'standard' Arduino stuff +#include // https://github.com/khoih-prog/Timezone_Generic [only this one, DO NOT install all dependencies !!] +#include "LocalDateTime.h" + +/*** HowTo **************************************************************** + TimeChangeRule myRule = {abbrev, week, dow, month, hour, offset}; + abbrev: is a character string abbreviation for the time zone, + it must be no longer than five characters. + week: is the week of the month that the rule starts. + dow: is the day of the week that the rule starts. + hour: is the hour in local time that the rule starts (0-23). + offset: is the UTC offset in minutes for the time zone being defined. + + For convenience, the following symbolic names can be used: + week: First, Second, Third, Fourth, Last + dow: Sun, Mon, Tue, Wed, Thu, Fri, Sat + month: Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec +**************************************************************************/ + +/*** Instruction ****************************************************************** + Select ONE set of TimeChangeRules below, also for non DST zones + Selection NOT possible during runtime, only at compiletime + The timezones are defined by the offset, in minutes, from UTC. + This means the start of a new minute, and the seconds, are timezone independent ! +**********************************************************************************/ + +// Eastern US time zone [Ottawa, New York, Miami, Nassau] +//TimeChangeRule mySTD = { "EST", First, Sun, Nov, 2, -300 }; // change to UTC - 5 hours +//TimeChangeRule myDST = { "EDT", Second, Sun, Mar, 2, -240 }; // change to UTC - 4 hours + +// Western European Time Zone [London = GMT/BST, Dublin = GMT/IST, Lisboa = WET/WEST, Canarias] +//TimeChangeRule mySTD = { "WET ", Last, Sun, Oct, 2, 0 }; // change to UTC +//TimeChangeRule myDST = { "WEST", Last, Sun, Mar, 1, 60 }; // change to UTC + 1 hours + +// Central European Time Zone, directive 2000/84/EC [Bruxelles, Paris, Madrid, Roma, Beograd, Warszawa, Stockholm, Berlin] +// All 3 European timezones change at the same time: 01:00 UTC +TimeChangeRule mySTD = { "CET ", Last, Sun, Oct, 3, 60 }; // change to UTC + 1 hours +TimeChangeRule myDST = { "CEST", Last, Sun, Mar, 2, 120 }; // change to UTC + 2 hours + +// Eastern European Time Zone [Helsinki, Rīga, Kyiv, Αθήνα] +//TimeChangeRule mySTD = { "EET ", Last, Sun, Oct, 4, 120 }; // change to UTC + 2 hours +//TimeChangeRule myDST = { "EEST", Last, Sun, Mar, 3, 180 }; // change to UTC + 3 hours + +// Australia Eastern Time Zone [Canberra, Brisbane, Sydney, Melbourne, Hobart] +//TimeChangeRule mySTD = { "AEST", First, Sun, Apr, 3, 600 }; // change to UTC + 10 hours +//TimeChangeRule myDST = { "AEDT", First, Sun, Oct, 2, 660 }; // change to UTC + 11 hours + +// Москва Standard Time (MSK, does not observe DST) +//TimeChangeRule mySTD = {"MSK ", Last, Sun, Oct, 1, 180}; // allways UTC + 3 hours +//TimeChangeRule myDST = {"MSK ", Last, Sun, Mar, 1, 180}; // allways UTC + 3 hours + +// Universal Time Coordinated [UTC] +//TimeChangeRule mySTD = {"UTC ", Last, Sun, Oct, 1, 0}; +//TimeChangeRule myDST = {"UTC ", Last, Sun, Mar, 1, 0}; + +Timezone* myTZ; + +void TZ_setup() { + myTZ = new Timezone(myDST, mySTD); +} + +time_t TZ_Sec(time_t t) { // convert UTC to Local-Time + TimeChangeRule* tcr; // pointer to the time change rule, use to get TZ abbrev + return (myTZ->toLocal(t, &tcr)); +} + +//End \ No newline at end of file diff --git a/LocalDateTime.h b/LocalDateTime.h new file mode 100644 index 0000000..afbb435 --- /dev/null +++ b/LocalDateTime.h @@ -0,0 +1,25 @@ +/* + LocalDateTime.h + + MIT License + Copyright (c) 2023 hdrlux + + function: + - Convert UTC timestamp to Local timestamp, including DST detection [=winter/summer time] + + usage: + - uncomment ONE timezone in file LocalDateTime.cpp +*/ + +#ifndef LocalDateTime_H +#define LocalDateTime_H + +#include // https://github.com/PaulStoffregen/Time valid until year 2099, no 2038 bug ! + +// main TZ setup, GPS_RTC_Clock +void TZ_setup(); + +// convert UTC to Local-Time +time_t TZ_Sec(time_t t); + +#endif // LocalDateTime_H \ No newline at end of file diff --git a/Local_names.cpp b/Local_names.cpp new file mode 100644 index 0000000..43f50ae --- /dev/null +++ b/Local_names.cpp @@ -0,0 +1,37 @@ +/* + Local_names.cpp + + MIT License + Copyright (c) 2023 hdrlux +*/ + +#include // for all the 'standard' Arduino stuff +#include "Local_names.h" + +#define dt_MAX_STRING_LEN 9 +#define dt_SHORT_STR_LEN 3 + +static char buffer_m[dt_MAX_STRING_LEN + 1]; +static char buffer_d[dt_MAX_STRING_LEN + 1]; + +//const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; // Translate to local Language +//const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; // Translate to local Language +const char monthShortNames_Loc[] PROGMEM = "ErrJanFebMrtAprMeiJunJulAugSepOktNovDec"; // Dutch +const char dayShortNames_Loc[] PROGMEM = "ErrZonMaaDinWoeDonVryZat"; // Dutch + +char* monthShortStrLoc(uint8_t month) { + for (int i = 0; i < dt_SHORT_STR_LEN; i++) + buffer_m[i] = pgm_read_byte(&(monthShortNames_Loc[i + (month * dt_SHORT_STR_LEN)])); + buffer_m[dt_SHORT_STR_LEN] = 0; + return buffer_m; +} + +char* dayShortStrLoc(uint8_t day) { + uint8_t index = day * dt_SHORT_STR_LEN; + for (int i = 0; i < dt_SHORT_STR_LEN; i++) + buffer_d[i] = pgm_read_byte(&(dayShortNames_Loc[index + i])); + buffer_d[dt_SHORT_STR_LEN] = 0; + return buffer_d; +} + +//End \ No newline at end of file diff --git a/Local_names.h b/Local_names.h new file mode 100644 index 0000000..5c67ec5 --- /dev/null +++ b/Local_names.h @@ -0,0 +1,20 @@ +/* + Local_names.h + + MIT License + Copyright (c) 2023 hdrlux + + Convert to short local weekday & month names + Workaround because DateStrings.cpp in TimeLib is english only +*/ + +#ifndef Local_names_H +#define Local_names_H + +// Local short month +char* monthShortStrLoc(uint8_t month); + +// Local short weekday +char* dayShortStrLoc(uint8_t day); + +#endif // Local_names_H \ No newline at end of file diff --git a/RTC_com.cpp b/RTC_com.cpp new file mode 100644 index 0000000..e75ed2d --- /dev/null +++ b/RTC_com.cpp @@ -0,0 +1,70 @@ +/* + RTC_com.cpp + + MIT License + Copyright (c) 2023 hdrlux +*/ + +#include // for all the 'standard' Arduino stuff +#include // https://github.com/JChristensen/DS3232RTC +#include // https://github.com/PaulStoffregen/Time [valid until year 2099, no 2038 bug] +#include "RTC_com.h" + +// add the static modifier to limit visibility of these variables to just this file +static int ledPin_PPS = A6; // Pin for RTC PseudoPPS LED [analog PIN used as digital PIN] +static int ledPin_Sync = A7; // Pin for valid RTC Sync +static byte RTC_1HZ_PIN = 3; // Pin 3 = NANO INT1, RTC provides a 1Hz interrupt signal on this Pin +static time_t last_sync_t = 0; // last sync +static byte sync_err_hours = 4; // set to 4 for DS3231M, may be set to 8 for DS3231SN. Warning if no GPS sync after n hours, possible 0.1 sec error. +bool SyncErr = true; + +#include +DS3232RTC myRTC; +volatile bool RTC_sec = false; // flag for PseudoPPS +static unsigned long RTCMillis; // blinking timer + +void rtc_interrupt() { + RTCMillis = millis(); + RTC_sec = true; +} + +void RTC_LED_setup() { + pinMode(ledPin_PPS, OUTPUT); + pinMode(ledPin_Sync, OUTPUT); + digitalWrite(ledPin_PPS, LOW); // LED off + digitalWrite(ledPin_Sync, LOW); // LED off +} + +void RTC_setup() { + pinMode(RTC_1HZ_PIN, INPUT_PULLUP); // enable pullup on interrupt pin (RTC SQW pin is open drain) + attachInterrupt(digitalPinToInterrupt(RTC_1HZ_PIN), rtc_interrupt, FALLING); // HIGH 500ms after start of second + myRTC.begin(); + myRTC.squareWave(DS3232RTC::SQWAVE_1_HZ); // 1 Hz square wave + RTC_sec = false; +} + +void RTC_loop() { + if (RTC_sec) { // do after RTC PseudoPPS interrupt, without interrupt NO time/date to display + RTC_sec = false; // clear flag + digitalWrite(ledPin_PPS, LOW); // LED off + time_t now_t = myRTC.get(); + if ((last_sync_t + (sync_err_hours * 3600)) < now_t) { + SyncErr = true; + digitalWrite(ledPin_Sync, LOW); // LED off + } + Sec_Flip(now_t); // pass current datetime from RTC in UTC + } + if (millis() > (RTCMillis + 100)) { // do 100ms after PseudoPPS interrupt + digitalWrite(ledPin_PPS, HIGH); // LED on + } +} + +void SetRTC(time_t t) { + myRTC.set(t + 1); // the GPS NMEA output is 1 second behind at PPS !! + last_sync_t = t; + SyncErr = false; + digitalWrite(ledPin_Sync, HIGH); // LED on + Serial.println("RTC set by GPS"); // debug +} + +//End \ No newline at end of file diff --git a/RTC_com.h b/RTC_com.h new file mode 100644 index 0000000..e886f9d --- /dev/null +++ b/RTC_com.h @@ -0,0 +1,34 @@ +/* + RTC_com.h + + MIT License + Copyright (c) 2023 hdrlux + + all RTC functions, tested hardware: + - DS3231SN via I2C, has only 2-digit years, the Epoch is 2000-01-01. Valid until year 2099 + - set Pin: PseudoPPS Interrupt in file RTC_com.cpp [default: 3 = NANO INT1] + - set Pin: PseudoPPS LED in file RTC_com.cpp [default: A3] +*/ + +#ifndef RTC_com_H +#define RTC_com_H + +extern bool SyncErr; // RTC not synced in last n hours +#include "GPS_RTC_Clock.h" // for passing new second + +// handle interrupt +void rtc_interrupt(); + +// setup the pin as output +void RTC_LED_setup(); + +// setup hardware & interrupt from PseudoPPS +void RTC_setup(); + +// main RTC loop, RTC_com +void RTC_loop(); + +// sync RTC from GPS +void SetRTC(time_t t); + +#endif // RTC_com_H \ No newline at end of file