Teensy 4.1 NTP server

With more data, I see my wild surmise about my GPSDO was wrong, and your more obvious suggestion was correct. Almost all the variation I saw was explained by air movement over the T4.1 PCB, as well as the ambient temperature. When I left the office with the door closed overnight, the error was a mostly a regular low-pass-filtered square wave tracking the 23-minute HVAC on/off cycle. This morning, after I sealed the T4.1 into an 8 inch cinderblock to remove drafts and stabilize its temperature, the chi-squared value is often less than 1 and rarely exceeds 5, and the offset rarely exceeds 30 ns, so the performance is quite different from before.
Code:
count       offset      frequency   chisq      ppb  ntpseconds
1723630418 0.000000013 0.000007707 0.333104610 7708 3805121064
1798629840 0.000000009 0.000007707 0.333148301 7707 3805121067
1823629647 0.000000023 0.000007707 0.638458192 7709 3805121068
1898629069 0.000000020 0.000007707 0.305683136 7709 3805121071
1998628298 0.000000026 0.000007707 0.437653899 7710 3805121075
2073627720 0.000000018 0.000007708 0.405045867 7709 3805121078
2148627142 0.000000010 0.000007708 0.337020397 7709 3805121081
2248626371 0.000000016 0.000007708 0.325883627 7709 3805121085
2323625793 0.000000010 0.000007708 0.257352889 7709 3805121088
2348625600 0.000000022 0.000007708 0.275229812 7710 3805121089
2423625022 0.000000014 0.000007708 0.226483017 7709 3805121092
2523624251 0.000000017 0.000007708 0.229082704 7710 3805121096
2598623673 0.000000010 0.000007709 0.233526707 7709 3805121099
2698622902 0.000000014 0.000007709 0.233599454 7710 3805121103
2723622709 0.000000024 0.000007709 0.321402192 7711 3805121104
2823621938 0.000000023 0.000007709 0.375832856 7711 3805121108
2898621360 0.000000010 0.000007709 0.377925456 7710 3805121111
2998620589 0.000000010 0.000007709 0.421401381 7710 3805121115
3023620396 0.000000020 0.000007709 0.451219887 7711 3805121116
3123619625 0.000000020 0.000007710 0.515136003 7711 3805121120
3198619047 0.000000007 0.000007710 0.347284496 7710 3805121123
3298618276 0.000000007 0.000007710 0.274755895 7710 3805121127
3323618083 0.000000017 0.000007710 0.437681466 7712 3805121128
3423617312 0.000000014 0.000007710 0.339892298 7711 3805121132
3523616541 0.000000010 0.000007710 0.339898229 7711 3805121136
 
Last edited:
With more data, I see my wild surmise about my GPSDO was wrong, and your more obvious suggestion was correct. Almost all the variation I saw was explained by air movement over the T4.1 PCB, as well as the ambient temperature. When I left the office with the door closed overnight, the error was a mostly a regular low-pass-filtered square wave tracking the 23-minute HVAC on/off cycle. This morning, after I sealed the T4.1 into an 8 inch cinderblock to remove drafts and stabilize its temperature, the chi-squared value is often less than 1 and rarely exceeds 5, and the offset rarely exceeds 30 ns, so the performance is quite different from before.

Nice! Good idea with the cinderblock, that should add a lot of thermal mass
 
After a few days of sitting in a concrete block, which is in turn inside a large styrofoam box, the T4.1 temperature and frequency drift has mostly stabilized. Its reported CPU temperature is around 61.2 C and the frequency drift is near +8.612 ppm, with stability near 0.005 ppm in one hour. Based on the crude reported CPU temperature, this particular T4.1 frequency tempco is +0.195 ppm/C so it appears that the oscillator temperature is not drifting more than 25 milli-degrees in that time period. Not too bad!

T41-NTP-ppb-2a.jpg
T41-NTP-ppb-2b.jpg
 
After a few days of sitting in a concrete block, which is in turn inside a large styrofoam box, the T4.1 temperature and frequency drift has mostly stabilized. Its reported CPU temperature is around 61.2 C and the frequency drift is near +8.612 ppm, with stability near 0.005 ppm in one hour. Based on the crude reported CPU temperature, this particular T4.1 frequency tempco is +0.195 ppm/C so it appears that the oscillator temperature is not drifting more than 25 milli-degrees in that time period. Not too bad!

My two have a tempco of 0.154ppm/C and 0.206ppm/C, so that fits with your 0.195ppm/C.

Wander being 0.005 ppm in an hour is pretty impressive.
 
