Usint the RTC on the 3.6 to get milliseconds

Status
Not open for further replies.

RandumbIntel

New member
I'm working on a datalogger for my racecar that harvest engine data and gps location at a 10 hz frequency. I would like to be able to timestamp data with a millisecond value, as well as I'm planning to incorporate a radio transceiver to acquire start and finish time data.

in using TimeLib.h I've gotten all the other time data, but I don't see a way to check milliseconds. Is there another library i should be using? Does the RTC have the resolution for millisecond output?
 
RTC AFAIK only ticks and updates on the seconds - at least directly ...

THOUGH - after writing the following an old sketch was pulled up - with THREAD LINK: millisecond-precision-from-Teensy-3*-RTC that may provide a better answer and direct solution.

EDIT: - though using that to read can be gone a long time ... maybe that is just the 1062 version that runs differently ... and doesn't return ms as linked below ...

The Teensy counts time with milliseconds - gets 1K updates each 'teensy second'. Anything disabling interrupts can skew that counting toward the next second update if one 1/1000th tick is missed.

Seems the RTC uses a unique crystal - so the two may diverge with each crystal being unique.

Watching the RTC for change in second and coordinating that with ARM_DWT_CYCCNT there will be some similar count diff between subsequent ARM_DWT_CYCCNT readings - that should be close to the F_CPU, perhaps 180 MHz.

That will change over time and temp where the observed count will have minor fluctuations from dozens in the short term to perhaps hundreds in a 24 hour period.

But for short timing periods, knowing the current RTC second and ARM_DWT_CYCCNT the offset from that last pairing will give the part of a second into the current second toward the next second.

Note the T_3.6 doesn't have the ARM_DWT_CYCCNT running by default, so this code will start it if it isn't started already:
Code:
  if ( ARM_DWT_CYCCNT == ARM_DWT_CYCCNT ) { // Enable CPU Cycle Counter
    ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
    ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter
  }

Finding the diff in ARM_DWT_CYCCNT is easy with uint32_t's - as long as the F_CPU cycle count doesn't overflow the 32 bit unsigned int:
Code:
	uint32_t startCNT = ARM_DWT_CYCCNT;
// do some delay( 10 );
	uint32_t endCNT = ARM_DWT_CYCCNT;
	uint32_t diffCNT = endCNT - startCNT;

Seems there was a way to get an interrupt trigger on RTC second change? In looking for that in old sketches - the above link was found ... ANd it might be a direct working answer for the T_3.6?

EDIT: For T_4's 1062 RTC seems Manitou did it again (Sep 19, 2019): github.com/manitou48/teensy4/blob/master/rtc.ino
 
Last edited:
Both, the the RTC of the T3.x and the T4.x count the number of clock oscillator periods to keep track of time. The clock crystal oscillates with 32.768kHz, thus the RTC increments every 1/32768 = 30.518... µs. If the clock is set correctly the registers hold the number of periods since 1970-01-01. Of course this number is too large for a 32bit number, so the RTC uses two 32bit registers to store it.

Here some T3.6 code which reads out the RTC as 64bit number which could be directly used as time stamp. Converting it to (floating point)seconds is also easy. Just divide the value by 32768 which gives you a floating point seconds value. However, please note that floats are not precise enough to keep the full time information (clock ticks since 1970-01-01) you need to use doubles instead. Alternatively you can substract some t0, e.g. the time when the sketch started before dividing by 32768. In this case you can use simple float mathematics. The following code shows how to implement this:

Code:
#include "Arduino.h"

/*
Read out the number of clock oscillator periods since 1.1.1970 as a 64bit number
To avoid wrong readings when one of the registers overflows during a readout the code
tries until two consequtive reads give the same result.
*/
uint64_t getRTC64()
{
    uint32_t hi1 = RTC_TSR, lo1 = RTC_TPR;
    while (true)
    {
        uint32_t hi2 = RTC_TSR, lo2 = RTC_TPR;
        if (lo1 == lo2 && hi1 == hi2)
        {
            return ((uint64_t)hi2 << 15) | (lo2 & 0x7FFF);
        }
        hi1 = hi2;
        lo1 = lo2;
    }
}

elapsedMicros stopwatch = 0;

void setup()
{
    while (!Serial) {}
    stopwatch = 0;    
}

