I found a great post here: "How to get a more accurate RTC clock set from a NTP time server"
Wonderful! I love accurate clocks...
I Implemented the NTP modification to get the fractional time part (milliseconds)
Then I wired up my DS3231's SQW pin to an interrupt.
Now I have on the FALLING edge exactly the start of a new scond
I thus SET the Arduino time_t t at that precise moment using RTC time so I am not dependent on the oscillator of the Arduino
It does work but I'm learning a lot with this project so my only question is now of how to improve my way of doing this because I think it's clumsy: Is there a better way to keep close to the Time AND Timezone libraries way of keeping time and still dictate the timing via the RTC SQW pulse??
What I now do is checking the now() time and every 60 seconds the arduino time_t t will be updated with the RTC time -->at the exact moment a new (RTC) second starts.
Q: Is it possible to use the RTC second pulse to continually keep the 'internal' arduino timer/ seconds in sync??
My code is part of a large project, very long and very unfinished (but does work)
So maybe it's enough to describe the steps and parts of the code?
If not please let me know.
Arduino Nano 33 Iot - SAMD21 MC
Main libraries:
#include <Arduino.h>// included for PlatformIO IDE
// Wifi
#include <WiFiNINA.h> // arduino-libraries/WiFiNINA@1.8.13
#include <WiFiUdp.h> // arduino-libraries/WiFiNINA@1.8.13
#include "wifi_secrets.h"
// Includes for Time
#include <TimeLib.h> // paulstoffregen/Time@1.6.1
#include <Timezone.h> // jchristensen/Timezone@1.2.4
// RTC
#include <DS3231.h> // northernwidget/DS3231@^1.1.0
At startup I sync the RTC with NTP time at millisecond accuracy.
(NTP function listed at the end of this post)
// set the RTC with NTP time
myClock.setEpoch(getNtpTime());
Setup: SQW and interrupt attachment:
// enable DS3231 Oscillator. Parameters:
// true:Oscillator ON, true:also when on battery, 0:frequency 1 Hz
myClock.enableOscillator(true, true, 0);
// attach the DS3231 Interrupt routine (use EXTERNAL pullup!)
// a new RTC second will start on the falling edge!
//
// Using this as the timing instead of the internal millis()
// will give amazing accuracy: only a few seconds/year
// (only if you use a GENUINE DS3231 chip of course...)
// Update via the NTP server once a month is more than
// enough to have always accurate time.
attachInterrupt(digitalPinToInterrupt(DS3231_1Hz_PIN),
secondInterrupt, FALLING);
Interrupt routine: called function:
void secondInterrupt()
{
newSecond = true;
}
Loop(): this function is called constantly:
void syncTimeWithRtcPulse()
{
// newSecond is set by the RTC SQW pulse via an 1Hz interrupt
if (newSecond)
{
newSecond = false;
// set the time every 60 seconds
if ((now() % 60 == 0) || (coldStart == true))
{
// coldStart only executes at power on or reset
coldStart = false;
// we first read the time t from the RTC
DateTime nowRTC = myRTC.now();
//next set the arduino time
setTime(nowRTC.unixtime());
}
}
}
changed part of NTP code (full NTP code below that):
// convert four bytes starting at location 44 to a long integer
// this is the fractional part of the NTP time info
fractionalPart = (unsigned long)packetBuffer[44] << 24;
fractionalPart |= (unsigned long)packetBuffer[45] << 16;
fractionalPart |= (unsigned long)packetBuffer[46] << 8;
fractionalPart |= (unsigned long)packetBuffer[47];
fractNtp = ((uint64_t)fractionalPart * 1000) >> 32;
// compensate for the fractional time AND detract
// 50 ms for round trip delay. works for me!
delay(950 - fractNtp);
// add one second to compensate for delay
return secsSince1900 - seventyYears + 1;
full NTP code
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
// ! Time zone >> NOT USED becaue we use the Timezone library
const int timeZone = 0;
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0)
; // discard any previously received packets
Serial.println("Transmit NTP Request");
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500)
{
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE)
{
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
// get fractional time - milliseconds
unsigned long fractionalPart;
// convert four bytes starting at location 44 to a long integer
// this is the fractional part of the NTP time info
fractionalPart = (unsigned long)packetBuffer[44] << 24;
fractionalPart |= (unsigned long)packetBuffer[45] << 16;
fractionalPart |= (unsigned long)packetBuffer[46] << 8;
fractionalPart |= (unsigned long)packetBuffer[47];
fractNtp = ((uint64_t)fractionalPart * 1000) >> 32;
// compensate for the fractional time AND detract
// 50 ms for round trip delay. works for me!
delay(950 - fractNtp);
// add one second to compensate for delay
return secsSince1900 - seventyYears + 1;
}
}
Serial.println("No NTP Response :-(");
return 0; // return 0 if unable to get the time
}