Well, I am cherry-picking the best few hours I've seen so far, but the concrete block is pretty effective in slowing down temperature changes. It appears to me if I had a high-resolution temperature sensor, and the temperature gradients and rate of change with time are low enough, that it may be possible to compensate the drift for that sort of stability over the medium term (at least for many hours). I don't know about temperature hysteresis and no doubt there is also an aging component longer-term, but so far in this experiment, I don't see much variation that isn't explained by temperature. I don't actually have a need for that level of performance, but it's interesting how stable it seems to be. If you maintained a constant error of 5 ppb for one year, it would add up to 0.16 seconds.
 
@ddrown, your isr in examples/lwip_1588_input/ needs asm volatile ("dsb");, otherwise the ISR fires twice for each PPS.

https://github.com/ddrown/teensy41_ethernet

Code:
calculate T4.1 drift from GPS PPS with examples/lwip_1588_input/
1 C=2186115 S=68 G=0 E=0
2 C=27185936 S=68 G=0 E=0

  27185936-2186115 = 24999821   -7.16 ppm  @25mhz
 
Last edited:
Very neat!

Good morning all,

Just put one of these together with a uBlox M8N and it seems like it is working just fine:

eticks offset drift chisq drift secs
2548391359 0.000000037 0.000007600 4.000188351 7604 3819280163
2623390789 0.000000025 0.000007600 4.000512123 7603 3819280166
2698390219 0.000000016 0.000007600 3.999779940 7602 3819280169
2773389649 0.000000010 0.000007600 3.999139786 7601 3819280172
2848389079 0.000000007 0.000007600 3.937441349 7601 3819280175
2898388699 0.000000006 0.000007600 3.749967575 7601 3819280177
2998387939 0.000000003 0.000007600 3.437464237 7600 3819280181
3073387369 0.000000003 0.000007600 3.000012875 7600 3819280184

I did add it as a server to my ntp.conf here on my Win10 box and it isn't using it yet even after an NTP stop / start using services.msc:

remote refid st t when poll reach delay offset jitter
==============================================================================
*192.168.4.50 .PPS. 1 u 59 128 377 0.946 -0.054 0.221
+192.168.4.57 .PPS. 1 u 73 128 377 0.869 +0.075 2.552
74.208.235.60 .INIT. 16 u - 1024 0 0.000 +0.000 0.000
172.86.179.86 .INIT. 16 u - 1024 0 0.000 +0.000 0.000

192.168.4.50 is an RPi GPS NTP server with standard NTP, and 192.168.4.57 is another RPi GPS NTP server built around NTP-Sec. Yes, I really have no need for a total of three NTP servers...but all this got started when the original RPi GPS NTP server died...I rebuilt it...and it didn't seem to be working so I then built the NTP-Sec version and ordered up the Teensy 4.1 parts...and then while the Teensy 4.1 shipped out to me...both the original GPS NTP server and the GPS NTP-Sec servers both started working just fine. :D

Just thought I'd post my results and send a nice thank you to ddrown for posting his code and doing the work in the first place to port it to the Teensy 4.1. :D

thanks much and 73,
ben, kd5byb
 
RPi Likes It!

Good morning again all,

So I fired up another RPi, added the Teensy 4.1 NTP server along with the addresses of my other NTP servers to ntp.conf...and it's clear that the Teensy 4.1 NTP server (192.168.4.66) is working fine and that the RPi is quite happy to use it as it's NTP source:

Code:
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*192.168.4.66    .PPS.            1 u   17   64    1    0.071    1.193   0.085
+192.168.4.50    .PPS.            1 u   16   64    1    0.387    1.165   0.182
+192.168.4.57    .PPS.            1 u   15   64    1    0.299    1.115   0.191
 108.61.73.244   .INIT.          16 u    -   64    0    0.000    0.000   0.000
 test.diarizer.c .INIT.          16 u    -   64    0    0.000    0.000   0.000
 lithium.constan .INIT.          16 u    -   64    0    0.000    0.000   0.000
 time.cloudflare .INIT.          16 u    -   64    0    0.000    0.000   0.000

I used the code block as I can't seem to find a fixed-width font in the font choices? Probably a PEBCAK on my part.

Very cool. :)

thanks much,
ben
 
Excellent, glad to hear it is working for you. Those are low delay and jitter numbers, I like to see that.
 
Very glad to hear that I'm getting low delay / jitter numbers - that's excellent.

Next up on my to-do list for this unit is an enclosure, as I do want to make it a permanent installation. I'm in process of looking at your code to see if there are some status variables used that I could then use to light LED's on a front panel display to "LED it up" a bit. ;)

Very likely one of those will be PPS. I'll buffer the PPS output from the GPS unit and make that one of the front panel LED's. I've got several GPS units for various things here and when one is having an issue (like it's antenna plug coming loose) it's easy to see because it's PPS gets out of sync with the other units. :)

Thanks much,
ben
 
Quick update - Teensy 4.1 NTP continues to work great. The Win10 box is now using it for time sync after I removed "iburst" from ntp.conf. :)

Very neat!

thanks much,
ben
 
