Teensy 4.1 - NTP/PTP/RTC

jwhill

Member
I want to see if I can implement an NTP and PTP client on a Teensy 4.1. I can send/encode and recieve/decode the NTP and PTP packets to/from the servers all day. But, I've only been successful at getting epoch to the second from the RTC. I can't seem to figure out how to extract any sub-second resolution from the RTC - to the nanosecond level would be optimal. Without it, I don't know how I can obtain all the necessary time events that are needed to calculate accurate PTP time or to dial-in NTP time. Then the next issue is, how do I set the RTC time accurately? Does anyone have an example that shows how to extract micro or nanoseconds from the RTC? The solutions I've seen so far are not complete and leave out what I believe are important parts - bear with me, I'm a bit of a noob when it comes to C. Any advise is much appreciated.

John
 
Paul posted this code:
Code:
extern "C" int _gettimeofday(struct timeval *tv, void *ignore) {
  uint32_t hi1 = SNVS_HPRTCMR;
  uint32_t lo1 = SNVS_HPRTCLR;
  while (1) {
    uint32_t hi2 = SNVS_HPRTCMR;  // ref manual 20.3.3.1.3 page 1231
    uint32_t lo2 = SNVS_HPRTCLR;
    if (lo1 == lo2 && hi1 == hi2) {
      tv->tv_sec = (hi2 << 17) | (lo2 >> 15);
      tv->tv_usec = ((lo2 & 0x7FFF) * 15625) >> 9;
      return 0;
    }
    hi1 = hi2;
    lo1 = lo2;
  }
}

in https://forum.pjrc.com/threads/70966-Getting-std-chrono-to-work-with-the-Teensy
The SNVS_HPRTCLR register is incremented 32768 times a second, hence the "((lo2 & 0x7fff) * 15625) >> 9" to convert 1/32768 to a microsecond.

In my code I use
Code:
/******************************************************************************/
// Read the RTC fractional value.
int16_t readRTCfrac()
{
#ifdef TEENSY4
  uint32_t hi1, lo1, hi2, lo2;
  hi1 = SNVS_HPRTCMR;
  lo1 = SNVS_HPRTCLR;
  while(1) {
    hi2 = SNVS_HPRTCMR;
    lo2 = SNVS_HPRTCLR;
    if (lo1 == lo2 && hi1 == hi2) {
      return (int16_t)(lo2 & 0x7fff);
    }
    hi1 = hi2;
    lo1 = lo2;
  }
#else
  int16_t frac;
  frac = RTC_TPR;
  frac &= 0x7ffff;
  return(frac);
#endif
}
to return the fractional counter as-is. The code handles both teensy 3 and teensy 4.

I haven't tried setting the fractional counter on a teensy 4.1, but in the teensy 3 it's as simple as writing to the register, as in
Code:
 /******************************************************************************/
// Set the RTC from the 32-bit utime_t value
void setRTCtt(utime_t utcTt)
{
#ifdef TEENSY4
  rtc_set(utcTt);
#else
  RTC_SR = 0;			// allow clock updates
  RTC_TPR = 0;			// set fractional part to zero
  RTC_TSR = utcTt;		// set seconds
  RTC_SR = RTC_SR_TCE;		// let clock run
#endif
}
which on the Teensy 3 sets the fractional part to 0 after enabling writes (by setting RTC_SR to zero). I haven't looked at the "rtc_set()" function in the Teensy 4's library, it probably does something similar.
 
Thank you. I tried compiling using your code and I'm getting "Compilation error: 'RTC_TPR' was not declared in this scope". Is there a library I'm missing?
 
I haven't tried setting the fractional counter on a teensy 4.1, but in the teensy 3 it's as simple as writing to the register, as in
Code:
/******************************************************************************/
// Set the RTC from the 32-bit utime_t value
void setRTCtt(utime_t utcTt)
{
#ifdef TEENSY4
rtc_set(utcTt);
#else
RTC_SR = 0; // allow clock updates
RTC_TPR = 0; // set fractional part to zero
RTC_TSR = utcTt; // set seconds
RTC_SR = RTC_SR_TCE; // let clock run
#endif
}
As stated in the post the code was for the Teensy 3.x.
It also suggested that you look at the code for the Teensy 4.x.
 
The code I submitted works for either Teensy 4 or Teensy 3, controlled by a "#define TEENSY4" if you want Teensy 4.
Here's the readRTCFrac() code for only Teensy 4:

Code:
#include <imxrt.h>

/******************************************************************************/
// Read the RTC fractional value.
int16_t readRTCfrac()
{
  uint32_t hi1, lo1, hi2, lo2;
  hi1 = SNVS_HPRTCMR;
  lo1 = SNVS_HPRTCLR;
  while(1) {
    hi2 = SNVS_HPRTCMR;
    lo2 = SNVS_HPRTCLR;
    if (lo1 == lo2 && hi1 == hi2) {
      return (int16_t)(lo2 & 0x7fff);
    }
    hi1 = hi2;
    lo1 = lo2;
  }
}
 
The code I submitted works for either Teensy 4 or Teensy 3, controlled by a "#define TEENSY4" if you want Teensy 4.
Here's the readRTCFrac() code for only Teensy 4:

Code:
#include <imxrt.h>

/******************************************************************************/
// Read the RTC fractional value.
int16_t readRTCfrac()
{
  uint32_t hi1, lo1, hi2, lo2;
  hi1 = SNVS_HPRTCMR;
  lo1 = SNVS_HPRTCLR;
  while(1) {
    hi2 = SNVS_HPRTCMR;
    lo2 = SNVS_HPRTCLR;
    if (lo1 == lo2 && hi1 == hi2) {
      return (int16_t)(lo2 & 0x7fff);
    }
    hi1 = hi2;
    lo1 = lo2;
  }
}
Thank you. I tried compiling using your code and I'm getting "Compilation error: 'RTC_TPR' was not declared in this scope". Is there a library I'm missing?
I am quite aware of that, just that @jwhill was/is using a Teensy 4.x and getting the error "Compilation error: 'RTC_TPR' was not declared in this scope" from the T3.x part of your code!!
 
I suggest you read through https://forum.pjrc.com/threads/61665-RTC-Registers-and-Millisecond-Precision-from-Teensy-4-0
Turns out the SNVS_HPRTCMR and SNVS_HPRTCLR together form a 64-bit counter incrementing 32768 times a second.
In the following code:
Code:
#include "imxrt.h"

unsigned long rtc_get(void)
{
	uint32_t hi1 = SNVS_HPRTCMR;
	uint32_t lo1 = SNVS_HPRTCLR;
	while (1) {
		uint32_t hi2 = SNVS_HPRTCMR;
		uint32_t lo2 = SNVS_HPRTCLR;
		if (lo1 == lo2 && hi1 == hi2) {
			return (hi2 << 17) | (lo2 >> 15);
		}
		hi1 = hi2;
		lo1 = lo2;
	}
}

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;
}

rtc_get() returns a 32-bit unsigned integer of only the seconds, throwing away the 15 lower bits of the 64-bit counter. rtc_set() takes a 32-bit unsigned integer of seconds and sets the two registers, setting the lowest 15-bits (fractional seconds) to zero.
Here's some code that I've just written that uses 64-bit unsigned integers to get/set the full 64-bit RTC in 1/32768 second increments. The code is untested.
Code:
#include "imxrt.h"

uint64_t rtc_get_64(void)
{
	uint32_t hi1 = SNVS_HPRTCMR;
        uint64_t tmp64;
	uint32_t lo1 = SNVS_HPRTCLR;
	while (1) {
		uint32_t hi2 = SNVS_HPRTCMR;
		uint32_t lo2 = SNVS_HPRTCLR;
		if (lo1 == lo2 && hi1 == hi2) {
                        tmp64 = hi1
			return (tmp64 << 32) | lo1;
		}
		hi1 = hi2;
		lo1 = lo2;
	}
}

void rtc_set_64(uint64_t 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 >> 32;
	SNVS_LPSRTCMR = t & 0xffffffff;
	// 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 tried your untested code. I am reading the RTC once a second:

uint64_t tmx = rtc_get_64();
Serial.println(tmx);

Output:
55052984023201
55052984055969
55052984088737
55052984121505
55052984154273
55052984187041
55052984219809
55052984252577
55052984285345
55052984318113
55052984350881
55052984383649
55052984416417
55052984449185
55052984481952
55052984514720
55052984547488
55052984580256
55052984613024
55052984645792
55052984678560
55052984711328
 
Here is my code using your suggestions for extracting the 64-bit RTC in 1/32768 second increments. I able able to get time in seconds, in the upper 64-bits, but struggling with the microseconds, as you can see:

Code:
#include <TimeLib.h>
#include <time.h>
#include <imxrt.h>

uint64_t rtc_get_64(void)
{
	uint32_t hi1 = SNVS_HPRTCMR;
        uint64_t tmp64;
	uint32_t lo1 = SNVS_HPRTCLR;
	while (1) {
		uint32_t hi2 = SNVS_HPRTCMR;
		uint32_t lo2 = SNVS_HPRTCLR;
		if (lo1 == lo2 && hi1 == hi2) {
      tmp64 = hi1;
			return (tmp64 << 32) | lo1;
		}
		hi1 = hi2;
		lo1 = lo2;
	}
}
int16_t readRTCfrac()
{
  uint32_t hi1, lo1, hi2, lo2;
  hi1 = SNVS_HPRTCMR;
  lo1 = SNVS_HPRTCLR;
  while(1) {
    hi2 = SNVS_HPRTCMR;
    lo2 = SNVS_HPRTCLR;
    if (lo1 == lo2 && hi1 == hi2) {
      return (int16_t)(lo2 & 0x7fff);
    }
    hi1 = hi2;
    lo1 = lo2;
  }
}
void setup() {
  // put your setup code here, to run once:
  
}

void loop() {
  delay(1000);
  uint64_t tmi = rtc_get_64();
  uint32_t tmp64 = (uint32_t)(tmi >> 32);
  uint32_t lo1 = (uint32_t)(tmi & 0xffffffff);

  // Convert the time value to seconds
  uint64_t time_seconds = (tmp64 * 4294967296ULL + lo1) / 32768; 
  uint64_t time_microseconds = ((tmp64 * 4294967296ULL + lo1) * 1000000ULL) / 32768;
  
  Serial.println("----------------------------");
  Serial.printf("tmp64 = %llu\n", tmp64);
  Serial.printf("lo1   = %llu\n", lo1);
  Serial.printf("%llu seconds\n", time_seconds);
  Serial.printf("%llu microseconds\n", time_microseconds);
  Serial.printf("rdFrac = %u\n", readRTCfrac());

  struct tm* timeinfo;
  time_t rawtime = (time_t)time_seconds;
  timeinfo = gmtime(&rawtime);
  char buffer[80];
  strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo);
  Serial.printf("Epoch time: %llu\n", time_seconds);
  Serial.printf("Time in common format: %s\n", buffer);
}

