How-to use a Garmin 18x with Chrony and PPS

Did you check the PPS Signal ? Is it 100ms long and maybe inverted ?

1 Like


100ms wide and it is not inverted.

I had the same GPS puck connected to the same physical appliance running pfSense (FreeBBD) which supports PPS on CTS pin. I did not have to invert the state in FreeBSD to achieve less than 5 usec jitter/offset.

The only difference is I was not using GPSD and the SHM reference clock source. I was using the NMEA reference clock source, which has PPS support built in.


Thanks for the GPSD DEV branch

According the to evidence, seems like the serial PPS signal is correct, and GPSD is not using the correct assert time stamp or even reliably.

root@ntp:/etc# ppscheck /dev/ttyS0
WARNING: time_pps_create(/dev/ttyS0)) failed: Operation not supported(95)
WARRING: /dev/ttyS0 does not appear to be a KPPS device
INFO: matching /dev/pps4 opened

# Src   Seconds                 Signals

  KPPS  0.000000000    assert  0
  KPPS  0.000000000    clear   0
  TTY   1715333027.000812157    TIOCM_CTS # assert
  TTY   1715333027.100897886  # clear, 100 msec wide

  TTY   1715333028.000690346    TIOCM_CTS
  TTY   1715333028.101043789  

  TTY   1715333029.000697980    TIOCM_CTS
  TTY   1715333029.100468698  

  TTY   1715333030.000974123    TIOCM_CTS
  TTY   1715333030.101002970  

  TTY   1715333031.000684070    TIOCM_CTS
  TTY   1715333031.100492811  

  TTY   1715333032.000667950    TIOCM_CTS
  TTY   1715333032.100541068  

More evidence that the GPSD DEV branch might be picking the incorrect CTS PPS time stamp. It seems to be picking the clear time stamp and occasional picking the assert time stamp.

root@ntp:~# ntpshmmon | grep NTP1
#      Name  Seen@                 Clock                 Real                 L Prc
sample NTP1  1715490814.101036614  1715490814.100535891  1715490815.000000000 0 -20 # clear
sample NTP1  1715490815.101082367  1715490815.100051271  1715490816.000000000 0 -20 # clear
sample NTP1  1715490816.100521309  1715490816.099647238  1715490817.000000000 0 -20 # clear 
sample NTP1  1715490818.000124658  1715490817.999817735  1715490818.000000000 0 -20 # assert
sample NTP1  1715490819.000479083  1715490818.999655612  1715490819.000000000 0 -20 # assert
sample NTP1  1715490819.101731785  1715490819.100440591  1715490820.000000000 0 -20 # clear
sample NTP1  1715490820.100275818  1715490820.099591296  1715490821.000000000 0 -20 # clear
sample NTP1  1715490821.100885293  1715490821.099497212  1715490822.000000000 0 -20 # clear
sample NTP1  1715490822.100415708  1715490822.099556362  1715490823.000000000 0 -20 # clear
sample NTP1  1715490823.100358284  1715490823.099773361  1715490824.000000000 0 -20 # clear
sample NTP1  1715490825.000161910  1715490824.999590523  1715490825.000000000 0 -20 # assert
sample NTP1  1715490826.001171789  1715490825.999503215  1715490826.000000000 0 -20 # assert
sample NTP1  1715490826.101031223  1715490826.099464949  1715490827.000000000 0 -20 # clear
sample NTP1  1715490827.101222676  1715490827.100882688  1715490828.000000000 0 -20 # clear
sample NTP1  1715490828.100505980  1715490828.099572955  1715490829.000000000 0 -20 # clear
sample NTP1  1715490829.101027082  1715490829.100143833  1715490830.000000000 0 -20 # clear
sample NTP1  1715490830.101088472  1715490830.100036110  1715490831.000000000 0 -20 # clear
sample NTP1  1715490831.100606614  1715490831.099841002  1715490832.000000000 0 -20 # clear
sample NTP1  1715490832.100494413  1715490832.099687653  1715490833.000000000 0 -20 # clear
sample NTP1  1715490834.000153648  1715490833.999751624  1715490834.000000000 0 -20 # assert
sample NTP1  1715490835.000495982  1715490834.999543197  1715490835.000000000 0 -20 # assert
sample NTP1  1715490835.100147368  1715490835.100022755  1715490836.000000000 0 -20 # clear
sample NTP1  1715490836.101040662  1715490836.099981427  1715490837.000000000 0 -20 # clear

Great, glad it is working in principle at least.