void loop()
{
    static uint64_t t0 = getRTC64();            // initialized on first use
                                                //
    if (stopwatch > 11'000)                     // print current time every 11ms using the stopwatch is more precise than a simple delay
    {                                           //
        float t = (getRTC64() - t0) / 32768.0f; // getRTC returns number of RTC oscillator periods (32.768kHz), division by 32768 gives seconds (float)
        Serial.printf("%.3f sec\n", t);

        stopwatch -= 11'000;
    }
}

It prints:
Code:
0.011 sec
0.022 sec
0.033 sec
0.044 sec
0.055 sec
0.066 sec
0.077 sec
0.088 sec
0.099 sec
0.110 sec
0.121 sec
0.132 sec
0.143 sec
0.154 sec
0.165 sec
0.176 sec
0.187 sec
0.198 sec
 
Last edited:
...and here an other approach if you prefer time strings over 64-bit timestamps:
Code:
#include <ctime>

void getRTC64(time_t* seconds, uint32_t* milliseconds)
{
    uint32_t hi1 = RTC_TSR, lo1 = RTC_TPR;
    while (true)
    {
        uint32_t hi2 = RTC_TSR, lo2 = RTC_TPR;
        if (lo1 == lo2 && hi1 == hi2)
        {
            *seconds      = (time_t)hi1;                     // RTC_TSR keeps seconds
            *milliseconds = (1000 * (lo1 & 0x7FFF)) / 32768; // RTC_TPR keeps clock cycles -> convert to msec
            return;
        }
        hi1 = hi2;
        lo1 = lo2;
    }
}

elapsedMillis stopwatch = 0;

void setup()
{
    while (!Serial) {}
    stopwatch = 0;
}

void loop()
{
    if (stopwatch > 22)
    {
        time_t sec;
        uint32_t msec;
        getRTC64(&sec, &msec);

        tm* timeinfo = localtime(&sec);

        char buf[80];
        strftime(buf, 80, "%F_%I:%M:%S", timeinfo);
        Serial.printf("%s.%03d\n", buf, msec);

        stopwatch -= 22;
    }
}
This time getRTC64() doesn't return the 64-bit number of periods but a time_t value (seconds since 1970-01-01) and the milliseconds in separate variables. You can then use the time_t value to do the usual c-style time formatting and just append the milliseconds to it. The code prints:
Code:
2021-12-29_12:34:35.502
2021-12-29_12:34:35.524
2021-12-29_12:34:35.546
2021-12-29_12:34:35.568
2021-12-29_12:34:35.590
2021-12-29_12:34:35.612
2021-12-29_12:34:35.634
2021-12-29_12:34:35.656
2021-12-29_12:34:35.678
2021-12-29_12:34:35.700
2021-12-29_12:34:35.722
2021-12-29_12:34:35.744
2021-12-29_12:34:35.766
2021-12-29_12:34:35.788
2021-12-29_12:34:35.810
2021-12-29_12:34:35.832
2021-12-29_12:34:35.854
2021-12-29_12:34:35.876
2021-12-29_12:34:35.898
2021-12-29_12:34:35.920
2021-12-29_12:34:35.942
2021-12-29_12:34:35.964
2021-12-29_12:34:35.986
2021-12-29_12:34:36.008
2021-12-29_12:34:36.030
2021-12-29_12:34:36.052
2021-12-29_12:34:36.074
2021-12-29_12:34:36.096
2021-12-29_12:34:36.118
2021-12-29_12:34:36.140
2021-12-29_12:34:36.162
2021-12-29_12:34:36.184
2021-12-29_12:34:36.206
2021-12-29_12:34:36.228
2021-12-29_12:34:36.250
2021-12-29_12:34:36.272
2021-12-29_12:34:36.294
2021-12-29_12:34:36.316
2021-12-29_12:34:36.338
2021-12-29_12:34:36.360
2021-12-29_12:34:36.382
 
Last edited:
Good answer(s) it seems @luni!

When I run these sketches on a T3.2 (no T3.6 available), I get the time the T3.2 was connected to my Windows PC, and after that the time never updates. It seems the RTC is being set on the USB connection, but is not "enabled". Is there something else I need to do to enable RTC in T3.2?
 
Try the examples. And you need a battery if it should keep the time when switched off.

The examples work, but luni's sketches do not, at least on T3.2

EDIT: Just saw the notes on crystal. So, luni's sketches rely on the crystal, but the TimeLib does not?
 
The TimeLib maintains its own timer which you can sync to e.g. the on board RTC or some external time generator. This makes a lot of sense for boards without an RTC. The code above does not use anything from TimeLib, it simply reads out the on-chip RTC. This of course only works when you have a crystal on the board.
 
AFAIK the timelib maintains its own timer which you can sync to e.g. the on board RTC or some external time generator. This makes a lot of sense for boards without an RTC. The code above does not use anything from TimeLib, it simply reads out the on-chip RTC. This of course only works when you have a crystal on the board.

Yes, it's something like that. I'll have to dig into TimeLib to see how that works. Probably an IntervalTimer. I see that T4.x rtc.c has different registers, but it looks like something similar could be done.
 
I see that T4.x rtc.c has different registers, but it looks like something similar could be done.
Sure, see here for the corresponding T4 code https://forum.pjrc.com/threads/6806...onds-correctly?p=287198&viewfull=1#post287198

Edit: The main difference is that the T4 maintains a true 64bit counter, the T3.x RTC has kind of a 47bit counter. I.e. the first 15 bits in RTC_TPR, the rest in RTC_TSR. I.e., RTC_TSR contains the # of periods << 15 (devision by 32768) which corresponds to seconds.
 
Last edited:
Status
Not open for further replies.
Back
Top