Output:
----------------------------
tmp64 = 12818
lo1 = 66018750565
1680129548 seconds
554229641535458 microseconds
rdFrac = 12390
Epoch time: 1680129548
Time in common format: 2023-03-29 22:39:08
----------------------------
tmp64 = 12818
lo1 = 66018783334
1680129549 seconds
554229642535488 microseconds
rdFrac = 12391
Epoch time: 1680129549
Time in common format: 2023-03-29 22:39:09
----------------------------
tmp64 = 12818
lo1 = 66018816104
1680129550 seconds
554229643535549 microseconds
rdFrac = 12392
Epoch time: 1680129550
Time in common format: 2023-03-29 22:39:10
 
Try this for your loop()

Code:
void loop() {
  delay(1000);
  uint64_t tmi = rtc_get_64();

  // Convert the time value to seconds
  uint64_t time_seconds = tmi >> 15; 
  uint64_t time_microseconds = ((tmi & 0x7fff) * 1000000ULL) >> 15;
  
  Serial.println("----------------------------");
  Serial.printf("%llu seconds\n", time_seconds);
  Serial.printf("%llu microseconds\n", time_microseconds);
  Serial.printf("rdFrac = %u\n", readRTCfrac());

  struct tm* timeinfo;
  time_t rawtime = (time_t)time_seconds;
  timeinfo = gmtime(&rawtime);
  char buffer[80];
  strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo);
  Serial.printf("Epoch time: %llu\n", time_seconds);
  Serial.printf("Time in common format: %s\n", buffer);
}
 
I ran using your code. I'm curious why rdFrac and the microseconds don't correlate in some way. Should I ignore rdFrac results? Also, again out of curiosity, in rtc_get_64() why do you read the registers twice and compare them? What is the purpose of that?

----------------------------
1680203866 seconds
353393 microseconds
rdFrac = 11580
Epoch time: 1680203866
Time in common format: 2023-03-30 19:17:46
----------------------------
1680203867 seconds
353424 microseconds
rdFrac = 11581
Epoch time: 1680203867
Time in common format: 2023-03-30 19:17:47
----------------------------
1680203868 seconds
353454 microseconds
rdFrac = 11582
Epoch time: 1680203868
Time in common format: 2023-03-30 19:17:48
----------------------------
1680203869 seconds
353485 microseconds
rdFrac = 11583
Epoch time: 1680203869
Time in common format: 2023-03-30 19:17:49
 
I ran using your code. I'm curious why rdFrac and the microseconds don't correlate in some way. Should I ignore rdFrac results? Also, again out of curiosity, in rtc_get_64() why do you read the registers twice and compare them? What is the purpose of that?

...

The RTC unit uses its own crystal - not sure if more accurate - but it will have unique count of time reference from the microseconds running on the core clock.

Did time ref against GPS Satellite PPS once per second signal and second usually some hundreds of micros short of a second.

So if using the RTC - use all of its value as a whole for whatever accuracy it presents so it will be consistent, the two will slip against each other.


The values are read twice and compared because the two reads are not atomic. The RTC on some occasions will update one while the other is read. So reading until both match the prior reading prevents getting one updated and the other not updated where one could rollover into the other.
 
The SNVS_HPRTCMR and SNVS_HPRTCLR registers together form a 64-bit counter incrementing 32768 times a second, asynchronously from the CPU clock. The lower 15 bits are returned in the readRTCfrac() call, which will range from 0 - 32767. This represents a fraction of a second, where 32768 would be a full second. To convert 0 - 32767 to microseconds, multiply by a million and then divide by 32768. In your example an rdFrac of 11583 becomes (11583 * 1000000)/37268 or 353485 microseconds. In my code the "(tmi & 0x7fff)" part extracts the lower 15 bits, the "((tmi & 0x7fff) * 1000000ULL)" multiplies the lower 15 bits by a million, and "((tmi & 0x7fff) * 1000000ULL) >> 15" takes the lower 15 bits, multiplies by a million, then divides by 32768 by shifting to the left 15 bits. Each tick of the RTC accounts for about 30.518 microseconds.

A mathematically equivalent and possibly more efficient equation is multiplying by 15265 and dividing by 512, or "((tmi & 0x7fff) * 15625ULL) >> 9". This is because a million divided by 64 is 15625, and 32768 divided by 64 is 512. This calculation can be performed in 32 bits instead of 64 bits as in "(((uint32_t)tmi & 0x7fff) * 15625UL) >> 9" without fear of overflow. The original "((tmi & 0x7fff) * 1000000ULL) >> 15" will overflow 32 bits, as 32767 * 1000000 is greater than 4294967295. 32767 * 15625 is only 511984375, well within 32 bits.
 
@defragster Thank you for the explanation about comparing two reads of the registers before returning the 64-bit time value.

@dundakitty Your last post was extremely helpful. Thank you for your patience and taking the time to explain so well. So, now I want to go back the other way and set the RTC using time from the NTP server and calling rtc_set_64(). From the NTP server, I'm getting a 64-bit time value that includes seconds and fractional seconds. Is it going to be as simple as converting to seconds-since-1970 and passing that value as-is via rtc_set_64()? I guess I'll give it a try :)
 
NTPv3 and below use 32-bit seconds and 32-bit fractional seconds. NTPv4 use 64-bit seconds and 64-bit fractional seconds. The Teensy 4 RTC has a 64-bit counter incremented 32768 times a second. If you take the seconds component of an NTP packet and convert it to seconds since 1970, that value can go into the upper 49 bits of the Teensy 64-bit counter. The lower 15 bits of the Teensy 64-bit counter should be set to the upper 15 bits of the NTP fractional seconds, without any other conversion.

The following code will set the RTC based on a separate seconds and fractional seconds. It's assuming the seconds has already been converted to seconds-since-1970 and that the fractional seconds have been converted to the 0 - 32767 range.

Code:
void rtc_set_secs_and_frac(uint32_t secs, uint32_t frac)
{
	// 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 = secs >> 17;
	SNVS_LPSRTCMR = ((secs & 0x1ffffUL) << 15) | (frac & 0x7fff);
	// 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;
}
 
Here is my attempt using your new rtc_set_secs_and_frac() subroutine. I included all my code, but I call your function twice, once in setup() where I try and clear the rtc (maybe that's not needed but I did it for my own sanity). Then, I call it again at the end of my getNTPTime() subroutine where I pass "epoch" (secs since 1970) and "fracSecs" via rtc_set_secs_and_frac().

Code:
#include <TimeLib.h>
#include <imxrt.h>
#include <NativeEthernet.h>
#include <NativeEthernetUdp.h>


//#define HWSERIAL1 Serial1   // Rx pin 0, Tx pin 1
//#define HWSERIAL2 Serial2   // Rx pin 7, Tx pin 8

const unsigned long seventyYears = 2208988800UL;  // NTP returns time since 1/1/1900
const int NTP_PACKET_SIZE = 48;                   // NTP time stamp is in the first 48 bytes of the message
const int NTP_WAIT_TIME = 100;                    // Delay in usecs to wait after sending NTP packet to server
unsigned long secsSince1900;
unsigned long highWord;
unsigned long lowWord;
uint32_t epoch;    
unsigned long fracHighWord;
unsigned long fracLowWord;
uint32_t fracSecs;
unsigned int Tcsc;
int isrNTPcnt = -1;
long double Trx,Ttx,Ttxrx;        // Used to decode the NTP packet
char s1[32];                      // String used to output the time value
unsigned int ntpPort = 8888;      // local port to listen for UDP packets

byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

const byte PPS = 5;   // Use pin 5 for PPS interrupt

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xD1
};

IPAddress ip(10, 2, 69, 70);
//IPAddress ip(192, 168, 111, 70);
IPAddress ptpMcastIp(224, 0, 1, 129);
//IPAddress dns(1, 1, 1, 1);
//IPAddress gw(10, 2, 69, 1);
//IPAddress subnet(255, 255, 255, 0);
IPAddress timeServer(10,2,69,199);
//IPAddress timeServer(192,168,111,56);