Teensy 4.1 NTP continues to work great. The Win10 box is now using it for time sync after I removed "iburst" from ntp.conf. :)

That's interesting, the teensy should be more than able to keep up with an iburst.

Next up on my to-do list for this unit is an enclosure, as I do want to make it a permanent installation. I'm in process of looking at your code to see if there are some status variables used that I could then use to light LED's on a front panel display to "LED it up" a bit. ;)

Very likely one of those will be PPS. I'll buffer the PPS output from the GPS unit and make that one of the front panel LED's. I've got several GPS units for various things here and when one is having an issue (like it's antenna plug coming loose) it's easy to see because it's PPS gets out of sync with the other units. :)

PPS would be able to tell you a lot at a glance

Some other ideas for LED indication:
* offset between local clock and GPS under a threshold (say 100ns)
* GPS in 3D lock
* GPS has >3 satellites with >20db SNR
* GPS has a valid year (after cold reset, most GPS modules start with a year in the past)
 
That's interesting, the teensy should be more than able to keep up with an iburst.

I'll try adding it back into Win10 Meinberg NTP ntp.conf and see what happens. It may have been just a coincidence that it "got happy" right after I removed it (and restarted the service with services.msc).

PPS would be able to tell you a lot at a glance

Some other ideas for LED indication:
* offset between local clock and GPS under a threshold (say 100ns)
* GPS in 3D lock
* GPS has >3 satellites with >20db SNR
* GPS has a valid year (after cold reset, most GPS modules start with a year in the past)

Excellent suggestions! At the moment another project has grabbed my attention, but when I get back working on this I will definitely consider all of those. :)

Thanks much,
ben
 
I've added a webserver to the code to make it easier to get data from the NTP server. /state.json from the teensy returns the following structure:

{
"ppsToGPS": 396, // milliseconds between PPS timestamp and GPS's uart message showing the date
"ppsMillis": 177223, // local millis() counter at PPS
"curMillis": 178286, // current millis() counter
"gpstime": 3820102515, // seconds since 01-01-1900 from GPS, aka NTP time
"counterPPS": 53101544, // IEEE 1588 counter value at PPS
"offsetHuman": 0.000000032, // offset between local clock and GPS, in seconds
"pidD": 0.000010763, // Theil-Sen estimate of local clock's error, this is 10.763ppm
"dChiSq": 5.998106956, // chi sq fit of local clock error estimate
"clockPpb": 10760 // PID output
}
 
Building on the JSON data, I added a webpage that will poll /state.json and build graphs. You can reach the web page by putting your teensy's IP address in your web browser.

graph1.png

graph2.png

As well as general stats:
Code:
PPS To GPS: 405 ms
millis() at PPS: 902.225
millis() at GPS Timestamp: 903689
millis() now: 903.693
NTP time: 3820194785
IEEE 1588 counter at PPS: 1073301322
Offset between NTP/GPS times: 0.000000008 s
Estimate of NTP clock freq: 0.000010612 s/s
ChiSq fit of freq measure: 2.924
PID output: 10613 ns/s (ppb)
GPS lock Status: 3D
GPS Strong signals (> 25db): 3, Weak Signals (10db-24db): 6, No Signal (0db-9db): 2

The index.html and index.js files take awhile to load (around 100ms), but the state.json file is taking 3-7ms. This shouldn't affect the NTP timestamp quality, as the 1588 hardware takes those independently from the main processor. My tests comparing one Teensy with the new webserver code and one without it show no difference in the round trip time or the offset.
 
Nice. Here is graphic data from T4.1 connected to sparkfun GNSS NEO-M9N with external antenna mounted on windowsill. M9N can use up to 4 constellations (GPS, GLONASS, Galileo, BEIDOU).
offd.jpg
sats.jpg

Code:
PPS To GPS: 134 ms
millis() at PPS: 3105.912
millis() at GPS Timestamp: 3106046
millis() now: 3106.625
NTP time: 3820244653
IEEE 1588 counter at PPS: 255890795
Offset between NTP/GPS times: 0.000000012 s
Estimate of NTP clock freq: 0.000007667 s/s
ChiSq fit of freq measure: 1.667
PID output: 7668 ns/s (ppb)
GPS lock Status: 3D
GPS Strong signals (> 25db): 25, Weak Signals (10db-24db): 5, No Signal (0db-9db): 8
Can you report HDOP in your textual summary?
 
Last edited:
Code:
GPS Strong signals (> 25db): 25, Weak Signals (10db-24db): 5, No Signal (0db-9db): 8
Can you report HDOP in your textual summary?

Wow, you have a very strong signal there. It should be easy to pull HDOP out of GSA, it's already parsing that message.
 
Wow, you have a very strong signal there.

Maybe not. the multiple satellites are from different constellations, i think the gps solution probably uses no more than 12 satellites, though it's able to choose from a larger collection of strong signals.
 