That indeed sounds like gpsd is detecting the wrong edge of the signal. With a u-blox receiver, or ntpd and kernel PPS, it would be easy to just invert the signal/tell ntpd to use the other edge. With gpsd, it is not so straight-forward. gpsd is trying to be smart, which in general obviously is a good thing. E.g., it tries to detect the pulse rate (0.5 Hz, 1Hz, or 5Hz), the β€œleading” edge marking the second, or whether it is maybe not a pulse but a square wave, where each edge is significant. The issue is that when things don’t go according to plan, there are no knobs or levers to manually override gpsd’s detection mechanism.

I don’t think this is related to the development branch of gpsd. As far as I can see, no significant changes have been done to the PPS code in some time, mostly what looks like style things only. But the point also wasn’t to make use of the development branch per se, it was just most readily accessible. You can try other versions, e.g., 3.25, which seems to be the version shipped by your OS:

git tag      # to list available releases
git checkout release-3.25    # to checkout the release that you want; repeat with other release tags to jump around in history if you like
scons -c && scons     # clean the outcome from the previous build and rebuild
git checkout master    # to go back to the latest development version downloaded

gpsd getting it wrong most of the time, but getting it right every now and then is interesting, and maybe a lead. I guess there might be some timing issue involved. User space PPS is not as tight timing-wise as kernel PPS. I.e., with the higher jitter, gpsd’s internal logic might mostly go one way (wrong), but with slightly different timing, it might go the other (right) way every now and then. Would be interesting to see what gpsd would see from the device with kernel PPS (i.e., under BSD), if it is having similar issues in that case (there are some differences, but also a lot of stuff in common which might also give clues as to what goes wrong).

Try running ntpshmmon with the -o option, that shows the offset instead of the β€œseen at” timestamp, and also look at NTP0 to get an impression of the timing of the serial data.

It is interesting that the interval between the edges mostly seems to be above 100ms, but sometimes slightly below. Not sure that has anything to do with it. Would be interesting to see whether there really is a correlation between the measured width of the pulse, and the detection of the wrong edge in most cases.

I looked at the code again, but ad hoc, nothing stood out that could explain this behavior, and it accounts for slower devices where the time keeping might not be all that accurate. But it is an intricate logic, and I don’t claim to really fully understand it.

A few things that you could try, depending on how comfortable you are with each:

  • Run gpsd with logging (-D option). I think one needs to crank the log level up to 4 or even 5 to see also informational messages from the PPS handling. There’s going to be a lot of other stuff at those log levels, but PPS-related messages should all have the (sub-)string β€œPPS” at the beginning, to facilitate filtering. Maybe something will stand out right away, but could be that more careful analysis side-by-side with the code is needed to understand what is going on.
  • Change the pulse width on the device. 100ms as such should have been on the safe side from what I understand, but would be interesting to see what cranking it up a bit, or lowering it slightly yields.
  • Lower the serial line rate. Apart from handling the PPS signal, gpsd also needs to match it up with the serial data. A bit of a long shot, but maybe some data is available/not available when gpsd expects it, confusing the pulse handling (though according to comments in the code, gpsd seems generally prepared to even deal with units sending a serial message relating to the epoch of a PPS pulse to be received even before the pulse itself).
  • You obfuscated your location in the NMEA stream, but along with it also removed the timestamp in the GGA message. Would be interesting to see that, and also really understand the timing of the NMEA messages in relation to the PPS signal. If I recall correctly, RMC should start the message cycle of an epoch. Why does it appear to be before the PPS signal in some cases? (Though need to check the timestamps more thoroughly still whether that is really the case.)
  • Disable Garmin binary. Again related to correlating serial data with the pulse where things could go wrong. Your use of the -g option to gpsmon at least seems to suggest that some data from the 18x is in Garmin binary. Again, at least with u-blox, gpsd tries to do smart things, maybe it similarly tries with Garmin binary, starting with enabling it because binary often give better/more info than plain NMEA (I have no personal experience with Garmin units), and something goes wrong.
  • Indeed try different versions of gpsd, to see whether that makes any difference. As said, it doesn’t look like there were any major changes in the relevant sections, but might be a minuscule non-obvious (e.g., seemingly unrelated) change somewhere.
  • If all else fails, maybe opening a bug report with the gpsd project would be an option to get support troubleshooting this? I think the setup you have may not be the most common (kernel PPS, i.e., via DCD, is obviously preferable and can be made to work in many instances, e.g., by changes in hardware - unless certain constraints like in your case legitimately prevent it/make it desirable to get it to work within a given, fixed context). But I think it is one that gpsd nonetheless aspires to support, especially since the Garmin 18x is a common-enough unit. Not sure about the maintainer’s current view on this, though. If he is sympathetic, he can be quite helpful.

