The real time clock - TeensyUser/doc GitHub Wiki
The processors of the T3.x and T4.x boards contain a Real Time Clock (RTC) which is driven by a 32.768kHz crystal. T3.5, T3.6 and the T4.x boards contain this crystal by default. For the T3.2 boards you need to add that crystal yourself. (PJRC recommends this crystal: https://www.digikey.de/short/qbfr27mm)
Basically, the RTC simply counts the number of oscillations of the connected 32.768 kHz crystal. Since there are 32768 oscillations per second which equals 2^15, the 15th bit of the RTC counter toggles every second. The 14th bit at 0.5 seconds and so on.
Bit | 0 | 1 | 2 | 3 | 4 | 5 |
Time | 30.52 µs | 61.04 µs | 122.1 µs | 244.1 µs | 488.3 µs | 976.6 µs |
Bit | 6 | 7 | 8 | 9 | 10 | 11 |
Time | 1.953 ms | 3.906 ms | 7.812 ms | 15.62 ms | 31.25 ms | 62.50 ms |
Bit | 12 | 13 | 14 | 15 | 16 | 17 |
Time | 125.0 ms | 250.0 ms | 0.500 s | 1.000 s | 2.000 s | 4.000 s |
Usually you'd use the time functions provided by Teensyduino to access the RTC. However, those functions are limited to a resolution of one full second. If you need the full 30.5 µs precision of the built in RTC you can easily read out the values yourself.
In principle we can just copy the contents of the two counter registers and do whatever calculations we want to do with the values. However, to avoid glitches we need to take into account that the LSB can overflow between the reads of the LSB and the MSB register. Since the RTC counter changes only slowly (T=30.5µs), we can simply read out the both register twice and check if the first read equals the second read.
The code below shows how to read out the RTC counter on the Teensy 4.x boards. All relevant information can be found in chapter 20, Secure Non-Volatile Storage (SNVS) and in chapter 20.4.1.1, SNVS_HP Real Time Counter of the i.MX RT1060 reference manual.
The SNVS_HPRTCLR register contains the 32 least significant and the SNVS_HPRTCMR register the 15 most significant bits of the RTC counter.
uint64_t get_RTC_periods()
{
uint32_t hi1 = SNVS_HPRTCMR, lo1 = SNVS_HPRTCLR;
while (true)
{
uint32_t hi2 = SNVS_HPRTCMR, lo2 = SNVS_HPRTCLR;
if (lo1 == lo2 && hi1 == hi2)
{
return (uint64_t)hi2 << 32 | lo2;
}
hi1 = hi2;
lo1 = lo2;
}
}
This function might be useful to generate a precise and compact timestamp for a logging application. Converting the timestamp to human readable time can then be done by the standard c-functions of time.h:
void loop()
{
uint64_t periods = get_RTC_periods();
time_t seconds = periods / 32768;
uint32_t milliseconds = (1000 * (periods & 0x7FFF)) / 32768;
char buf[80];
tm* timeinfo = localtime(&seconds);
strftime(buf, 80, "%F_%H:%M:%S", timeinfo);
Serial.printf("%s.%03d\n", buf, milliseconds);
delay(123);
}
Which prints:
2021-12-30_19:33:39.406
2021-12-30_19:33:39.529
2021-12-30_19:33:39.652
2021-12-30_19:33:39.775
2021-12-30_19:33:39.898
2021-12-30_19:33:40.021
2021-12-30_19:33:40.144
2021-12-30_19:33:40.267
2021-12-30_19:33:40.390
2021-12-30_19:33:40.513
2021-12-30_19:33:40.636
2021-12-30_19:33:40.759
2021-12-30_19:33:40.882
2021-12-30_19:33:41.005
...
While reading out the RTC as the number of periods since 1970-01-01 generates a nice and compact timestamp, displaying it in human readable form requires a conversion to seconds which can be a bit tedious. For this purpose a direct readout of the RTC in seconds and milliseconds is more useful:
#include <ctime>
char buf[80];
time_t seconds, milliseconds;
void get_RTC_Time(time_t* s, time_t* ms)
{
uint32_t hi1 = SNVS_HPRTCMR, lo1 = SNVS_HPRTCLR;
while (true)
{
uint32_t hi2 = SNVS_HPRTCMR, lo2 = SNVS_HPRTCLR;
if (lo1 == lo2 && hi1 == hi2)
{
*s = (time_t)((hi2 << 17) | (lo2 >> 15));
*ms = (time_t)((1000 * (lo2 & 0x7FFF)) / 32768);
return;
}
hi1 = hi2;
lo1 = lo2;
}
}
void setup()
{
while (!Serial) {}
stopwatch = 0;
}
void loop()
{
get_RTC_Time(&seconds, &milliseconds);
tm* timeinfo = localtime(&seconds);
strftime(buf, 80, "%F_%H:%M:%S", timeinfo);
Serial.printf("%s.%03d\n", buf, milliseconds);
delay(123);
}
tbd