There are, somewhat confusingly,
two RTCs in the Teensy 4.0.
There's one called the RTC (which is what the your program is talking to when you make a Teensy3Clock.get() or .set() call) and another called the SRTC (Secure Real Time Clock, if memory serves). The SRTC is what the loader sets the time in and what preserves the time when you pull the power - assuming you have 3V on Vbat.
At startup, the time in the RTC is synced from the SRTC. This happens in cores/teensy4/startup.c, towards the end of ResetHandler(). If the SRTC isn't running (which will be the case when there's no battery on Vbat), the SRTC is initialised to Jan 1, 2019 before the RTC is synced.
Teensy3Clock.get() reads the RTC and returns a value suitable for use in TimeLib's setTime() call.
Teensy3Clock.set() sets the RTC, but, again confusingly,
not the SRTC. Your program can set the RTC and all will be well and good until you lose power. When the Teensy is powered back up again, the RTC will be synced to the SRTC (which is still running using the time originally set by the loader).
So let's say that you load your program in. The loader sets the time, which, right now, is an hour behind. Let's say it shows as April 2, 2020 8:00AM. You want to fix that and do something like this (with some appropriate logic around it to ensure that it only gets called once after the Teensy is loaded - have fun with that!):
Code:
time_t newTime = Teensy3Clock.get() + 3600;
Teensy3Clock.set(newTime);
Now the time is set to April 2, 9:00AM.
An hour later, the RTC would show Apr 2, 10:00AM. You pull the power and a minute later plug it back in again. You might expect the RTC to show a value of Apr 2, 2020 10:01AM. But instead it shows Apr 2, 2020
9:01AM. The SRTC was an hour behind all this time, and when the RTC was synced again at powerup, time travel ensued.
The problem is that Teensy3Clock.set() only sets the RTC. You can see this for yourself by looking at the rtc_set() routine in cores/teensy/rtc.c. On my development box, I've replaced the rtc_set() routine with one that sets the SRTC instead, and then syncs the RTC. Doing this means that any time changes you make are preserved across power outages.
Code:
void rtc_set(unsigned long t)
{
// stop the RTC
SNVS_HPCR &= ~(SNVS_HPCR_RTC_EN | SNVS_HPCR_HP_TS);
while (SNVS_HPCR & SNVS_HPCR_RTC_EN) ; // wait
// stop the SRTC
SNVS_LPCR &= ~SNVS_LPCR_SRTC_ENV;
while (SNVS_LPCR & SNVS_LPCR_SRTC_ENV); // wait
// set the SRTC
SNVS_LPSRTCLR = t << 15;
SNVS_LPSRTCMR = t >> 17;
// start the SRTC
SNVS_LPCR |= SNVS_LPCR_SRTC_ENV;
while (!(SNVS_LPCR & SNVS_LPCR_SRTC_ENV)); // wait
// start the RTC and sync it to the SRTC
SNVS_HPCR |= SNVS_HPCR_RTC_EN | SNVS_HPCR_HP_TS;
}
I came up with based on the code in cores/teensy4/startup.c and from reading the i.MX RT1060 Processor Reference Manual. The SNVS_HPCR_HP_TS bit is used in the startup code but I couldn't find it in the reference manual; it appeared to be in a block of reserved bits in the register I was looking it (SNVS_HP Control Register on pages 1299-1300). I assume it's what syncs the RTC to the SRTC. Anyhow, it works for my purposes; you might find it useful too but YMMV.
One thing to keep in mind is that if you do this, you'll have to re-patch the rtc.c file each time you upgrade Teensyduino.