Last edited:
Not familiar with the M9N. Is it a dual frequency unit, similar to the ublox F9P? If not, I have one of those working right beside my desk as we speak. I could run some of those tests for you as a comparison, with or with ntrip, as you prefer. But I only have a Teensy 4, not a 4.1 (yet.)
 
Ok, updated the code with the *dop data:

Code:
GPS pdop=1.8, hdop=1.5, vdop=0.9

Thanks! Not a lot of variation in dop values, maybe needs two decimal places. Here is data for 3 different GPS units with external antenna.
Code:
Adafruit [URL="https://www.adafruit.com/product/746"]ultimate[/URL], 1 constellation
   GPS Strong signals (> 25db): 10, Weak Signals (10db-24db): 2, No Signal (0db-9db): 0
   GPS pdop=1.35, hdop=0.77, vdop=1.11
   
Sparkfun [URL="https://www.sparkfun.com/products/16329"]NEO-M8U[/URL], 2 constellations
   GPS Strong signals (> 25db): 15, Weak Signals (10db-24db): 3, No Signal (0db-9db): 3
   GPS pdop=1.55, hdop=0.7, vdop=1.39

Sparkfun [URL="https://www.sparkfun.com/products/15712"]NEO-M9N[/URL], 4 constellations
   GPS Strong signals (> 25db): 20, Weak Signals (10db-24db): 7, No Signal (0db-9db): 3
   GPS pdop=1.1, hdop=0.6, vdop=0.9


Variations on a theme:
Just because I could, I hacked your library to use GPT2@24mhz on pin 15 instead of the 1588 timer @25mhz. One could add precision by running GPT @150mhz, though you would need to reduce sample history from 16 to 8. And then based on that experiment, I made another version of the library that could do the PID tuning on a Teensy 4.0 (no Ethernet) using GPT2 capture.
 
Last edited:
A feature request - discipline the audio clock to the GPS with small tweaks to the rate and this code:

Code:
// set audio rate in Hz

void ClockSkew::setAudioRate(double fs)
{
  // !!! you must change avr/libraries/Audio/avr/libraries/Audio/output_XXX.cpp
  // to be "int c2 = 10000000;"     Not 10000.

  Serial.printf("abs rate: %.10f\n", fs);

  assert(CCM_ANALOG_PLL_AUDIO_DENOM >= 1000000);     // has user changed this for more resolution?

  // calc new audio clock rate
  // this block from teensy code
  // PLL between 27*24 = 648MHz und 54*24=1296MHz
  // n1, n2 choosen for compatibility with I2S (same PLL frequency) :
  int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
  int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);
  double C = ((double)fs * 256 * n1 * n2) / 24000000;
  int c0 = C;
  int c2 = CCM_ANALOG_PLL_AUDIO_DENOM;
  int c1 = C * c2 - (c0 * c2);

  CCM_ANALOG_PLL_AUDIO_NUM = c1;   // set rate

}  // setAudioRate()
 
Good morning!

Just compiled and uploaded the latest code to my Teensy 4.1 and it's working great. I've got parts for an enclosure for it printing on the 3D printer now, so once I get the unit enclosed and stable, I'll post some screen grabs of the web status page. :)

thanks much!
-ben
 
Thanks! Not a lot of variation in dop values, maybe needs two decimal places.

Yeah, from my study of the GPS modules I have, I haven't seen much value from the reported dop in terms of measuring time quality.

Variations on a theme:
Just because I could, I hacked your library to use GPT2@24mhz on pin 15 instead of the 1588 timer @25mhz. One could add precision by running GPT @150mhz. And then based on that experiment, I made another version of the library that could do the PID tuning on a Teensy 4.0 (no Ethernet) using GPT2 capture.

Using a different timer is a tradeoff between getting a faster timebase to make measurements in but you lose the ability to use the same timebase that your network packets are measured in. So you need to create a phase measurement to tie the GPT2 timer to the 1588 timer. Since they should be the same frequency error through the various PLLs and dividers, that's not too hard. Edit: I just noticed you mentioned using it on a non-ethernet teensy, so that must be for non NTP uses. That makes sense, if you had multiple capture pins, you could use it to compare input frequencies and timestamps between devices.

jonr said:
A feature request - discipline the audio clock to the GPS with small tweaks to the rate and this code

That is an interesting idea. The D term of the PID should be able to drive this.

kd5byb said:
Just compiled and uploaded the latest code to my Teensy 4.1 and it's working great. I've got parts for an enclosure for it printing on the 3D printer now, so once I get the unit enclosed and stable, I'll post some screen grabs of the web status page.

I'd love to see your enclosure design, that sounds neat. I've been meaning to design a "shield" with a GPS module and an ethernet connector built in, but keep on getting distracted with other projects.
 
Back
Top