EthernetUDP ntpUdp;               //Init UDP object
elapsedMicros t_ntp_micros;    // Init microsecond counters
//======================================================
// Custom Subroutines
//======================================================
//-----------------------------------------
//void sendNTPpacket(const char * address) {
void sendNTPpacket(IPAddress address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request 
  packetBuffer[0] = 0xE3;     // LI, Version, Mode
  packetBuffer[1] = 0x00;     // Stratum, or type of clock
  packetBuffer[2] = 0x04;     // Polling Interval
  packetBuffer[3] = 0xEC;     // Peer Clock Precision (-20 => 954 nS )
  packetBuffer[12] = 0x00;
  packetBuffer[13]  = 0x00;
  packetBuffer[14]  = 0x00;
  packetBuffer[15]  = 0x00;

  // all NTP fields have been given values, now
  // you we send a packet requesting a timestamp:
  ntpUdp.beginPacket(address, 123); // NTP requests are sent to port 123
  ntpUdp.write(packetBuffer, NTP_PACKET_SIZE);
  ntpUdp.endPacket();
}
//-----------------------------------------
void getNTPTime() {
  t_ntp_micros = 0;
  // send and NTP packet to the time server  
  sendNTPpacket(timeServer);
  delayMicroseconds(NTP_WAIT_TIME);
  if (ntpUdp.parsePacket() == NTP_PACKET_SIZE) {
    ntpUdp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    // decode the "Receive timestamp" from the packet
    highWord = word(packetBuffer[32], packetBuffer[33]);
    lowWord = word(packetBuffer[34], packetBuffer[35]);
    secsSince1900 = highWord << 16 | lowWord;
    epoch = secsSince1900 - seventyYears;    
    fracHighWord = word(packetBuffer[36], packetBuffer[37]);
    fracLowWord = word(packetBuffer[38], packetBuffer[39]);
    fracSecs = fracHighWord << 16 | fracLowWord;  
    Trx=epoch+(fracSecs/1E9);
    sprintf(s1,"%.9Lf",Trx);    

    // decode the "Transmit timestamp" from the packet
    highWord = word(packetBuffer[40], packetBuffer[41]);
    lowWord = word(packetBuffer[42], packetBuffer[43]);
    secsSince1900 = highWord << 16 | lowWord;
    epoch = secsSince1900 - seventyYears;    
    fracHighWord = word(packetBuffer[44], packetBuffer[45]);
    fracLowWord = word(packetBuffer[46], packetBuffer[47]);
    fracSecs = fracHighWord << 16 | fracLowWord;  
    Ttx=epoch+(fracSecs/1E9);
    sprintf(s1,"%.9Lf",Ttx);  
    Ttxrx = Ttx - Trx;  
    Tcsc = t_ntp_micros - NTP_WAIT_TIME;

    // -----
    Serial.printf("NTP,%lu.%lu\n", epoch, fracSecs);
    rtc_set_secs_and_frac(epoch, fracSecs);
    // -----

    //if (Ttx > 0.0) {
    //  Serial.printf("NTP,%ld,%Lf,%Lf,%Lf\n", Tcsc, Trx, Ttx,Ttxrx);
    //} else {
    //  Serial.printf("Time Not Valid\n");
    //} 
  }  
}
//-----------------------------------------
uint64_t rtc_get_64(void)
{
	uint32_t hi1 = SNVS_HPRTCMR;
        uint64_t tmp64;
	uint32_t lo1 = SNVS_HPRTCLR;
	while (1) {
		uint32_t hi2 = SNVS_HPRTCMR;
		uint32_t lo2 = SNVS_HPRTCLR;
		if (lo1 == lo2 && hi1 == hi2) {
      tmp64 = hi1;
			return (tmp64 << 32) | lo1;
		}
		hi1 = hi2;
		lo1 = lo2;
	}
}
//-----------------------------------------
void rtc_set_64(uint64_t 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 >> 32;
	SNVS_LPSRTCMR = t & 0xffffffff;
	// 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;
}
//-----------------------------------------
void rtc_set_secs_and_frac(uint32_t secs, uint32_t frac)
{
	// 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 = secs >> 17;
	SNVS_LPSRTCMR = ((secs & 0x1ffffUL) << 15) | (frac & 0x7fff);
	// 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;
}
//-----------------------------------------
void isrPPS() {
  isrNTPcnt++;
  if (isrNTPcnt == 0 || isrNTPcnt == 10) { 
    Serial.println("-----------------------------");
    getNTPTime();
    
    // get rtc 
    uint64_t tmi = rtc_get_64();
    uint64_t time_seconds = tmi >> 15; 
    uint32_t time_microseconds32 = ((tmi & 0x7fff) * 15625UL) >> 9;
    Serial.printf("RTC,%llu.%lu\n", time_seconds,time_microseconds32);  
    Serial.println("-----------------------------");    
    if (isrNTPcnt == 10) {isrNTPcnt = 0;}
  } 
}
//-----------------------------------------

//====================================================== 
void setup() {
  Serial.begin(115200);     // Init USB serial
  while (!Serial) {
    ; // Wait for USB serial port to connect
  }  
  
  Ethernet.begin(mac, ip);  // Init ethernet with manual configuration
  ntpUdp.begin(ntpPort);       // Ethernet.begin(mac, ip, dns, gw, subnet); 

  // Output network information
  Serial.printf("MAC Address: %02X:%02X:%02X:%02X:%02X:%02X \n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.print("IP address:  ");
  Serial.println(Ethernet.localIP());
  Serial.print("NTP Server:  ");
  Serial.println(timeServer);
  Serial.println();
  delay(2000);
  attachInterrupt(digitalPinToInterrupt(PPS), isrPPS, RISING);

  // try and clear rtc
  uint32_t rtc_epoch_init = 0x0;
  uint32_t rtc_frac_init = 0x0;
  rtc_set_secs_and_frac(rtc_epoch_init, rtc_frac_init);
}

void loop() {
   
}

Here are few outputs from the start of my program:

MAC Address: DE:AD:BE:EF:FE:XX
IP address: x.x.x.x
NTP Server: x.x.x.x

-----------------------------
RTC,0.473114
-----------------------------
-----------------------------
NTP,1680284874.34920754
RTC,2992898048.391296
-----------------------------
-----------------------------
NTP,1680284884.34196320
RTC,2529165312.391296
-----------------------------
-----------------------------
NTP,1680284894.34362736
RTC,2866806784.391296
-----------------------------
-----------------------------
NTP,1680284904.34461700
RTC,2953314304.391296
-----------------------------
-----------------------------
NTP,1680284914.34234604
RTC,3252158464.391296
-----------------------------
-----------------------------
NTP,1680284924.34527268
RTC,2957508608.391296
-----------------------------
 
The NTP packet does not include fractional time in nanoseconds. The 32-bit field for fractional time is in the full 32-bits. It counts from 0 - 4294967295, or about 233 picoseconds per tick. Dividing the fractional seconds by a billion as in "Trx=epoch+(fracSecs/1E9);" does not give you the appropriate time stamp. Passing the 32-bit fractional second from NTP directly into the "rtc_set_secs_and_frac()" routine is also not correct. I said "The lower 15 bits of the Teensy 64-bit counter should be set to the upper 15 bits of the NTP fractional seconds, without any other conversion." I also said "It's assuming the seconds has already been converted to seconds-since-1970 and that the fractional seconds have been converted to the 0 - 32767 range." I guess you missed the part about "0 - 32767 range". Printing the NTP seconds and fractional seconds as "Serial.printf("NTP,%lu.%lu\n", epoch, fracSecs)" won't be comparable to the code you use to print the RTC timestamp. When printing the RTC timestamp you've converted the fractional 0 - 32767 range into microseconds, while the NTP fractional time is in 233-picosecond units. Dividing the NTP fractional time by 4295 will come close to converting it to microseconds.

I also made a mistake in my code, swapping the high 32-bits of the RTC register with the low 32-bits of the RTC register when setting the RTC.

Try this code instead:

Code:
void rtc_set_64(uint64_t 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 & 0xffffffff;
        SNVS_LPSRTCMR = t >> 32;
	// 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;
}

void rtc_set_secs_and_frac(uint32_t secs, uint32_t frac)
{
	// 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 = ((secs & 0x1ffffUL) << 15) | (frac & 0x7fff);
	SNVS_LPSRTCMR = secs >> 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;
}

//-----------------------------------------
void getNTPTime() {
  t_ntp_micros = 0;
  // send and NTP packet to the time server  
  sendNTPpacket(timeServer);
  delayMicroseconds(NTP_WAIT_TIME);
  if (ntpUdp.parsePacket() == NTP_PACKET_SIZE) {
    ntpUdp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    // decode the "Receive timestamp" from the packet
    highWord = word(packetBuffer[32], packetBuffer[33]);
    lowWord = word(packetBuffer[34], packetBuffer[35]);
    secsSince1900 = highWord << 16 | lowWord;
    epoch = secsSince1900 - seventyYears;    
    fracHighWord = word(packetBuffer[36], packetBuffer[37]);
    fracLowWord = word(packetBuffer[38], packetBuffer[39]);
    fracSecs = fracHighWord << 16 | fracLowWord;  
    Trx=epoch+(fracSecs/4294967296.0);
    sprintf(s1,"%.9Lf",Trx);    

    // decode the "Transmit timestamp" from the packet
    highWord = word(packetBuffer[40], packetBuffer[41]);
    lowWord = word(packetBuffer[42], packetBuffer[43]);
    secsSince1900 = highWord << 16 | lowWord;
    epoch = secsSince1900 - seventyYears;    
    fracHighWord = word(packetBuffer[44], packetBuffer[45]);
    fracLowWord = word(packetBuffer[46], packetBuffer[47]);
    fracSecs = fracHighWord << 16 | fracLowWord;  
    Ttx=epoch+(fracSecs/4294967296.0);
    sprintf(s1,"%.9Lf",Ttx);  
    Ttxrx = Ttx - Trx;  
    Tcsc = t_ntp_micros - NTP_WAIT_TIME;

    // -----
    Serial.printf("NTP,%lu.%lu\n", epoch, fracSecs/4295);
    rtc_set_secs_and_frac(epoch, fracSecs>>17);
    // -----

    //if (Ttx > 0.0) {
    //  Serial.printf("NTP,%ld,%Lf,%Lf,%Lf\n", Tcsc, Trx, Ttx,Ttxrx);
    //} else {
    //  Serial.printf("Time Not Valid\n");
    //} 
  }  
}
 
@dundakitty, sorry for my slowness in understanding. I'm catching on. I'm getting numbers that look way more reasonable to me now, thanks to your help. I'm adding way more resolution than is necessary to the RTC fractional seconds, but I wanted to retain the resolution of the 30.15us, and it seems to work. I'm printing out all the bits (binary) for each register and used that to watch what is going on with the bit-shifting for each parameter. My code is now getting the NTP packet from the server, decoding it, and using it to set the RTC, including the fractional seconds into the lower 15 bits of the rtc (in the range of 0-32767).

As long as I read NTP and set RTC each iteration, I see a difference of about 80us between the read of the NTP and RTC. The problem now is that if I decide to stop reading NTP and setting RTC, hoping to just let the RTC free-run and read it periodically, something happens to the RTC register and the fractional seconds get mangled up. My program reads NTP and sets RTC for the first 5 seconds of a 30 second loop. Betwen loop 6 and 30, I was hoping to monitor the RTC on it's own see how well it keeps time. I pasted a few seconds of output below my code. I wouldn't think that the RTC would need to continuously be spoon-fed time.


Code:
/*
/*
  Objective: Set RTC using NTP once and let RTC free-run (reading periodically)
*/

#include <TimeLib.h>
#include <imxrt.h>
#include <NativeEthernet.h>
#include <NativeEthernetUdp.h>

//#define HWSERIAL1 Serial1   // Rx pin 0, Tx pin 1
//#define HWSERIAL2 Serial2   // Rx pin 7, Tx pin 8

const unsigned long seventyYears = 2208988800UL;  // NTP returns time since 1/1/1900
const int NTP_PACKET_SIZE = 48;                   // NTP time stamp is in the first 48 bytes of the message
const int NTP_WAIT_TIME = 100;                    // Delay in usecs to wait after sending NTP packet to server
const long SECS_PER_TICK32 =  pow(10, 12) / pow(2, 32); 
const long SECS_PER_TICK15 =  pow(10, 12) / pow(2, 15);
unsigned long secsSince1900;
unsigned long highWord;
unsigned long lowWord;
uint32_t epoch;    
unsigned long fracHighWord;
unsigned long fracLowWord;
uint32_t fracSecs, ntp_fracPicos;
unsigned int Tcsc;
int isrNTPcnt = -1;
long double Trx,Ttx,Ttxrx;        // Used to decode the NTP packet
char s1[32];                      // String used to output the time value
unsigned int ntpPort = 8888;      // local port to listen for UDP packets

byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

const byte PPS = 5;   // Use pin 5 for PPS interrupt

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xD1
};

