<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>GPS &#8211; Managing your Modems Since 2007</title>
	<atom:link href="https://ioncontrol.co/blog/tag/gps/feed/" rel="self" type="application/rss+xml" />
	<link>https://ioncontrol.co/blog</link>
	<description></description>
	<lastBuildDate>Wed, 03 Jun 2026 14:58:18 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://ioncontrol.co/blog/wp-content/uploads/2025/06/logo-150x150.png</url>
	<title>GPS &#8211; Managing your Modems Since 2007</title>
	<link>https://ioncontrol.co/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>A GPS-synced NTP Server for Small Cells</title>
		<link>https://ioncontrol.co/blog/2025/10/05/a-gps-synced-ntp-server-for-small-cells/</link>
		
		<dc:creator><![CDATA[dan]]></dc:creator>
		<pubDate>Sun, 05 Oct 2025 11:36:21 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[eNB]]></category>
		<category><![CDATA[GPS]]></category>
		<category><![CDATA[LTE]]></category>
		<category><![CDATA[ModemManager]]></category>
		<guid isPermaLink="false">http://ioncontrol.co/blog/?p=35</guid>

					<description><![CDATA[Base stations need accurate time for framing and synchronization; they&#8217;ll often have GPS antenna ports and provide options for NTP or PTP clock sync. But what if you have a couple base stations and you don&#8217;t want all those GPS antennas hanging near the window? You get a GPS receiver and feed it to an [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Base stations need accurate time for framing and synchronization; they&#8217;ll often have GPS antenna ports and provide options for NTP or PTP clock sync. But what if you have a couple base stations and you don&#8217;t want all those GPS antennas hanging near the window?</p>



<p class="wp-block-paragraph">You get a GPS receiver and feed it to an NTP server, that&#8217;s what. Let&#8217;s do it.</p>



<h4 class="wp-block-heading"><strong>GPS Receiver</strong></h4>



<p class="wp-block-paragraph">The most important choice is which GPS receiver. You want one that provides &#8220;Pulse Per Second&#8221; (PPS) so the time server knows when each second actually starts and can compensate for latency or jitter. That significantly limits your options, but aside from overbuilt marine units like the Garmin 16x you can usually find cheap <a href="https://www.u-blox.com/">u-blox</a> modules like the 7M, M8, or F9. My advice: look at pictures of the board and make sure something says &#8220;PPS&#8221; before you buy it.</p>



<p class="wp-block-paragraph">I&#8217;m going to use USB because the <a href="https://www8.hp.com/h20195/v2/GetPDF.aspx/c06040430.pdf">HP EliteDesk 800 G4 mini</a> that will be my NTP server doesn&#8217;t have a serial port and if I added a serial port then I couldn&#8217;t have two NICs. And while USB PPS is less accurate than serial-based PPS, that doesn&#8217;t really matter for my application.</p>



<p class="wp-block-paragraph">I looked hard for an off-the-shelf USB GPS unit that had PPS capability via USB but those either don&#8217;t exist or are hard to find. So I chose a u-blox NEO-7M based board with a clearly marked PPS pin next to the TX/RX pins. Plus it had an external antenna connector (not shown below, but present at J1 as shipped).</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img fetchpriority="high" decoding="async" width="572" height="543" src="https://ioncontrol.co/blog/wp-content/uploads/2025/09/Image.jpeg" alt="uBlox NEO-7M GPS board" class="wp-image-37" style="width:438px;height:auto" srcset="https://ioncontrol.co/blog/wp-content/uploads/2025/09/Image.jpeg 572w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/Image-300x285.jpeg 300w" sizes="(max-width: 572px) 100vw, 572px" /></figure>



<p class="wp-block-paragraph">The board has a USB-C port already and I hoped the PPS signal would be connected there so I wouldn&#8217;t have to buy an RS-232/USB converter. Spoiler alert: it isn&#8217;t. This is quite silly. So we need a USB serial cable too.</p>



<h4 class="wp-block-heading"><strong>The USB Serial Cable</strong></h4>



<p class="wp-block-paragraph">The PPS signal usually arrives on the host as one of the RS-232 signals like DCD or RI or CTS. So I need a cable that exposes those signals and can just connect to my GPS board&#8217;s serial pins. I found an <a href="https://ftdichip.com/products/c232hd-ddhsp-0/">FTDI C232HD-DDHSP-0</a> with all the required signals broken out.</p>



<p class="wp-block-paragraph">Since it&#8217;s gotta look nice everything is going into a small project box, which means I need to drill a hole for the USB serial cable and its cord grip.</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img decoding="async" width="712" height="1024" src="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6099-712x1024.jpeg" alt="Drilling a hole in the project box for the USB serial cable cord grip" class="wp-image-38" style="width:397px;height:auto" srcset="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6099-712x1024.jpeg 712w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6099-208x300.jpeg 208w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6099-768x1105.jpeg 768w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6099-1068x1536.jpeg 1068w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6099-1423x2048.jpeg 1423w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6099-scaled.jpeg 1779w" sizes="(max-width: 712px) 100vw, 712px" /></figure>



<figure class="wp-block-image aligncenter size-large"><img decoding="async" width="1024" height="506" src="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6100-1024x506.jpeg" alt="Cord grip installed in the project box" class="wp-image-39" srcset="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6100-1024x506.jpeg 1024w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6100-300x148.jpeg 300w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6100-768x379.jpeg 768w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6100-1536x759.jpeg 1536w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6100-2048x1012.jpeg 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">On to the antenna; it&#8217;s just a male SMA to female SMA bulkhead cable:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="751" src="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6105-1024x751.jpeg" alt="Everything in the project box with the antenna connector" class="wp-image-40" srcset="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6105-1024x751.jpeg 1024w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6105-300x220.jpeg 300w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6105-768x563.jpeg 768w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6105-1536x1126.jpeg 1536w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6105-2048x1501.jpeg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Time to hook up the board&#8230; I connected the cable RX to the board&#8217;s TXD, cable TX to board&#8217;s RXD, and VCC and GND.</p>



<p class="wp-block-paragraph">What about PPS? I first tried the Ring Indicator (RI) signal and could not get PPS to be recognized by gpsd, while ppscheck worked fine. It turns out gpsd doesn&#8217;t read the serial signals directly but tries to use the kernel&#8217;s pps_ldisc driver which only supports DCD. Lame.</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="907" height="1024" src="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6107-907x1024.jpeg" alt="Serial cable connected to the GPS board" class="wp-image-41" style="width:489px;height:auto" srcset="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6107-907x1024.jpeg 907w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6107-266x300.jpeg 266w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6107-768x867.jpeg 768w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6107-1360x1536.jpeg 1360w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6107-1814x2048.jpeg 1814w" sizes="auto, (max-width: 907px) 100vw, 907px" /></figure>



<h4 class="wp-block-heading"><strong>Everything Else</strong></h4>



<p class="wp-block-paragraph">With the board hooked up and tested I added two mounting brackets and insulated the inside with hot glue.</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="801" height="1024" src="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6110-801x1024.jpeg" alt="GPS board fit into the case with hot glue covering the nuts holding the external brackets in place." class="wp-image-42" style="width:548px;height:auto" srcset="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6110-801x1024.jpeg 801w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6110-235x300.jpeg 235w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6110-768x982.jpeg 768w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6110-1201x1536.jpeg 1201w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6110-1601x2048.jpeg 1601w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6110-scaled.jpeg 2001w" sizes="auto, (max-width: 801px) 100vw, 801px" /></figure>



<p class="wp-block-paragraph">Screw on the top, and we&#8217;re ready to set up the software side of things.</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="740" src="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6111-1024x740.jpeg" alt="The finished GPS box with the USB serial cable." class="wp-image-43" style="width:548px;height:auto" srcset="https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6111-1024x740.jpeg 1024w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6111-300x217.jpeg 300w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6111-768x555.jpeg 768w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6111-1536x1111.jpeg 1536w, https://ioncontrol.co/blog/wp-content/uploads/2025/09/IMG_6111-2048x1481.jpeg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h4 class="wp-block-heading"><strong>Double-check</strong></h4>



<p class="wp-block-paragraph">It&#8217;s worth running <code>gpsmon /dev/ttyUSB0</code> to ensure everything (including PPS) is working before getting <code>gpsd</code> running. It should look something like this:</p>



<pre class="wp-block-code alignwide has-small-font-size" style="border-style:none;border-width:0px"><code><code>┌──────────────────────────────────────────────────────────────────────────────┐
│Time: 2025-09-26T01:57:18.000Z   Lat: YY ZZ.046970' N   Lon:  ZZ XX.045530' W │
└───────────────────────────────── Cooked TPV ─────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ GPRMC GPVTG GPGGA GPGSA GPGSV GPGLL                                          │
└───────────────────────────────── Sentences ──────────────────────────────────┘
┌───────────────────────┌─────────────────────────┌────────────────────────────┐
│ SVID  PRN  Az El SN HU│Time:     015718.00	  │Time:      015718.00        │
│GP  2    2 101 30 26  Y│Latitude:   XXXX.04697 N │Latitude:  XXXX.04697       │
│GP  7    7 113 81 29  Y│Longitude: YYYYY.04553 W │Longitude: YYYYY.04553      │
│GP  8    8  75 48 41  Y│Speed:    0.223          │Altitude:  267.9            │
│GP 27   27  22 17 33  Y│Course:                  │Quality:   1   Sats: 05     │
│GP 30   30 156 65 30  Y│Status:   A        FAA:A │HDOP:      2.42             │
│GP  9    9  67 15 25  N│MagVar:                  │Geoid:     -30.9            │
│                       └───────── RMC ───────────└─────────── GGA ────────────┘
│                       ┌─────────────────────────┌────────────────────────────┐
│                       │Mode: A3 Sats: 2 7 8 27 +│UTC:           RMS:         │
│                       │DOP H=2.42 V=3.44 P=4.21 │MAJ:           MIN:         │
│                       │TOFF: -1.280474680       │ORI:           LAT:         │
│                       │PPS: -1.412210534        │LON:           ALT:         │
└──────── GSV ──────────└────── GSA + PPS ────────└─────────── GST ────────────┘</code></code></pre>



<p class="wp-block-paragraph"><code>gpsmon</code> talks directly to the device so it will read PPS on more RS-232 control signals than the kernel&#8217;s PPS driver supports. Just something to watch out for in case <code>gpsmon</code> shows PPS but <code>gpsd</code> doesn&#8217;t.</p>



<h4 class="wp-block-heading"><strong>gpsd</strong></h4>



<p class="wp-block-paragraph">On the software side the GPS module is managed by <code>gpsd</code>, which autodetects vendor quirks and (as long as you&#8217;re using DCD for PPS) configures the kernel&#8217;s PPS device for you. </p>



<p class="wp-block-paragraph">My simple Fedora <code>gpsd</code> config gets written to <code>/etc/sysconfig/gpsd</code> but other distros use <code>/etc/default/gpsd</code>.</p>



<pre class="wp-block-code"><code># Options for gpsd, including serial devices
OPTIONS="-n"
DEVICES="/dev/ttyUSB0"
# Set to 'true' to add USB devices automatically via udev
USBAUTO="true"</code></pre>



<p class="wp-block-paragraph">Later I&#8217;ll be replacing the <code>/dev/ttyUSB0</code> device path with a more permanent one since there will eventually be 10+ modems connected and USB device names are notoriously unstable.</p>



<p class="wp-block-paragraph">Now I can start <code>gpsd</code>:</p>



<pre class="wp-block-code"><code>systemctl start gpsd</code></pre>



<p class="wp-block-paragraph">and double-check one last time by running <code>gpsmon</code> again, but this time without arguments since it will automatically use data from the running <code>gpsd</code>.</p>



<h4 class="wp-block-heading"><strong>chrony</strong></h4>



<p class="wp-block-paragraph">Now that I have GPS up and running it&#8217;s time for NTP, which was the whole point of this exercise. <code>gpsd</code> writes location and timing data from GPS receivers to a well-known Shared Memory (SHM) segment that other daemons like <code>ntpd</code> or <code>chrony</code> read. I&#8217;m going to use <code>chrony</code>.</p>



<p class="wp-block-paragraph">Here&#8217;s my minimal <code>/etc/chrony.conf</code>:</p>



<pre class="wp-block-code alignwide"><code># Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift

# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3

# Enable kernel synchronization of the real-time clock (RTC).
rtcsync

# Allow NTP client access from local network.
allow all

# Serve time even if not synchronized to a time source.
local stratum 1

# Set the TAI-UTC offset of the system clock.
leapseclist /usr/share/zoneinfo/leap-seconds.list

# Specify directory for log files.
logdir /var/log/chrony

# Listen to gpsd for reference time
refclock SHM 0 refid GPS precision 1e-1 offset 0.0
refclock SHM 1 refid PPS precision 1e-7
</code></pre>



<p class="wp-block-paragraph">The important bits are the &#8220;<code>allow all</code>&#8220;, &#8220;<code>local stratum 1</code>&#8220;, and both &#8220;<code>refclock</code>&#8221; lines. The NTP server will be on an isolated network so I don&#8217;t care much about limiting access by source IP. The <code>local stratum 1</code> ensures that NTP clients see the server as high-precision (which it is!). The <code>refclock</code> lines are what tells <code>chrony</code> to get timing information from <code>gpsd</code> for both GPS time and PPS high precision adjustment. The <code>SHM 0</code> and <code>SHM 1</code> are <code>gpsd</code>&#8216;s predefined shared memory segments for GPS and PPS info, respectively. Eventually I&#8217;ll do some tests and figure out better timing offsets, but not today.</p>



<p class="wp-block-paragraph">Now I can start <code>chronyd</code>:</p>



<pre class="wp-block-code"><code>systemctl start chronyd</code></pre>



<p class="wp-block-paragraph">and double-check that it&#8217;s working and synced to GPS by running <code>chronyc sources</code> a couple times until it&#8217;s synced up:</p>



<pre class="wp-block-code alignwide"><code>$ chronyc sources
MS Name/IP address         Stratum Poll Reach LastRx Last sample               
===============================================================================
#x GPS                           0   4   177    14    +81ms&#91;  +81ms] +/-  100ms
#x PPS                           0   4   177    16    -35ms&#91;  -35ms] +/-   98us
</code></pre>



<p class="wp-block-paragraph">and finally double-check by sending an NTP request to the server:</p>



<pre class="wp-block-code alignwide"><code>$ ntpcheck utils ntpdate -s localhost

Server: localhost:123, Stratum: 1, Requests 3
Last Request:Offset: 0.000000s (0us) | Delay: 0.000058s (58us)
Correct Time is 2025-10-02 19:29:15.287043328 -0500 CDT

Average (3 requests):
Offset: 0.000015s (15us) | Delay: 0.000094s (94us)</code></pre>



<p class="wp-block-paragraph">And now I&#8217;m done! Time to point my base stations to this <code>chronyd</code>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