Looks like PPS time stamp is correct when the serial NMEA message offset is > 100 msec. I am currently running at 38400 baud (fastest it supports). I assumed faster is always better. How is that for an edge case?

Ubuntu 24.04

root@ntp:~# ntpshmmon -o 
ntpshmmon: version 3.25
#      Name     Offset            Clock                 Real                 L Prc
sample NTP0          0.085228630  1715539516.085228630  1715539516.000000000 0  -1
sample NTP1         -0.873629879  1715539516.126370121  1715539517.000000000 0 -20
sample NTP0          0.081718522  1715539517.081718522  1715539517.000000000 0  -1
sample NTP1         -0.884069774  1715539517.115930226  1715539518.000000000 0 -20
sample NTP0          0.075880041  1715539518.075880041  1715539518.000000000 0  -1
sample NTP1         -0.895254918  1715539518.104745082  1715539519.000000000 0 -20
sample NTP0          0.078816731  1715539519.078816731  1715539519.000000000 0  -1
sample NTP1         -0.899284870  1715539519.100715130  1715539520.000000000 0 -20
sample NTP0          0.084647225  1715539520.084647225  1715539520.000000000 0  -1
sample NTP1         -0.899280238  1715539520.100719762  1715539521.000000000 0 -20
sample NTP0          0.089901582  1715539521.089901582  1715539521.000000000 0  -1
sample NTP1         -0.899164593  1715539521.100835407  1715539522.000000000 0 -20
sample NTP0          0.095393339  1715539522.095393339  1715539522.000000000 0  -1
sample NTP1         -0.899273486  1715539522.100726514  1715539523.000000000 0 -20
sample NTP0          0.101733133  1715539523.101733133  1715539523.000000000 0  -1 # NMEA data offset
sample NTP1          0.000697857  1715539524.000697857  1715539524.000000000 0 -20 # PPS assert offset
sample NTP0          0.067291492  1715539524.067291492  1715539524.000000000 0  -1
sample NTP1         -0.899164022  1715539524.100835978  1715539525.000000000 0 -20
sample NTP0          0.061891719  1715539525.061891719  1715539525.000000000 0  -1
sample NTP1         -0.900270046  1715539525.099729954  1715539526.000000000 0 -20

Installed FreeBSD 14.0, GPSD and Chrony (for some reason I could not get the NTPD SHM devices to work with GPSD, so I switched to Chrony). Added the PPS_MODE flag to the loader. PPS offset looks great. You are right Linux <> FreeBSD. Config files are not in the same locations and setup is completely different. :confounded:

FreeBSD - 14.0

root@ntp-2:/boot # cat loader.conf.local 

root@ntp-2:/boot # sysctl -a | grep -i pps_mode
dev.uart.0.pps_mode: 1

