How To Install Using Arduino Library Manager
Why do we need this Timezone_Generic Library
The Timezone_Generic Library is designed to work in conjunction with the Arduino Time library, which must also be installed on your system. This documentation assumes some familiarity with the Time library.
The primary goal of the Timezone_Generic Library is to convert Universal Coordinated Time (UTC) to the correct local time, whether it is Daylight Saving Time (a.k.a. summer time, DST) or standard time. The time source could be a GPS receiver, an NTP server, or a Real-Time Clock (RTC) set to UTC. But whether a hardware RTC or other time source is even present is immaterial, since the Time library can function as a software RTC without additional hardware (although its accuracy is dependent on the accuracy of the microcontroller's system clock.)
The Timezone_Generic Library implements two objects to facilitate time zone conversions:
- A TimeChangeRule object describes when local time changes to daylight (summer) time, or to standard time, for a particular locale.
- A Timezone object uses TimeChangeRules to perform conversions and related functions. It can also write its TimeChangeRules to or read them from EEPROM/DueFlashStorage/FlashStorage/LittleFS/SPIFFS. Multiple time zones can be represented by defining multiple Timezone objects.
The examples will demonstrate how to get the UTC time from NTP server, then update the DS3231 RTC to make sure the time is perfectly correct.
You can also modify the examples to read the NTP and update RTC once per every pre-determined period to ensure the RTC accuracy.
This Timezone_Generic Library is based on and modified from Jack Christensen's Timezone Library to add functions and support to many boards and shields.
Releases v1.2.5
1. Add examples to use STM32 Built-In RTC.
Releases v1.2.4
1. Initial porting to many Generic boards using WiFi/Ethernet modules/shields.
2. Add support to SAMD21/SAMD51, nRF52, STM32F/L/H/G/WB/MP1 with WiFiNINA, ESP8266-AT, W5x00, ENC28J60, LAN8742A Ethernet modules/shields.
3. Add support to SAM DUE DueFlashStorage, SAMD FlashStorage, nRF52 LittleFS, STM32 and AVR EEPROM
4. Add functions.
5. Completely new examples using NTP time to update DS3231 RTC.
Currently Supported Boards
- ESP8266. To be done soon.
- ESP32. To be done soon.
- AdaFruit Feather nRF52832, nRF52840 Express, BlueFruit Sense, Itsy-Bitsy nRF52840 Express, Metro nRF52840 Express, NINA_B302_ublox, NINA_B112_ublox etc..
- Arduino SAMD21 (ZERO, MKR, NANO_33_IOT, etc.).
- Adafruit SAMD21 (Itsy-Bitsy M0, Metro M0, Feather M0, Gemma M0, etc.).
- Adafruit SAMD51 (Itsy-Bitsy M4, Metro M4, Grand Central M4, Feather M4 Express, etc.).
- Seeeduino SAMD21/SAMD51 boards (SEEED_WIO_TERMINAL, SEEED_FEMTO_M0, SEEED_XIAO_M0, Wio_Lite_MG126, WIO_GPS_BOARD, SEEEDUINO_ZERO, SEEEDUINO_LORAWAN, SEEED_GROVE_UI_WIRELESS, etc.)
- STM32 (Nucleo-144, Nucleo-64, Nucleo-32, Discovery, STM32F1, STM32F3, STM32F4, STM32H7, STM32L0, etc.).
- STM32F/L/H/G/WB/MP1 (Nucleo-64 L053R8,Nucleo-144, Nucleo-64, Nucleo-32, Discovery, STM32Fx, STM32H7, STM32Lx, STM32Gx, STM32WB, STM32MP1, etc.) having 64K+ Flash program memory.
Currently Supported WiFi Modules/Shields
- ESP8266 built-in WiFi. To be done soon.
- ESP32 built-in WiFi. To be done soon.
- WiFiNINA using WiFiNINA or WiFiNINA_Generic library.
- ESP8266-AT, ESP32-AT WiFi shields using WiFiEspAT or ESP8266_AT_WebServer library.
Currently Supported Ethernet Modules/Shields
- W5x00's using Ethernet, EthernetLarge, Ethernet2 or Ethernet3 Library.
- ENC28J60 using EthernetENC or UIPEthernet library.
- LAN8742A using STM32Ethernet / STM32 LwIP libraries.
Currently Supported storage
- ESP8266 EEPROM, LittleFS, SPIFFS. To be done soon.
- ESP32 EEPROM, SPIFFS. To be done soon.
- SAM DUE DueFlashStorage.
- SAMD FlashStorage.
- nRF52 LittleFS.
- STM32 and AVR EEPROM.
Sample Code
This is the BI_RTC_Alarm_STM32_Ethernet example
Code: Select all
/****************************************************************************************************************************
STM32 has five clock sources: HSI, HSE, LSI, LSE, PLL.
(1) HSI is a high-speed internal clock, RC oscillator, with a frequency of 8MHz and low accuracy.
(2) HSE is a high-speed external clock, which can be connected with quartz/ceramic resonator or external clock source.
Its frequency range is from 4MHz to 16MHz.
(3) LSI is a low-speed internal clock, RC oscillator, with a frequency of 40 kHz, providing a low-power clock.
(4) LSE is a low-speed external clock connected to 32.768 kHz quartz crystal.
(5) PLL is the frequency doubling output of PLL, and its clock input source can be HSI/2, HSE or HSE/2.
Frequency doubling can be chosen as 2 to 16 times, but the maximum output frequency should not exceed 72MHz.
The system clock SYSCLK can be derived from three clock sources:
(1) HSI oscillator clock
(2) HSE oscillator clock
(3) PLL Clock
STM32 can choose a clock signal to output to MCO foot (PA8), and can choose 2-frequency, HSI, HSE, or system clock for PLL output.
Before any peripheral can be used, its corresponding clock must be enabled first.
*****************************************************************************************************************************/
#include "defines.h"
#include <Timezone_Generic.h> // https://github.com/khoih-prog/Timezone_Generic
#include <STM32RTC.h>
/* Get the rtc object */
STM32RTC& rtc = STM32RTC::getInstance();
//////////////////////////////////////////
// US Eastern Time Zone (New York, Detroit)
TimeChangeRule myDST = {"EDT", Second, Sun, Mar, 2, -240}; //Daylight time = UTC - 4 hours
TimeChangeRule mySTD = {"EST", First, Sun, Nov, 2, -300}; //Standard time = UTC - 5 hours
Timezone myTZ(myDST, mySTD);
// If TimeChangeRules are already stored in EEPROM, comment out the three
// lines above and uncomment the line below.
//Timezone myTZ(100); //assumes rules stored at EEPROM address 100
TimeChangeRule *tcr; //pointer to the time change rule, use to get TZ abbrev
//////////////////////////////////////////
char timeServer[] = "time.nist.gov"; // NTP server
unsigned int localPort = 2390; // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48; // NTP timestamp is in the first 48 bytes of the message
const int UDP_TIMEOUT = 2000; // timeout in miliseconds to wait for an UDP packet to arrive
byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets
// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;
// send an NTP request to the time server at the given address
void sendNTPpacket(char *ntpSrv)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(ntpSrv, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
void update_RTC(unsigned long epoch)
{
// Update RTC
Serial.println("\nUpdating Time for STM32 RTC");
// STM32 RTC clock starts from 01/01/2000 if not set. No battery-backed RTC.
// STM32 RTC specific code
// Can use either one of these functions
// 1. The best way to set from epoch
rtc.setEpoch(epoch);
// 2. you can also use the harder way
// Get the time_t from epoch
//time_t epoch_t = epoch;
//rtc.setTime(hour(epoch_t), minute(epoch_t), second(epoch_t));
//rtc.setDate(weekday(epoch_t), day(epoch_t), month(epoch_t), year(epoch_t));
}
void alarmMatch(void *data)
{
UNUSED(data);
Serial.println("*****RTC ALARM ACTIVATED*****");
Serial.println("*****RTC ALARM ACTIVATED*****");
}
void setRTC_Alarm(unsigned long epochAlarm)
{
rtc.attachInterrupt(alarmMatch);
// Alarm at epochAlarm
rtc.setAlarmEpoch(epochAlarm, rtc.MATCH_DHHMMSS);
// Display Alarm time from RTC
Serial.println("=======RTC ALARM SET========");
// STM32 RTC specific code
time_t utcAlarm = epochAlarm;
time_t localAlarm = myTZ.toLocal(utcAlarm, &tcr);
//////
printDateTime(utcAlarm, "UTC");
printDateTime(localAlarm, tcr -> abbrev);
Serial.println("============================");
}
void getNTPTime(void)
{
static bool gotCurrentTime = false;
// Just get the correct ime once
if (!gotCurrentTime)
{
sendNTPpacket(timeServer); // send an NTP packet to a time server
// wait to see if a reply is available
delay(1000);
if (Udp.parsePacket())
{
Serial.println(F("Packet received"));
// We've received a packet, read the data from it
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, esxtract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
Serial.print(F("Seconds since Jan 1 1900 = "));
Serial.println(secsSince1900);
// now convert NTP time into everyday time:
Serial.print(F("Unix time = "));
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
// print Unix time:
Serial.println(epoch);
// Get the time_t from epoch
time_t epoch_t = epoch;
// set the system time to UTC
// warning: assumes that compileTime() returns US EDT
// adjust the following line accordingly if you're in another time zone
setTime(epoch_t);
update_RTC(epoch);
// Set test alarm after 30s
setRTC_Alarm(epoch + 30L);
// print the hour, minute and second:
Serial.print(F("The UTC time is ")); // UTC is the time at Greenwich Meridian (GMT)
Serial.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day)
Serial.print(':');
if (((epoch % 3600) / 60) < 10)
{
// In the first 10 minutes of each hour, we'll want a leading '0'
Serial.print('0');
}
Serial.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute)
Serial.print(':');
if ((epoch % 60) < 10)
{
// In the first 10 seconds of each minute, we'll want a leading '0'
Serial.print('0');
}
Serial.println(epoch % 60); // print the second
gotCurrentTime = true;
}
else
{
// wait ten seconds before asking for the time again
delay(10000);
}
}
}
//////////////////////////////////////////
// format and print a time_t value, with a time zone appended.
void printDateTime(time_t t, const char *tz)
{
char buf[32];
char m[4]; // temporary storage for month string (DateStrings.cpp uses shared buffer)
strcpy(m, monthShortStr(month(t)));
sprintf(buf, "%.2d:%.2d:%.2d %s %.2d %s %d %s",
hour(t), minute(t), second(t), dayShortStr(weekday(t)), day(t), m, year(t), tz);
Serial.println(buf);
}
void setup()
{
Serial.begin(115200);
while (!Serial);
Serial.println("\nStart BI_RTC_Alarm_STM32_Ethernet on " + String(BOARD_NAME) + ", using " + String(SHIELD_TYPE));
ET_LOGWARN3(F("Board :"), BOARD_NAME, F(", setCsPin:"), USE_THIS_SS_PIN);
ET_LOGWARN(F("Default SPI pinout:"));
ET_LOGWARN1(F("MOSI:"), MOSI);
ET_LOGWARN1(F("MISO:"), MISO);
ET_LOGWARN1(F("SCK:"), SCK);
ET_LOGWARN1(F("SS:"), SS);
ET_LOGWARN(F("========================="));
#if !(USE_BUILTIN_ETHERNET || USE_UIP_ETHERNET)
// For other boards, to change if necessary
#if ( USE_ETHERNET || USE_ETHERNET_LARGE || USE_ETHERNET2 || USE_ETHERNET_ENC )
// Must use library patch for Ethernet, Ethernet2, EthernetLarge libraries
Ethernet.init (USE_THIS_SS_PIN);
#elif USE_ETHERNET3
// Use MAX_SOCK_NUM = 4 for 4K, 2 for 8K, 1 for 16K RX/TX buffer
#ifndef ETHERNET3_MAX_SOCK_NUM
#define ETHERNET3_MAX_SOCK_NUM 4
#endif
Ethernet.setCsPin (USE_THIS_SS_PIN);
Ethernet.init (ETHERNET3_MAX_SOCK_NUM);
#elif USE_CUSTOM_ETHERNET
// You have to add initialization for your Custom Ethernet here
// This is just an example to setCSPin to USE_THIS_SS_PIN, and can be not correct and enough
//Ethernet.init(USE_THIS_SS_PIN);
#endif //( ( USE_ETHERNET || USE_ETHERNET_LARGE || USE_ETHERNET2 || USE_ETHERNET_ENC )
#endif
// start the ethernet connection and the server:
// Use DHCP dynamic IP and random mac
uint16_t index = millis() % NUMBER_OF_MAC;
// Use Static IP
//Ethernet.begin(mac[index], ip);
Ethernet.begin(mac[index]);
// you're connected now, so print out the data
Serial.print(F("You're connected to the network, IP = "));
Serial.println(Ethernet.localIP());
Udp.begin(localPort);
// STM32 RTC specific code
// Select RTC clock source: LSI_CLOCK, LSE_CLOCK or HSE_CLOCK.
// By default the LSI is selected as source.
//rtc.setClockSource(STM32RTC::LSE_CLOCK);
rtc.begin(); // initialize RTC 24H format
//////
}
void loop()
{
// Get time from NTP once, then update RTC
// You certainly can make NTP check every hour/day to update RTC ti have better accuracy
getNTPTime();
// Display time from RTC
Serial.println("============================");
// STM32 RTC specific code
time_t utc = rtc.getEpoch();
time_t local = myTZ.toLocal(utc, &tcr);
//////
printDateTime(utc, "UTC");
printDateTime(local, tcr -> abbrev);
delay(10000);
}
Debug Termimal Output Samples
1. This is terminal debug output when running BI_RTC_STM32_Ethernet example on STM32F7 Nucleo-144 NUCLEO_F767ZI with LAN8742A using STM32Ethernet Library to demonstrate the usage of STM32 built-in RTC
Code: Select all
Start BI_RTC_STM32_Ethernet on NUCLEO_F767ZI, using LAN8742A Ethernet & STM32Ethernet Library
[ETHERNET_WEBSERVER] Board : NUCLEO_F767ZI , setCsPin: 10
[ETHERNET_WEBSERVER] Default SPI pinout:
[ETHERNET_WEBSERVER] MOSI: 11
[ETHERNET_WEBSERVER] MISO: 12
[ETHERNET_WEBSERVER] SCK: 13
[ETHERNET_WEBSERVER] SS: 10
[ETHERNET_WEBSERVER] =========================
You're connected to the network, IP = 192.168.2.97
Packet received
Seconds since Jan 1 1900 = 3812898366
Unix time = 1603909566
Updating Time for STM32 RTC
The UTC time is 18:26:06
============================
18:26:06 Wed 28 Oct 2020 UTC
14:26:06 Wed 28 Oct 2020 EDT
============================
18:26:15 Wed 28 Oct 2020 UTC
14:26:15 Wed 28 Oct 2020 EDT
============================
18:26:24 Wed 28 Oct 2020 UTC
14:26:24 Wed 28 Oct 2020 EDT
============================
18:26:33 Wed 28 Oct 2020 UTC
14:26:33 Wed 28 Oct 2020 EDT
============================
18:26:43 Wed 28 Oct 2020 UTC
14:26:43 Wed 28 Oct 2020 EDT
Code: Select all
Start BI_RTC_Alarm_STM32_Ethernet on NUCLEO_F767ZI, using LAN8742A Ethernet & STM32Ethernet Library
[ETHERNET_WEBSERVER] Board : NUCLEO_F767ZI , setCsPin: 10
[ETHERNET_WEBSERVER] Default SPI pinout:
[ETHERNET_WEBSERVER] MOSI: 11
[ETHERNET_WEBSERVER] MISO: 12
[ETHERNET_WEBSERVER] SCK: 13
[ETHERNET_WEBSERVER] SS: 10
[ETHERNET_WEBSERVER] =========================
You're connected to the network, IP = 192.168.2.96
Packet received
Seconds since Jan 1 1900 = 3812900198
Unix time = 1603911398
Updating Time for STM32 RTC
=======RTC ALARM SET========
18:57:08 Wed 28 Oct 2020 UTC
14:57:08 Wed 28 Oct 2020 EDT
============================
The UTC time is 18:56:38
============================
18:56:38 Wed 28 Oct 2020 UTC
14:56:38 Wed 28 Oct 2020 EDT
============================
18:56:47 Wed 28 Oct 2020 UTC
14:56:47 Wed 28 Oct 2020 EDT
============================
18:56:56 Wed 28 Oct 2020 UTC
14:56:56 Wed 28 Oct 2020 EDT
============================
18:57:05 Wed 28 Oct 2020 UTC
14:57:05 Wed 28 Oct 2020 EDT
*****RTC ALARM ACTIVATED*****
*****RTC ALARM ACTIVATED*****
============================
18:57:15 Wed 28 Oct 2020 UTC
14:57:15 Wed 28 Oct 2020 EDT