IPAddress ip(10, 2, 69, 70);
//IPAddress ip(192, 168, 111, 70);
IPAddress ptpMcastIp(224, 0, 1, 129);
//IPAddress dns(1, 1, 1, 1);
//IPAddress gw(10, 2, 69, 1);
//IPAddress subnet(255, 255, 255, 0);
IPAddress timeServer(10,2,69,199);
//IPAddress timeServer(192,168,111,56);

EthernetUDP ntpUdp;               //Init UDP object
elapsedMicros t_ntp_micros;    // Init microsecond counters
elapsedMicros t_isr_micros;
//======================================================
// Custom Subroutines
//======================================================
//-----------------------------------------
//void sendNTPpacket(const char * address) {
void sendNTPpacket(IPAddress address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request 
  packetBuffer[0] = 0xE3;     // LI, Version, Mode
  packetBuffer[1] = 0x00;     // Stratum, or type of clock
  packetBuffer[2] = 0x04;     // Polling Interval
  packetBuffer[3] = 0xEC;     // Peer Clock Precision (-20 => 954 nS )
  packetBuffer[12] = 0x00;
  packetBuffer[13]  = 0x00;
  packetBuffer[14]  = 0x00;
  packetBuffer[15]  = 0x00;

  // all NTP fields have been given values, now
  // you we send a packet requesting a timestamp:
  ntpUdp.beginPacket(address, 123); // NTP requests are sent to port 123
  ntpUdp.write(packetBuffer, NTP_PACKET_SIZE);
  ntpUdp.endPacket();
}
//-----------------------------------------
void getNTPTime() {
  t_ntp_micros = 0;
  sendNTPpacket(timeServer);        // send and NTP packet to the time server  
  delayMicroseconds(NTP_WAIT_TIME);
  if (ntpUdp.parsePacket() == NTP_PACKET_SIZE) {
    ntpUdp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    // decode the "Transmit timestamp" from the packet
    highWord = word(packetBuffer[40], packetBuffer[41]);
    lowWord = word(packetBuffer[42], packetBuffer[43]);
    secsSince1900 = highWord << 16 | lowWord;
    epoch = secsSince1900 - seventyYears;    
    fracHighWord = word(packetBuffer[44], packetBuffer[45]);
    fracLowWord = word(packetBuffer[46], packetBuffer[47]);
    fracSecs = fracHighWord << 16 | fracLowWord;  

    // Print out values/register contents
    ntp_fracPicos = fracSecs * SECS_PER_TICK32;       
    Serial.println("--------NTP Read----------");
    Serial.print("HLWord:  ");
    printBinary(highWord);
    printBinary(lowWord);
    Serial.println();
    Serial.print("FHLWord: ");
    printBinary(fracHighWord);
    printBinary(fracLowWord);
    Serial.println();
    Serial.print("Fsecs:   ");
    printBinary(fracSecs);
    Serial.print("(");
    Serial.print(fracSecs);
    Serial.println(")");
    Serial.print("Fsec17:  ");
    printBinary(fracSecs >> 17);
    Serial.print("(");
    Serial.print(fracSecs >> 17);
    Serial.println(")");
    Serial.print("s1900:   ");
    printBinary(secsSince1900);
    Serial.print("(");
    Serial.print(secsSince1900);
    Serial.println(")");
    Serial.print("s1970:   ");
    printBinary(epoch);
    Serial.print("(");
    Serial.print(epoch); 
    Serial.println(")");          
    Serial.print("NTP:     ");
    Serial.print(epoch);
    Serial.print(".");
    Serial.printf("%012lu\n", ntp_fracPicos);
    Serial.println("-----------------------------");

    rtc_set_secs_and_frac(epoch, fracSecs >> 17);  // Set rtc
  }  
}
//-----------------------------------------
void isrPPS() {
  t_isr_micros = 0;
  isrNTPcnt++;
  
  if (isrNTPcnt == 0) {rtc_set_64(0);}    // clear rtc one time at the start
  if (isrNTPcnt < 5) {getNTPTime();}      // get NTP and set RTC first 5 seconds of each 30 second period
  
  // get rtc 
  Serial.println("--------RTC Read----------"); 
  uint64_t tmi = rtc_get_64();
  uint64_t time_seconds = tmi >> 15; 
  Serial.print("tfracs:  ");
  int time_fracs = (tmi & 0x7fff);
  int rtc_fracMicros = time_fracs * SECS_PER_TICK15;
  printBinary(time_fracs);
  Serial.print("(");
  Serial.print(time_fracs);
  Serial.println(")");
    
  Serial.print("RTC64:   ");
  Serial.println(tmi);
  Serial.print("HCMRCLR: ");
  printBinary(SNVS_HPRTCMR);
  printBinary(SNVS_HPRTCLR);
  Serial.println();  
  Serial.print("RTC:     ");
  Serial.print(time_seconds);
  Serial.print(".");     
  Serial.printf("%012d\n", rtc_fracMicros);
  Serial.println("-----------------------------");
  int diff =  rtc_fracMicros - ntp_fracPicos;
  Serial.printf("diff: %012d\n", diff);
  Serial.println();
  if (isrNTPcnt == 30) {isrNTPcnt = 0;}
}
//-----------------------------------------
void rtc_set_secs_and_frac(uint32_t secs, uint32_t frac)
{
	// 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 = ((secs & 0x1ffffUL) << 15) | (frac & 0x7fff);
	SNVS_LPSRTCMR = secs >> 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;
}
//-----------------------------------------
void rtc_set_64(uint64_t 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 & 0xffffffff;
        SNVS_LPSRTCMR = t >> 32;
	// 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;
}
//-----------------------------------------
uint64_t rtc_get_64(void)
{
	uint32_t hi1 = SNVS_HPRTCMR;
  uint64_t tmp64;
	uint32_t lo1 = SNVS_HPRTCLR;
	while (1) {
		uint32_t hi2 = SNVS_HPRTCMR;
		uint32_t lo2 = SNVS_HPRTCLR;
		if (lo1 == lo2 && hi1 == hi2) {
      tmp64 = hi1;
			return (tmp64 << 32) | lo1;
		}
		hi1 = hi2;
		lo1 = lo2;
	}
}
//-----------------------------------------
void printBinary(uint32_t num) {
  for (int i = 3; i > -1; i--){
        BinaryStrZeroPad((num >> (8*i)) & 0xFF, 7);
        Serial .print(" ");
  }
  //Serial.println();
}
void BinaryStrZeroPad(int Number,char ZeroPadding){
//ZeroPadding = nth bit, e.g for a 16 bit number nth bit = 15
signed char i=ZeroPadding;

	while(i>=0){
	    if((Number & (1<<i)) > 0) Serial.write('1');
	    else Serial.write('0');
	    --i;
	}
}
//-----------------------------------------
//====================================================== 
void setup() {
  Serial.begin(115200);     // Init USB serial
  while (!Serial) {
    ; // Wait for USB serial port to connect
  }  
  
  Ethernet.begin(mac, ip);  // Init ethernet with manual configuration
  ntpUdp.begin(ntpPort);       // Ethernet.begin(mac, ip, dns, gw, subnet); 

  // Output network information
  Serial.printf("MAC Address: %02X:%02X:%02X:%02X:%02X:%02X \n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.print("IP address:  ");
  Serial.println(Ethernet.localIP());
  Serial.print("NTP Server:  ");
  Serial.println(timeServer);
  Serial.println();
  delay(2000);
  attachInterrupt(digitalPinToInterrupt(PPS), isrPPS, RISING);
}

void loop() {
   
}


In output below, after the 3rd pair of NTP/RTC reads, I skip reading NTP and only read RTC. I notice that that on the 4th second, where I do not read NTP, the epoch (seconds) do not increment and the fractional seconds change completely and they no longer increment.

diff = RTCfracsecs - NTPfracsecs.


--------NTP Read----------
HLWord: 00000000 00000000 11100111 11010011 00000000 00000000 00111011 11010010
FHLWord: 00000000 00000000 00000000 00010110 00000000 00000000 10001110 10011100
Fsecs: 00000000 00010110 10001110 10011100 (1478300)
Fsec17: 00000000 00000000 00000000 00001011 (11)
s1900: 11100111 11010011 00111011 11010010 (3889380306)
s1970: 01100100 00101000 10111101 01010010 (1680391506)
NTP: 1680391506.000342965600
-----------------------------
--------RTC Read----------
tfracs: 00000000 00000000 00000000 00001110 (14)
RTC64: 55063068868622
HCMRCLR: 00000000 00000000 00110010 00010100 01011110 10101001 00000000 00001110
RTC: 1680391506.000427246092
-----------------------------
diff: 000084280492
--------NTP Read----------
HLWord: 00000000 00000000 11100111 11010011 00000000 00000000 00111011 11010010
FHLWord: 00000000 00000000 00000000 00100010 00000000 00000000 10111001 10001110
Fsecs: 00000000 00100010 10111001 10001110 (2275726)
Fsec17: 00000000 00000000 00000000 00010001 (17)
s1900: 11100111 11010011 00111011 11010010 (3889380306)
s1970: 01100100 00101000 10111101 01010010 (1680391506)
NTP: 1680391506.000527968432
-----------------------------
--------RTC Read----------
tfracs: 00000000 00000000 00000000 00010100 (20)
RTC64: 55063068868628
HCMRCLR: 00000000 00000000 00110010 00010100 01011110 10101001 00000000 00010100
RTC: 1680391506.000610351560
-----------------------------
diff: 000082383128
--------NTP Read----------
HLWord: 00000000 00000000 11100111 11010011 00000000 00000000 00111011 11010011
FHLWord: 00000000 00000000 00000000 00111001 00000000 00000000 11101101 01111010
Fsecs: 00000000 00111001 11101101 01111010 (3796346)
Fsec17: 00000000 00000000 00000000 00011100 (28)
s1900: 11100111 11010011 00111011 11010011 (3889380307)
s1970: 01100100 00101000 10111101 01010011 (1680391507)
NTP: 1680391507.000880752272
-----------------------------
--------RTC Read----------
tfracs: 00000000 00000000 00000000 00011111 (31)
RTC64: 55063068901407
HCMRCLR: 00000000 00000000 00110010 00010100 01011110 10101001 10000000 00011111
RTC: 1680391507.000946044918
-----------------------------
diff: 000065292646
--------RTC Read----------
tfracs: 00000000 00000000 01111111 11110111 (32759)
RTC64: 55063068934135
HCMRCLR: 00000000 00000000 00110010 00010100 01011110 10101001 11111111 11111000
RTC: 1680391507.-01002042266
-----------------------------
diff: -01882794538

--------RTC Read----------
tfracs: 00000000 00000000 01111111 11110111 (32759)
RTC64: 55063068966903
HCMRCLR: 00000000 00000000 00110010 00010100 01011110 10101010 01111111 11111000
RTC: 1680391508.-01002042266
-----------------------------
diff: -01882794538

--------RTC Read----------
tfracs: 00000000 00000000 01111111 11110111 (32759)
RTC64: 55063068999671
HCMRCLR: 00000000 00000000 00110010 00010100 01011110 10101010 11111111 11111000
RTC: 1680391509.-01002042266
-----------------------------
diff: -01882794538
--------RTC Read----------
tfracs: 00000000 00000000 01111111 11110111 (32759)
RTC64: 55063069032439
HCMRCLR: 00000000 00000000 00110010 00010100 01011110 10101011 01111111 11111000
RTC: 1680391510.-01002042266
-----------------------------
diff: -01882794538
 
Here's the problem:
Code:
const long SECS_PER_TICK32 =  pow(10, 12) / pow(2, 32); 
const long SECS_PER_TICK15 =  pow(10, 12) / pow(2, 15);
...
ntp_fracPicos = fracSecs * SECS_PER_TICK32;       
...
int rtc_fracMicros = time_fracs * SECS_PER_TICK15;
"const long" creates 32-bit constants, "SECS_PER_TICK32" with the value 232, "SECS_PER_TICK15" with the value 30,517,578. "SECS_PER_TICK32" is almost correct, as it should be close to 232.831. That's why with integer math we use a combination of a multiply and divide, so we can preserve accuracy. "fracSecs" is an unsigned 32-bit variable in 232.831 picosecond units. Multiplying by 232 will be sure to overflow the 32-bits, so the math operations have to be done in greater than 32-bit precision.
The same can be said for "SECS_PER_TICK15". A 15-bit value in a 32-bit variable will overflow when multiplied by 30,517,578.

I recommend dropping the conversion to picoseconds entirely. Instead of printing out as fractional seconds with a ".", just print out the timestamp using a space between the seconds and fractional seconds. Print out the NTP fractional part as-is, but convert the RTC fractional stamp by shifting it up 17. The two fractions will be directly comparable that way, without any math overflow. The "diff" value would then be "(time_fracs << 17) - fracSecs".
 
It's taken a while but I finally got my program working like a wanted it to. In my setup, I have four mini-gps devices and a PC feeding serial data to serials 3,4,6,7, and 8. Three of the GPS devices (BDS GPS ATGM336H) are sending NMEA strings at 10Hz (115K baud) and one (NavStar-mini) device is sending nmea strings, 1Hz, at 9600 baud. I also have a single PC serial port (ttyS0) connected to the serial 7 that I send a time-hack (nanoseconds since 1970) at 10Hz (115K baud). So, five serial feeds in total. In setup(), I configure my three mini-gps devices, specifying nmea strings, rate, and baud (I cannot configure one of NavStar-mini for some reason). After that I get the time from the NTP server (8 times), get an average and halve it, and then I set the RTC.

When the first character gets read on a given serial port, I read the latest getRTC() timestamp. I write the timestamp to the the string that gets sent out a UDP socket along witht the nmea string. I haven't gotten to PTP yet, this is my version 1. Thanks for everyone's help.

Every 60 seconds I sync up the RTC with NTP like I did in setup.

Code:
// This program simulates incoming sensor data on four serial ports using 4 GPS devices and a PC port.
// One of the GPS devices provides all nmea strings at 9600 baud at a 1Hz rate (cannot be modified), 
// the other 3 GPS devices can be customized to send all nmea strings at different output rates (1,2,4,5,10Hz)
// ;gga and gvt only, or just gga at the same baud rate for all three (currently 115200 is the default). The PC 
// port is used to feed a time-hack to the Teensy serial port at a 10Hz rate - to simulate yet another serial feed to the teensy.
//
//  NTP/RTC:
//     In setup(), prior to calling initNTPrtc() 8 times, the microsecond timer is started; the total time
//     that it took to get ntp time 8 times is averaged, halved, and converted to counts; added to the counts, and 
//     the final counts is used to set the RTC. rtcThread() gets kicked off and reads the RTC periodically 
//     (less than 1ms) constantly updating rtcSeconds and rtcFracs. The serial threads grab 
//     rtcFracs when first character is read and both get written ahead of the nmea string that is written to the 
//     UDP socket.

// Currently, initNTPrtc() is called every minute and sets the RTC. While PPS is present, I'm not using it at this time for sync'ing time
// because I have to simulate this operation as if PPS is not available.

// notes:
// When using a TI MAX3232 RS232 driver, remember that channel 2 RS232 is inverted
//
// * Note about using chronyd on a linux system as NTP server:
//   Make sure to set the "allow x.x.x.0/24" in /etc/chrony.conf to the appropriate subnet.

#include <EEPROM.h>
#include <imxrt.h>
#include <NativeEthernet.h>
#include <NativeEthernetUdp.h>
#include <TeensyThreads.h>
#include <TimeLib.h>
extern "C" uint32_t set_arm_clock(uint32_t frequency); // required prototype to set CPU clock

#define CLOCK_FREQ 816000000 // 720, 816, 912(c), 960(c), 1008(c)  c=cooling required

//------------------------
// Serial vars
//------------------------
#define HWSERIAL1 Serial1 //rx = pin 0, tx = pin 1
#define HWSERIAL2 Serial2 //rx = pin 7, tx = pin 8
#define GPSHWSERIAL3 Serial3 //rx = pin 15, tx = pin 14 ; NAVSTAR
#define GPSHWSERIAL4 Serial4 //rx = pin 16, tx = pin 17
#define GPSHWSERIAL6 Serial6 //rx = pin 25, tx = pin 24
#define HWSERIAL7 Serial7 //rx = pin 28, tx = pin 29 ; PC
#define GPSHWSERIAL8 Serial8 //rx = pin 34, tx = pin 35

int defaultbaud3=9600, defaultbaud4=115200, defaultbaud6=115200;
int defaultbaud7=115200,defaultbaud8=115200, defaultbaudUSB=115200;
int defaultrate4=10, defaultrate6=10, defaultrate8=10;
int defaultnmea4=2, defaultnmea6=2, defaultnmea8=2; //0=all,1=gga,2=gga+gvt

String inData3, inData4, inData6, inData7, inData8;
char ser_s1[32], ser_s2[16];

char GPSbaud_4800[]="$PCAS01,0*1C\r\n"; // GPS CAS Instructions
char GPSbaud_9600[]="$PCAS01,1*1D\r\n";
char GPSbaud_19200[]="$PCAS01,2*1E\r\n";
char GPSbaud_38400[]="$PCAS01,3*1F\r\n";
char GPSbaud_57600[]="$PCAS01,4*18\r\n";
char GPSbaud_115200[]="$PCAS01,5*19\r\n";
char GPSupdatefreq_1Hz[]="$PCAS02,1000*2E\r\n";
char GPSupdatefreq_2Hz[]="$PCAS02,500*1A\r\n";
char GPSupdatefreq_4Hz[]="$PCAS02,250*18\r\n";
char GPSupdatefreq_5Hz[]="$PCAS02,200*1D\r\n";
char GPSupdatefreq_10Hz[]="$PCAS02,100*1E\r\n";
char GPSnmeaEnableALL[]="$PCAS03,1,1,1,1,1,1,1,1,1,1,1,1,1,1*02\r\n";
char GPSnmeaEnableGGA_only[]="$PCAS03,1,0,0,0,0,0,0,0,0,0,0,0,0,0*03\r\n";
char GPSnmeaEnableGGA_VTG_only[]="$PCAS03,1,0,0,0,0,1,0,0,0,0,0,0,0,0*02\r\n";
char GPSreset_hot[]="$PCAS10,0*1C\r\n";
char GPSreset_warm[]="$PCAS10,1*1D\r\n";
char GPSreset_cold[]="$PCAS10,2*1E\r\n";
char GPSreset_factory[]="$PCAS10,3*1F\r\n";

//------------------------
// Network vars
//------------------------
//   Interface Settings
byte mac[] = {                            
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xD5
};
IPAddress ip(10, 2, 69, 70); // Ignored when using dhcp
IPAddress bcastip(10,2,69,255);
IPAddress ptpMcastIp(224, 0, 1, 129);
IPAddress timeServer(10,2,69,199);

//   Socket Settings (Interface can handle up to 8 sockets)
EthernetUDP Udp3;    //chan 3 udp socket
EthernetUDP Udp4;    //chan 4 udp socket
EthernetUDP Udp6;    //chan 6 udp socket
EthernetUDP Udp7;    //chan 7 udp socket
EthernetUDP Udp8;    //chan 8 udp socket
EthernetUDP ntpUdp;  //NTP socket

//   UDP Source ports (49152 through 65535)
uint16_t udp_srcport3=49253, udp_srcport4=49254, udp_srcport6=49254;
uint16_t udp_srcport7=49257, udp_srcport8=49258, ntpudp_srcport=49250;    

//   UDP Destination ports (should eventually be selectable)
uint16_t udp_dstport3=10543, udp_dstport4=10544, udp_dstport6=10546;
uint16_t udp_dstport7=10547, udp_dstport8=10548, ntpudp_dstport=123;

const int NMEA_SIZE = 128;
const int NTP_PACKET_SIZE = 48; 
const int NTP_WAIT_TIME = 5;
const int NTP_SYNC_FREQ = 59; //seconds
const int NTP_RTC_CAL = 60; // this number represents number of counts to add to value used to set the RTC. 1 cnt = ~30.5us

//------------------------
// General Purpose vars
//------------------------
const int rstALLpin = 32;
const byte PPS = 31;   // Use pin 31 for PPS interrupt
int PPSpin = LOW;

const float SECS_PER_TIC_RTC = 1.0 / pow(2, 15);
const double SECS_PER_TIC_NTP = 1.0 / pow(2, 32);
unsigned int Tcsc, initLock=0;
unsigned long secsSince1900, highWord,lowWord, fracHighWord, fracLowWord, seventyYears = 2208988800UL;
uint32_t epoch, ntpFracSecs;    
uint64_t rtcSeconds;
double rtcFracf;
int rtcFracint=0;
double rtcSecondsf, ntpFracf;
char T0secs[]="0000000000000000", T0fracs[]="9999999";  //time set to RTC
char rtcSecStr[]="0000000000000000", rtcFracStr[]="9999999"; //used by getRTC()
long double Trx,Ttx,Ttxrx;
byte packetBuffer[NTP_PACKET_SIZE];
bool ntpFail = false;

//Allocate some space in EEPROM for storing serial settings
unsigned int eeAddrBaudGPS4 = 0;
unsigned int eeAddrGPSupdatefreq4 = sizeof(GPSbaud_115200) + 1;
unsigned int eeAddrNmeaGPS4 = eeAddrGPSupdatefreq4 + sizeof(GPSupdatefreq_1Hz) + 1;
unsigned int eeAddrBaudGPS6 = eeAddrNmeaGPS4 + sizeof(GPSnmeaEnableALL) +1;
unsigned int eeAddrGPSupdatefreq6 = eeAddrBaudGPS6 + sizeof(GPSbaud_115200) + 1;
unsigned int eeAddrNmeaGPS6 = eeAddrGPSupdatefreq6 + sizeof(GPSupdatefreq_1Hz) + 1;
unsigned int eeAddrBaudGPS8 = eeAddrNmeaGPS6 + sizeof(GPSnmeaEnableALL) +1;
unsigned int eeAddrGPSupdatefreq8 = eeAddrBaudGPS8 + sizeof(GPSbaud_115200) + 1;
unsigned int eeAddrNmeaGPS8 = eeAddrGPSupdatefreq8 + sizeof(GPSupdatefreq_1Hz) + 1;
String eeGPSbaud4, eeGPSupdatefreq4, eeGPSnmea4, eeGPSbaud6, eeGPSupdatefreq6, eeGPSnmea6, eeGPSbaud8, eeGPSupdatefreq8, eeGPSnmea8;

// Timers/counters
elapsedMicros t_micros;
elapsedMillis t_millis;
elapsedMicros t_ntp_micros;
int clockset_cnt =0;

// semaphores
Threads::Mutex Udp_mutex;
Threads::Mutex serial_mutex;
//======================================================
// Custom Subroutines
//======================================================
void sendNTPpacket(IPAddress address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form an NTP packet 
  packetBuffer[0] = 0xE3;     // LI, Version, Mode
  packetBuffer[1] = 0x00;     // Stratum, or type of clock
  packetBuffer[2] = 0x04;     // Polling Interval
  packetBuffer[3] = 0xEC;     // Peer Clock Precision (-20 => 954 nS )
  packetBuffer[12] = 0x00;
  packetBuffer[13]  = 0x00;
  packetBuffer[14]  = 0x00;
  packetBuffer[15]  = 0x00;

  // Send the packet to the NTP server
  ntpUdp.beginPacket(address, 123); // NTP requests are sent to port 123
  ntpUdp.write(packetBuffer, NTP_PACKET_SIZE);
  ntpUdp.endPacket();
}
//-----------------------------------------
void getNTPTime() {
  sendNTPpacket(timeServer);        // send and NTP packet to the time server  
  epoch = 0; 
  t_millis = 0;
  while(1) {
    if (t_millis > 999) {
      ntpFail = true;
      break;
    }
    if (ntpUdp.parsePacket() == NTP_PACKET_SIZE) {
      ntpUdp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
      // decode the "Transmit timestamp" from the packet
      highWord = word(packetBuffer[40], packetBuffer[41]);
      lowWord = word(packetBuffer[42], packetBuffer[43]);
      secsSince1900 = highWord << 16 | lowWord;
      epoch = secsSince1900 - seventyYears;    
      fracHighWord = word(packetBuffer[44], packetBuffer[45]);
      fracLowWord = word(packetBuffer[46], packetBuffer[47]);
      ntpFracSecs = fracHighWord << 16 | fracLowWord;  

      Serial.println("--------NTP Read----------");
      Serial.print("NTP:     ");
      Serial.print(epoch);
      Serial.print(" ");
      ntpFracf = ntpFracSecs * SECS_PER_TIC_NTP;
      Serial.printf("%lf\n", ntpFracf);
      Serial.println("-----------------------------");
      break;
    } 
  } 
}
//-----------------------------------------
void rtc_set_secs_and_frac(uint32_t secs, uint32_t frac)
{
	// 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 = ((secs & 0x1ffffUL) << 15) | (frac & 0x7fff);
	SNVS_LPSRTCMR = secs >> 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;
}
//-----------------------------------------
uint64_t rtc_get_64(void)
{
	uint32_t hi1 = SNVS_HPRTCMR;
  uint64_t tmp64;
	uint32_t lo1 = SNVS_HPRTCLR;
	while (1) {
		uint32_t hi2 = SNVS_HPRTCMR;
		uint32_t lo2 = SNVS_HPRTCLR;
		if (lo1 == lo2 && hi1 == hi2) {
      tmp64 = hi1;
			return (tmp64 << 32) | lo1;
		}
		hi1 = hi2;
		lo1 = lo2;
	}
}
//-----------------------------------------
void printBinary(uint32_t num) {
  for (int i = 3; i > -1; i--){
        BinaryStrZeroPad((num >> (8*i)) & 0xFF, 7);
        Serial .print(" ");
  }
}
void BinaryStrZeroPad(int Number,char ZeroPadding){
//ZeroPadding = nth bit, e.g for a 16 bit number nth bit = 15
signed char i=ZeroPadding;

	while(i>=0){
	    if((Number & (1<<i)) > 0) Serial.write('1');
	    else Serial.write('0');
	    --i;
	}
}
//-----------------------------------------
void getRTC() {
  uint64_t tmi = rtc_get_64();
  rtcSeconds = tmi >> 15; 
  int rtcFracSecs = (tmi & 0x7fff);
  rtcFracf = rtcFracSecs * SECS_PER_TIC_RTC;
  rtcFracint = rtcFracf * 1E6;
  sprintf(rtcSecStr, "%llu ", rtcSeconds);
  sprintf(rtcFracStr, "%06d ", rtcFracint);  
}
//-----------------------------------------
/*
void printUTCtime() {
    unsigned int xepoch = rtcSeconds;
    // print the hour, minute and second:
    Serial.print("UTC:     ");    
    Serial.printf("%d %d %d ", year(rtcSeconds), month(rtcSeconds), day(rtcSeconds));
    Serial.print((xepoch  % SECS_PER_DAY) / SECS_PER_HOUR); // print the hour (86400 equals secs per day)
    Serial.print(':');
    if (((xepoch % SECS_PER_HOUR) / SECS_PER_MIN) < 10) {
      // In the first 10 minutes of each hour, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.print((xepoch  % SECS_PER_HOUR) / SECS_PER_MIN); // print the minute 
    Serial.print(':');
    if ((xepoch % SECS_PER_MIN) < 10) {
      // In the first 10 seconds of each minute, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.println(xepoch % SECS_PER_MIN); // print the second
} 
*/
//-----------------------------------------
void serial3Thread() {
  while (1) {
    char rtcFracStrTmp[NMEA_SIZE]="";
    serial_mutex.lock();
    while (GPSHWSERIAL3.available() > 0) {
      char received = GPSHWSERIAL3.read();
      if (strlen(rtcFracStrTmp) < strlen(rtcFracStr))
        strcat(rtcFracStrTmp, rtcFracStr);
      inData3 += received;
      if (received == '\n') {
        char chan3[NMEA_SIZE] = "Rx3: ";
        strcat(chan3, rtcSecStr);
        strcat(chan3, rtcFracStr);
        char charBuf3[NMEA_SIZE];
        //memset(charBuf3, 0, NMEA_SIZE);
        inData3.toCharArray(charBuf3, NMEA_SIZE);
        strcat(chan3, charBuf3);
        Udp_mutex.lock();
        Udp3.beginPacket(bcastip, udp_dstport3);
        Udp3.write(chan3, strlen(chan3));
        Udp3.endPacket();
        Udp_mutex.unlock();
        inData3 = "";
      }
    }
    serial_mutex.unlock();
    threads.yield();
  }
}
//-----------------------------------------
void serial4Thread() {
  while (1) {
    char rtcFracStrTmp[NMEA_SIZE]="";
    serial_mutex.lock();
    while (GPSHWSERIAL4.available() > 0) {
      char received = GPSHWSERIAL4.read();
      if (strlen(rtcFracStrTmp) < strlen(rtcFracStr))
        strcat(rtcFracStrTmp, rtcFracStr);
      inData4 += received;
      if (received == '\n') {
        char chan4[NMEA_SIZE] = "Rx4: ";
        strcat(chan4, rtcSecStr);
        strcat(chan4, rtcFracStr);
        char charBuf4[NMEA_SIZE];
        //memset(charBuf4, 0, NMEA_SIZE);
        inData4.toCharArray(charBuf4, NMEA_SIZE);
        strcat(chan4, charBuf4);
        Udp_mutex.lock();
        Udp4.beginPacket(bcastip, udp_dstport4);
        Udp4.write(chan4, strlen(chan4));
        Udp4.endPacket();
        Udp_mutex.unlock();
        inData4 = "";
      }
    }
    serial_mutex.unlock();
    threads.yield();
  }
}
//-----------------------------------------
void serial6Thread() { 
  while (1) {
    char rtcFracStrTmp[NMEA_SIZE]="";
    serial_mutex.lock();
    while (GPSHWSERIAL6.available() > 0) {
      char received = GPSHWSERIAL6.read();
      if (strlen(rtcFracStrTmp) < strlen(rtcFracStr))
        strcat(rtcFracStrTmp, rtcFracStr);
      inData6 += received;
      if (received == '\n') {
        char chan6[NMEA_SIZE] = "Rx6: ";
        strcat(chan6, rtcSecStr);
        strcat(chan6, rtcFracStr);
        char charBuf6[NMEA_SIZE];
        //memset(charBuf6, 0, NMEA_SIZE);
        inData6.toCharArray(charBuf6, NMEA_SIZE);
        strcat(chan6, charBuf6);
        Udp_mutex.lock();
        Udp6.beginPacket(bcastip, udp_dstport6);
        Udp6.write(chan6, strlen(chan6));
        Udp6.endPacket();
        Udp_mutex.unlock();
        inData6 = "";
      }
    }
    serial_mutex.unlock();
    threads.yield();
  }
}
//-----------------------------------------
void serial7Thread() {
  while (1) {
    char rtcFracStrTmp[NMEA_SIZE]="";
    serial_mutex.lock();
    while (HWSERIAL7.available() > 0) {
      char received = HWSERIAL7.read();
      if (strlen(rtcFracStrTmp) < strlen(rtcFracStr))
        strcat(rtcFracStrTmp, rtcFracStr);
      inData7 += received;
      if (received == '\n') {
        char chan7[NMEA_SIZE] = "Rx7: ";
        strcat(chan7, rtcSecStr);
        strcat(chan7, rtcFracStr);
        char charBuf7[NMEA_SIZE];
        //memset(charBuf7, 0, NMEA_SIZE);
        inData7.toCharArray(charBuf7, NMEA_SIZE);
        strcat(chan7, charBuf7);
        Udp_mutex.lock();
        Udp7.beginPacket(bcastip, udp_dstport7);
        Udp7.write(chan7, strlen(chan7));
        Udp7.endPacket();
        Udp_mutex.unlock();
        inData7 = "";
      }
    }
    serial_mutex.unlock();
    threads.yield();
  }
}
//-----------------------------------------
void serial8Thread() {
  while (1) {
    char rtcFracStrTmp[NMEA_SIZE]="";
    serial_mutex.lock();
    while (GPSHWSERIAL8.available() > 0) {
      char received = GPSHWSERIAL8.read();
      if (strlen(rtcFracStrTmp) < strlen(rtcFracStr))
        strcat(rtcFracStrTmp, rtcFracStr); 
      inData8 += received;
      if (received == '\n') {
        char chan8[NMEA_SIZE] = "Rx8: ";
        strcat(chan8, rtcSecStr);
        strcat(chan8, rtcFracStrTmp);
        char charBuf8[NMEA_SIZE];
        //memset(charBuf8, 0, NMEA_SIZE);
        inData8.toCharArray(charBuf8, NMEA_SIZE);
        strcat(chan8, charBuf8); 
        Udp_mutex.lock();
        Udp8.beginPacket(bcastip, udp_dstport8);
        Udp8.write(chan8, strlen(chan8));
        Udp8.endPacket();
        Udp_mutex.unlock();
        inData8 = "";
      }
    }
    serial_mutex.unlock();
    threads.yield();
  }
}
//-----------------------------------------
// Clear the EEPROM
void eepromCLR() {
  for ( unsigned int i = 0 ; i < EEPROM.length() ; i++ )
    EEPROM.write(i, 0);
  digitalWrite(13, HIGH);
}
//-----------------------------------------
//--ISR for PPS -------------
void isrPPS() {
  PPSpin = HIGH;
  //Serial.println("PPS");
}
//-----------------------------------------
// Set GPS device baud rate 
void GPSbaudSet(HardwareSerial *serport, int baud) {
  serial_mutex.lock();
  switch (baud) {
    case 9600: 
      serport->print(GPSbaud_9600);
      break;
    case 19200: 
      serport->print(GPSbaud_19200);
      break;   
    case 38400:
      serport->print(GPSbaud_38400);
      break;
    case 56700:
      serport->print(GPSbaud_57600);
      break;
    case 115200:
      serport->print(GPSbaud_115200);
      break;
    default:
            break;
  }
  serport->flush();
  serport->end();
  serport->begin(baud);
  serial_mutex.unlock();

  //this eeprom write means nothing at this point
  //to make meaningful, need to write settings for all ports individually
  //EEPROM.put(eeAddrBaud, GPSbaud_115200);
}
//-----------------------------------------
void GPSnmeaRateSet(HardwareSerial *serport, int rate) {
  serial_mutex.lock();
  switch (rate) {
    case 1: 
      serport->print(GPSupdatefreq_1Hz);
      break;
    case 2: 
      serport->print(GPSupdatefreq_2Hz);
      break;   
    case 4:
      serport->print(GPSupdatefreq_4Hz);
      break;
    case 5:
      serport->print(GPSupdatefreq_5Hz);
      break;
    case 10:
      serport->print(GPSupdatefreq_10Hz);
      break;
    default:
            break;
  }
  serport->flush();
  serial_mutex.unlock();
}
//-----------------------------------------
// Hot reset; no init, all data in backup buffer is valid.
void GPShotreset() {
  GPSHWSERIAL4.print(GPSreset_hot);
  GPSHWSERIAL4.flush();
  GPSHWSERIAL6.print(GPSreset_hot);
  GPSHWSERIAL6.flush();
  GPSHWSERIAL8.print(GPSreset_hot);
  GPSHWSERIAL8.flush();
  //delay(1000);
}
//-----------------------------------------
// Warm reset; no init, ephemeris is cleared.
void GPSwarmreset() {
  GPSHWSERIAL4.print(GPSreset_warm);
  GPSHWSERIAL4.flush();
  GPSHWSERIAL6.print(GPSreset_warm);
  GPSHWSERIAL6.flush();
  GPSHWSERIAL8.print(GPSreset_warm);
  GPSHWSERIAL8.flush();
  //delay(1000);
}
//-----------------------------------------
// Cold reset; no init, all data except configuration in backup
// buffer is cleared.
void GPScoldreset() {
  GPSHWSERIAL4.print(GPSreset_cold);
  GPSHWSERIAL4.flush();
  GPSHWSERIAL6.print(GPSreset_cold);
  GPSHWSERIAL6.flush();
  GPSHWSERIAL8.print(GPSreset_cold);
  GPSHWSERIAL8.flush();
  //delay(1000);
}
//-----------------------------------------
// Factory reset: output all NMEA strings 1Hz at 9600 baud
void GPSfactoryreset(HardwareSerial *serport) {
  // loop through all baud rates sending the GPSreset_factory
  // CAS-string each iteration.
  serial_mutex.lock();

  int i, br[] = {9600, 19200, 38400, 57600, 115200};
  //Serial.println("Cycling through all baud rates...");
  for (i = 0; i < 5; i++) {
    //Serial.printf("  %d\n", br[i]);
    serport->end();
    serport->begin(br[i]);
    serport->print(GPSreset_factory);
    serport->flush();
  }
  delay(1000); // give it time to reset
  //serport->print(GPSbaud_9600);
  serport->end();
  serport->begin(9600);
  while (!serport) { ; }
  serial_mutex.unlock();
}
//-----------------------------------------
void GPSnmeaString(HardwareSerial *serport,  int nmea) {
  switch (nmea) {
    case 0:
        serport->print(GPSnmeaEnableALL);
        break;
    case 1:
        serport->print(GPSnmeaEnableGGA_only);
        break;
    case 2:
        serport->print(GPSnmeaEnableGGA_VTG_only);
        break;

  }
  serport->flush(); 
}
//-----------------------------------------
void debug_checksums() {
  Serial.printf("%0*X\n", 2,get_checksum(GPSbaud_4800));
  Serial.printf("%0*X\n", 2,get_checksum(GPSbaud_9600));
  Serial.printf("%0*X\n", 2,get_checksum(GPSbaud_19200));
  Serial.printf("%0*X\n", 2, get_checksum(GPSbaud_38400));
  Serial.printf("%0*X\n", 2, get_checksum(GPSbaud_57600));
  Serial.printf("%0*X\n", 2, get_checksum(GPSbaud_115200));
  Serial.printf("%0*X\n", 2, get_checksum(GPSupdatefreq_1Hz));
  Serial.printf("%0*X\n", 2, get_checksum(GPSupdatefreq_2Hz));
  Serial.printf("%0*X\n", 2, get_checksum(GPSupdatefreq_4Hz));
  Serial.printf("%0*X\n", 2, get_checksum(GPSupdatefreq_5Hz));
  Serial.printf("%0*X\n", 2, get_checksum(GPSupdatefreq_10Hz));
  Serial.printf("%0*X\n", 2, get_checksum(GPSnmeaEnableALL));
  Serial.printf("%0*X\n", 2, get_checksum(GPSnmeaEnableGGA_only));
  Serial.printf("%0*X\n", 2, get_checksum(GPSnmeaEnableGGA_VTG_only));
  Serial.printf("%0*X\n", 2, get_checksum(GPSreset_hot));
  Serial.printf("%0*X\n", 2, get_checksum(GPSreset_warm));
  Serial.printf("%0*X\n", 2, get_checksum(GPSreset_cold));
  Serial.printf("%0*X\n", 2, get_checksum(GPSreset_factory));
}
//-----------------------------------------
// Get NMEA checksum => XOR result of all chars in cmd string between $ and *
// this function returns the decimal checksum value (calling function needs 
// to convert to hex)
int get_checksum(char *nmea_sentence) {
  size_t i;
  int checksum = 0;

  // loop through all characters between "$" and "*"
  for (i = 1; i < strlen(nmea_sentence) && nmea_sentence[i] != '*'; i++) {
      checksum ^= nmea_sentence[i]; // XOR the character
  }
  return checksum;
}
//-----------------------------------------
void resetAllGPS() {
    GPSfactoryreset(&GPSHWSERIAL4);
    GPSfactoryreset(&GPSHWSERIAL6);
    GPSfactoryreset(&GPSHWSERIAL8);
    //eepromCLR();
    Serial.println("Factory Reset for all GPS devices");
    digitalWrite(13, LOW);
}
void eepromResetAllGPS() {
   Serial.println("EEPROM: GPS baud is null; reseting GPS...");
    GPSfactoryreset(&GPSHWSERIAL4);
    GPSfactoryreset(&GPSHWSERIAL6);
    GPSfactoryreset(&GPSHWSERIAL8);
    
    GPSHWSERIAL4.end();
    GPSHWSERIAL6.end();
    GPSHWSERIAL8.end();
    Serial.println("Stopping GPS serial");
    Serial.println("Setting baud-rate to 9600");
    GPSHWSERIAL4.begin(9600);
    while (!GPSHWSERIAL4) { ; }
    GPSHWSERIAL6.begin(9600);
    while (!GPSHWSERIAL6) { ; }
    GPSHWSERIAL8.begin(9600);
    while (!GPSHWSERIAL8) { ; }
    Serial.println("Setting baud-rate to default and default rate and strings");
    
    GPSbaudSet(&GPSHWSERIAL4, defaultbaud4);
    GPSbaudSet(&GPSHWSERIAL6, defaultbaud6);
    GPSbaudSet(&GPSHWSERIAL8, defaultbaud8);
    GPSnmeaRateSet(&GPSHWSERIAL4, 1);
    GPSnmeaRateSet(&GPSHWSERIAL6, 1);
    GPSnmeaRateSet(&GPSHWSERIAL8, 1);
}
//-----------------------------------------
void fetchEeprom() {
  Serial.println("Fetching EEPROM data...");

  EEPROM.get(eeAddrBaudGPS4, eeGPSbaud4);
  EEPROM.get(eeAddrGPSupdatefreq4, eeGPSupdatefreq4);
  EEPROM.get(eeAddrNmeaGPS4, eeGPSnmea4);
  EEPROM.get(eeAddrBaudGPS6, eeGPSbaud6);
  EEPROM.get(eeAddrGPSupdatefreq6, eeGPSupdatefreq6);
  EEPROM.get(eeAddrNmeaGPS6, eeGPSnmea6);
  EEPROM.get(eeAddrBaudGPS8, eeGPSbaud8);
  EEPROM.get(eeAddrGPSupdatefreq8, eeGPSupdatefreq8);
  EEPROM.get(eeAddrNmeaGPS8, eeGPSnmea8);
  Serial.print("GPS4 baud = ");
  Serial.println(eeGPSbaud4); 
  Serial.print("GPS4 rate = ");
  Serial.println(eeGPSupdatefreq4);
  Serial.print("GPS4 nmea = ");
  Serial.println(eeGPSnmea4);
  Serial.print("GPS6 baud = ");
  Serial.println(eeGPSbaud6);
  Serial.print("GPS6 rate = ");
  Serial.println(eeGPSupdatefreq6);
  Serial.print("GPS6 nmea = ");
  Serial.println(eeGPSnmea6);
  Serial.print("GPS8 baud = ");
  Serial.println(eeGPSbaud8);
  Serial.print("GPS8 rate = ");
  Serial.println(eeGPSupdatefreq8);
  Serial.print("GPS8 nmea = ");
  Serial.println(eeGPSnmea8);
  //EEPROM.put(eeAddrBaud, GPSbaud_57600);
  //EEPROM.put(eeAddrGPSupdatefreq, GPSupdatefreq_10Hz);
  //EEPROM.put(eeAddrNmea, GPSnmeaEnableALL);
}
//-----------------------------------------
void initNTPrtc() {
  int ntpdelay = 0;
  float delay2fracs17 = 0.0;
  int fracs17new = 0;
  t_ntp_micros = 0;
  
  getNTPTime();
  getNTPTime();
  getNTPTime();
  getNTPTime();
  getNTPTime();
  getNTPTime();
  getNTPTime();
  getNTPTime();
  if (ntpFail == false) {            // if any of the getNTPTime() functions failed, don't set the RTC
    ntpdelay = (t_ntp_micros/8)/2;
    delay2fracs17 = (ntpdelay / 1000000.0) / SECS_PER_TIC_RTC;
    fracs17new = (ntpFracSecs >> 17) + delay2fracs17 + NTP_RTC_CAL;
    rtc_set_secs_and_frac(epoch, fracs17new);  // Set the rtc  

    sprintf(T0secs, "%lu", epoch);
    sprintf(T0fracs, "%06d", (int)(fracs17new * SECS_PER_TIC_RTC * 1E6));
    Serial.print("ntp avg delay (us) = ");
    Serial.println(ntpdelay);
    Serial.print("NTP_RTC_CAL = ");
    Serial.println(NTP_RTC_CAL);
    Serial.print("RTC Time Set = ");
    Serial.print(T0secs);
    Serial.print(" ");
    Serial.println(T0fracs);
  }
}
//-----------------------------------------
void rtcThread() {
  while(1) {
    getRTC();
    threads.delay_us(10);
    threads.yield();
  }
}
//-----------------------------------------
//-----------------------------------------
//======================================================
// END Custom Subroutines
//======================================================
void setup() {
  t_micros=0;
  
  set_arm_clock (CLOCK_FREQ); 
  
  // Start ethernet interface and open UDP sockets
  Ethernet.begin(mac);
  ntpUdp.begin(ntpudp_srcport);
  Udp3.begin(udp_srcport3);  
  Udp4.begin(udp_srcport4);
  Udp6.begin(udp_srcport6);
  Udp7.begin(udp_srcport7);
  Udp8.begin(udp_srcport8);

  // Configure serial ports
  Serial.begin(defaultbaudUSB);       // USB serial port
  while (!Serial) { ; }
  HWSERIAL7.begin(defaultbaud7);    // PC serial port
  while (!HWSERIAL7) { ; }
  GPSHWSERIAL3.begin(defaultbaud3);   // GPS3 serial port
  while (!GPSHWSERIAL3) { ; }  
  GPSHWSERIAL4.begin(defaultbaud4);   // GPS4 serial port 
  while (!GPSHWSERIAL4) { ; }
  GPSHWSERIAL6.begin(defaultbaud6);   // GPS6 serial port
  while (!GPSHWSERIAL6) { ; }
  GPSHWSERIAL8.begin(defaultbaud8);   // GPS8 serial port
  while (!GPSHWSERIAL8) { ; }

  // Output some useful debug info
  Serial.printf("CPU clock freq = %u\n",F_CPU_ACTUAL);
  Serial.printf("MAC Address: %02X:%02X:%02X:%02X:%02X:%02X \n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  Serial.print("IP Address:  ");
  Serial.println(Ethernet.localIP());
  Serial.printf("Time Server: ");
  Serial.println(timeServer);
  Serial.println();
  
  // If pin 32 HIGH, perform a full reset
  digitalWrite(13, LOW);
  pinMode(rstALLpin, INPUT);
  if (digitalRead(rstALLpin) == HIGH) {
    Serial.println("GPS Reset pin is HIGH..");
    //resetAllGPS();  //to be uncommented
  }

  fetchEeprom();

  // for now, do a factory reset each time 
  resetAllGPS();

  Serial.println("Configuring GPS serial ports...");
  // Configure GPS serial ports
  GPSnmeaRateSet(&GPSHWSERIAL4, defaultrate4);
  GPSnmeaString(&GPSHWSERIAL4, defaultnmea4);
  GPSbaudSet(&GPSHWSERIAL4, defaultbaud4);
  GPSHWSERIAL4.end();
  GPSHWSERIAL4.begin(defaultbaud4);
  EEPROM.put(eeAddrBaudGPS4, String(defaultbaud4));
  EEPROM.put(eeAddrGPSupdatefreq4, String(defaultrate4));
  EEPROM.put(eeAddrNmeaGPS4, String(defaultnmea4));

  GPSnmeaRateSet(&GPSHWSERIAL6, defaultrate6);
  GPSnmeaString(&GPSHWSERIAL6, defaultnmea6);
  GPSbaudSet(&GPSHWSERIAL6, defaultbaud6);
  GPSHWSERIAL6.end();
  GPSHWSERIAL6.begin(defaultbaud6);
  EEPROM.put(eeAddrBaudGPS6, String(defaultbaud6));
  EEPROM.put(eeAddrGPSupdatefreq6, String(defaultrate6));
  EEPROM.put(eeAddrNmeaGPS6, String(defaultnmea6));

  GPSnmeaRateSet(&GPSHWSERIAL8, defaultrate8);
  GPSnmeaString(&GPSHWSERIAL8, defaultnmea8);
  GPSbaudSet(&GPSHWSERIAL8, defaultbaud8);
  GPSHWSERIAL8.end();
  GPSHWSERIAL8.begin(defaultbaud8);
  EEPROM.put(eeAddrBaudGPS8, String(defaultbaud8));
  EEPROM.put(eeAddrGPSupdatefreq8, String(defaultrate8));
  EEPROM.put(eeAddrNmeaGPS8, String(defaultnmea8));

  Serial.println("Initializing RTC with NTP server...");
  // use NTP server to initialize RTC
  ntpFail = false;  //make sure ntpFail = false before calling initNTPrtc()
  initNTPrtc();
  if (ntpFail == true) {
    Serial.println("NTP failed");
  }

  // Start threads
  threads.addThread(serial3Thread);
  threads.addThread(serial4Thread);
  threads.addThread(serial6Thread);
  threads.addThread(serial7Thread);
  threads.addThread(serial8Thread);
  threads.addThread(rtcThread);

  Serial.print("Init time = ");
  Serial.print(t_micros / 1000000.0);
  Serial.println(" secs");
  Serial.println("Running...");

  // Register interrupt on PPS pin
  attachInterrupt(digitalPinToInterrupt(PPS), isrPPS, RISING);  
  t_millis = 0;
  clockset_cnt = 0;
}
//======================================================
void loop() {
  threads.yield();
  if (t_millis > 998) {
    clockset_cnt ++;
    if (clockset_cnt > NTP_SYNC_FREQ) {
      ntpFail = false;
      initNTPrtc();
      clockset_cnt = 0;
    }
    t_millis = 0;
  }
}
 
Are you using the PPS pin for anything?
Looking at the code the PPS pin is used to trigger an interrupt that then sets a variable (B.T.W., that variable should be marked as volatile) but the variable doesn't seem to then be used anywhere that I can see.

I've seen GPS systems take over 100ms to start outputting data after the start of the second. And more importantly the time taken is very variable depending on the number of satellites in view. The PPS on the other hand is generally very stable and reliable once the system has lock. Most GPS data sheets indicate accuracies in the 5-10ns region. If the aim is to be as accurate as possible shouldn't that be the thing used to set the time?
 
@AndyA, thanks for the advice. I'm a bit behind the curve with C and not up on when it applies to mark a variable as volatile. I guess that will come with experience and advice from nice folks like yourself. At this point I'm not using PPS because I don't know if PPS will be available in the environment, and I'm trying to see how accurate I can get without it. Yes, using PPS to initiate the time read/set is the optimal method. I have tried reading the RTC on the PPS interrupt and noticed that it drifts fairly quickly - I'm seeing the RTC timestamp lose about 1 count (30.5us) every 5 or 6 seconds. So, I have to keep reading NTP time and set the RTC every minute to keep it within 1ms accuracy. In my next version, I will query a PTP server for time reference. Although I'm still concerned about the drifting RTC and whether its me or this is just how the RTC performs.
 
I'm still concerned about the drifting RTC and whether its me or this is just how the RTC performs.
If you want an accurate RTC look at the RV-3028-CT. It's factory calibrated to +/- 1 ppm @ 25C.
You can get a sample from the manufacturers (demo board and/or ICs) or if you are short of time Pimoroni do a breakout board.
You can find a library here.
 
Back
Top