/dev/ttyu0                    NMEA0183>
β”‚Time: 2024-05-12T21:39:06.000Z   Lat: XX XX.599400' N   Lon:  XX XX.263300' W β”‚r":"NMEA0183","readonly":"true","activated":"2024-05-12T21:38:5
└───────────────────────────────── Cooked TPV β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜]}
β”‚ GPGGA GPRMC GPGSA GPGSV PGRME                                                β”‚
└───────────────────────────────── Sentences β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ SVID  PRN  Az El SN HUβ”‚Time:     213906         β”‚Time:      213906           β”‚
β”‚GP  5    5   4 57 46  Yβ”‚Latitude:    XXXX.5994 N β”‚Latitude:  XXXX.5994        β”‚
β”‚GP 15   15 191 54 17  Yβ”‚Longitude:  XXXXX.2633 W β”‚Longitude: XXXXX.2633       β”‚
β”‚GP 20   20  39 36 41  Yβ”‚Speed:    000.1          β”‚Altitude:  2.3              β”‚
β”‚GP 29   29 310 54 50  Yβ”‚Course:   000.0          β”‚Quality:   1   Sats: 04     β”‚
β”‚GP 23   23 250  6 25  Nβ”‚Status:   A        FAA:A β”‚HDOP:      1.5              β”‚
β”‚SB138   51 228 46  0  Nβ”‚MagVar:   007.2W         β”‚Geoid:     -28.7            β”‚
β”‚                       └───────── RMC ───────────└─────────── GGA β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       β”‚Mode: A3 Sats: 5 15 20   β”‚UTC:           RMS:         β”‚
β”‚                       β”‚DOP H=1.5  V=2.3  P=2.8  β”‚MAJ:           MIN:         β”‚
β”‚                       β”‚TOFF:  0.149858632       β”‚ORI:           LAT:         β”‚
β”‚                       β”‚PPS:  0.000011444        β”‚LON:           ALT:         β”‚
└──────── GSV ──────────└────── GSA + PPS ────────└─────────── GST β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
root@ntp-2:/usr/local/etc # gpsmon -a | grep -i pps
TOFF= 1715550025.068900268 real= 1715550025.000000000
PPS= 1715550026.00002302 clock= 1715550026.00000000 offset= 0.000023023
TOFF= 1715550026.075223465 real= 1715550026.000000000
PPS= 1715550027.00002048 clock= 1715550027.00000000 offset= 0.000020485
TOFF= 1715550027.081342965 real= 1715550027.000000000
PPS= 1715550028.00002379 clock= 1715550028.00000000 offset= 0.000023797
TOFF= 1715550028.087593513 real= 1715550028.000000000
PPS= 1715550029.00002874 clock= 1715550029.00000000 offset= 0.000028747
TOFF= 1715550029.094062283 real= 1715550029.000000000
PPS= 1715550030.00002342 clock= 1715550030.00000000 offset= 0.000023421
TOFF= 1715550030.099321907 real= 1715550030.000000000
PPS= 1715550031.00002090 clock= 1715550031.00000000 offset= 0.000020908
TOFF= 1715550031.105047413 real= 1715550031.000000000
PPS= 1715550032.00002083 clock= 1715550032.00000000 offset= 0.000020833
TOFF= 1715550032.070664659 real= 1715550032.000000000
PPS= 1715550033.00002333 clock= 1715550033.00000000 offset= 0.000023330
TOFF= 1715550033.067559462 real= 1715550033.000000000
(122) {"class":"WATCH","enable":true,"json":false,"nmea":false,"raw":2,"scaled":false,"timing":false,"split24":false,"pps":true}
------------------- PPS offset:  0.000023023 ------
------------------- PPS offset:  0.000020485 ------
------------------- PPS offset:  0.000023797 ------
------------------- PPS offset:  0.000028747 ------
------------------- PPS offset:  0.000023421 ------
------------------- PPS offset:  0.000020908 ------
------------------- PPS offset:  0.000020833 ------
------------------- PPS offset:  0.000023330 ------

Did that, but not sure where the logs are.

I used the -a option you suggested when GPSMON crashed on Ubuntu 24.04. I had to go back and make sure I did not have a typo in my post. An β€œa” and β€œg” look the same without my glasses. :eyeglasses:
It’s a NMEA 0183 GPS 19X (GPS+GLONASS). Basically the same as the 16X and 18X GPS only serial units.


  • Couldn’t figure out how to get NTPSHMMON installed on FreeBSD
  • I tried several times to recompile the Linux kernel with the required KPPS support enabled, but all the wikis and google searches that provide instructions how to recompile the linux kernel are horribly out of date.
    GPSD Time Service HOWTO


  • So is slowing down the baud rate the next step? FreeBSD did not seem to have a problem at the same baud rate.
  • For giggles, I set the PPS_MODE=2 (CD) and GPSD would not detect the PPS signal.


Created an issue report. NOTE: You need a GitLab account to view.

Ah, interesting! That is the kind of stuff I was hoping for, as it gives a lead to further investigate what is going on.

I wouldn’t put it in such absolute terms. There are aspects to consider when picking the speed. E.g., if there are lots of messages, higher speed is needed to get them across (that’s why the 5Hz version of the 18x has a higher default speed). Or if one doesn’t have PPS and wants to derive time from the serial stream only. Then, higher speeds help reduce the delay from the top of the second until the edge of the first message is received.

On the other hand, if the receiver is slow (e.g., a simple embedded controller), or if the environment is noisy, lower speeds may be needed/helpful.

But in general, it shouldn’t matter much. And also in this case, there is no general issue. It is just that the intelligent detection mechanism within gpsd is challenged to handle the specific combination of serial data and pulse.

Ok, good and bad. Bad because it means this is related to kernel PPS vs. user space PPS (but could also be Linux vs. BSD). Good because that suggests/supports that the issue is more likely with the code part that is specific to user space PPS.

Try journalctl --since 15:00, replacing the time as appropriate.

I know what you mean :smile:. And I just notice I had a typo: I meant to say -n, not -g, sorry for that.

Ah, ok, I missed that you have a 19x, since the title of the thread was referring to the 18x. I had already wondered a bit because I don’t think the 18x would even go as high as 38400 baud. From the specs, I am now also seeing that it appears to have 10Hz serial message output at that 38400 baud. That would challenge gpsd when trying to match serial data and PPS. Though in the various output logs you show, the rate seems to be 1 Hz only, but maybe that is causing the slightly strange timing of the serial data, e.g., RMC being sent after GGA for the same epoch. According to the device spec, RMC should be the first sentence in an epoch.

For compatibility, most devices use that as default output format. But many devices also have a proprietary binary format that can be enabled that gives enhanced functionality, even beyond the proprietary NMEA-like messages in text format. E.g., the u-blox protocol description has hundreds of pages, with the binary protocol description being several times longer than the NMEA protocol description. Garmin also seems to have a proprietary binary protocol. At least for the u-blox, since the binary protocol is much more powerful than the NMEA protocol, gpsd automatically switches the receiver to it if it detect a u-blox type device. Could be the same for the Garmin units. Try omitting the -n when running gpsmon to see whether it is emitting any non-text messages. With -n, on my u-blox, someone - gpsd, gpsmon? - is synthesizing NMEA messages from the binary data, not giving a clear picture as far as the actual contents and timing of the device’s output is concerned.

Not sure, maybe it is using Linux-specific functionality? You mentioned that ntpd was also having issues with the SHM interface. Though with SHM working with chronyd, that doesn’t seem to be a general issue.

Yeah, that is indeed a challenge. Building the original Linux kernel might still be doable. But most distributions in one way or another modify the Linux kernel, so the first challenge is to get the sources of those modified kernels.

If you decide to proceed on that route, consider using simplified patches, vs. what you referred to earlier. Unless you need it to be configurable at run-time whether CTS or DCD is used, the patch can be stripped down considerably.

That would be my suggestion, seeing that there seems to be a timing issue (you observing a correlation between the delay of the serial data being below or above 100 ms, and PPS being off or not).

If I understood correctly, FreeBSD was using kernel PPS, so that is one difference. Then, BSD and Linux being different systems in itself could also make a decisive difference. Remember, we’re talking about some milliseconds, or fractions thereof, apparently making a difference.

Ah, great! Let’s see what comes back.

Hmm, I am logged into GitLab, but still get a β€œnot found” error??

PS: Given that, while still largely related to the original topic of this thread, the discussion has now gone into some depth into specific issues, I would try to move the part about user space PPS into its own, separate thread - unless you object, and obviously only if I figure out how to do it, and have the needed system privileges to do so.


No worries. I gave up.

KPPS on CTS pin does not work even with the pps_ldisc module loaded on Ubuntu 24.04. I think it’s a limitation of the kernel. I opened a kernel feature request to add KPPS on CTS, so maybe sometime in the next century the feature will be added.

GPSD normal PPS does not work even though it claims it will even on CTS pin. I was able to force normal PPS by not including the timepps.h header file you suggested above, but then it picks the wrong edge. GPSD’s own ppscheck tool even has comments that specially state that PPS on CTS is a viable method. Gary’s comments seem like there is something there to be investigated, but his feedback was very condescending. I have no room for that type of interaction in my life right now. So I threw in the towel.

* Possible pins for PPS: DCD, CTS, RI, DSR. Pinouts:
 * DB9  DB25  Name      Full name
 * ---  ----  ----      --------------------
 *  3     2    TXD  --> Transmit Data
 *  2     3    RXD  <-- Receive Data
 *  7     4    RTS  --> Request To Send
 *  8     5    CTS  <-- Clear To Send
 *  6     6    DSR  <-- Data Set Ready
 *  4    20    DTR  --> Data Terminal Ready
 *  1     8    DCD  <-- Data Carrier Detect
 *  9    22    RI   <-- Ring Indicator
 *  5     7    GND      Signal ground

Yes, unfortunately, it is. Just if you like, and even in case you’re not too familiar with C, you can take a look at the patch set you shared. Even without understanding much of what is going on in detail, maybe one can see that in one place, there is a piece of code that is pretty much a duplicate of something that’s already there. That is about calling the PPS functionality from within the interrupt handler not only for DCD, but adding the same logic for CTS.

Ok, sorry to hear. That is what I referred to that it might happen when I said that he can be helpful if/when he is sympathetic…

Yep, fully understand. This kind of experience is why I eventually stopped contributing to the project as well.

So again, fully only if you feel like it, maybe in a day or two, is to see what happens if the speed is reduced (maybe it’ll magically start working - not a solution to the actual problem, but good enough for the purpose), and/or providing the logs to see what is going on, attempting an actual fix, or maybe also getting just enough of an understanding to find a more palatable workaround.

But just suggestions if/should you feel like it maybe in a few days, but entirely up to you.