<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://satpulse.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://satpulse.net/" rel="alternate" type="text/html" /><updated>2026-04-11T08:04:39+00:00</updated><id>https://satpulse.net/feed.xml</id><title type="html">SatPulse</title><subtitle>SatPulse makes it easy to use a GPS receiver as a source of time for a PTP and NTP server</subtitle><author><name>James Clark</name></author><entry><title type="html">Building an NTP server on a Raspberry Pi with chrony or ntpd-rs</title><link href="https://satpulse.net/2026/04/06/building-an-ntp-server-on-a-raspberry-pi-with-chrony-or-ntpd-rs.html" rel="alternate" type="text/html" title="Building an NTP server on a Raspberry Pi with chrony or ntpd-rs" /><published>2026-04-06T00:00:00+00:00</published><updated>2026-04-06T00:00:00+00:00</updated><id>https://satpulse.net/2026/04/06/building-an-ntp-server-on-a-raspberry-pi-with-chrony-or-ntpd-rs</id><content type="html" xml:base="https://satpulse.net/2026/04/06/building-an-ntp-server-on-a-raspberry-pi-with-chrony-or-ntpd-rs.html"><![CDATA[<p>In an <a href="https://satpulse.net/2026/04/01/using-satpulse-for-timing-without-a-phc.html">earlier post</a>, I described how SatPulse can now work without the specialised PHC hardware it previously required. This means, in particular, that it is possible to use SatPulse to build an NTP server on a regular Raspberry Pi, rather than requiring a Raspberry Pi CM4 or CM5.</p>

<p>In this post, I will explain how to do this, using two different NTP servers: chrony and ntpd-rs.</p>

<h2 id="hardware">Hardware</h2>

<p>Before we get into the details of configuration, let me say something about GPS hardware.
When using a Raspberry Pi, the PPS signal is connected to a GPIO pin on the Pi.
This means that the time of the PPS edge is measured in software by the kernel,
rather than in hardware.
The accuracy you will get is in the microsecond range.
Basic GPS receivers can achieve an accuracy of 50ns in normal conditions.
So my main hardware advice is that in this context a fancy, expensive GPS receiver will not deliver any measurable benefit.</p>

<p>GPS receivers are available in a Pi HAT form factor, which plugs into the 40-pin GPIO header.
These HATs typically wire the GPS PPS signal to pin 12 (GPIO 18).
I do not recommend these when using PHC hardware, because the GPS PPS signal needs to be wired to
the SYNC_OUT pin on a different header.
But when using a normal Raspberry Pi, they are very convenient,
although you typically pay a premium for this convenience.</p>

<h2 id="setup">Setup</h2>

<p>The following steps can be done exactly as described in <a href="https://satpulse.net/setup/index.html">SatPulse Setup Guide</a>.</p>

<ol>
  <li><a href="https://satpulse.net/setup/rpi-os.html">Install Raspberry Pi OS</a></li>
  <li><a href="https://satpulse.net/setup/rpi-uart.html">Configure the UART</a></li>
  <li><a href="https://satpulse.net/setup/satpulse-install.html">Install SatPulse</a> but choose the 0.2 pre-release</li>
  <li><a href="https://satpulse.net/setup/gps-serial.html">Verify the GPS serial connection</a>; the device will be <code class="language-plaintext highlighter-rouge">/dev/ttyAMA0</code></li>
</ol>

<p>Configuration of kernel PPS is different.
First, configure pin 12 (GPIO 18) as a PPS pin, by adding the following at the bottom of <code class="language-plaintext highlighter-rouge">/boot/firmware/config.txt</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dtoverlay=pps-gpio,gpiopin=18
</code></pre></div></div>

<p>Reboot after this. To verify the PPS signal is working, install pps-tools using</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install pps-tools
</code></pre></div></div>

<p>Then do:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo ppstest /dev/pps0
</code></pre></div></div>

<p>It should show PPS events once per second:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1775392263.000000336, sequence: 170178 - clear  0.000000000, sequence: 0
source 0 - assert 1775392264.000000215, sequence: 170179 - clear  0.000000000, sequence: 0
source 0 - assert 1775392264.999998780, sequence: 170180 - clear  0.000000000, sequence: 0
source 0 - assert 1775392265.999999085, sequence: 170181 - clear  0.000000000, sequence: 0
</code></pre></div></div>

<p>Exit with Ctrl-C.</p>

<p>Now you need to edit <code class="language-plaintext highlighter-rouge">/etc/satpulse.toml</code>.  Most important point is to remove the <code class="language-plaintext highlighter-rouge">[phc]</code> section.
All you need is this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[serial]
device = "/dev/ttyAMA0"
# fix to match your serial device speed
speed = 9600

[gps]
config = true
vendor = "u-blox"

# Enable HTTP monitoring on port 2000
[[http]]
listen = ":2000"

[ntp]
# Use this for chrony
sock.path = "/var/run/chrony.satpulse.sock"
# Use this for ntpd-rs
# sock.path = "/run/ntpd-rs/satpulse.ttyAMA0.sock"
</code></pre></div></div>

<p>Uncomment the sock.path line for whichever of chrony or ntpd-rs you use.</p>

<p>You can now start SatPulse using</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl start satpulse@ttyAMA0
</code></pre></div></div>

<p>You can point a browser at port 2000 and you should get a page showing information
from the GPS receiver.</p>

<h3 id="chrony">Chrony</h3>

<p>To configure chrony, first install with</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install chrony
</code></pre></div></div>

<p>Then create a file <code class="language-plaintext highlighter-rouge">/etc/chrony/conf.d/satpulse.conf</code> with the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>refclock PPS /dev/pps0 poll 2 lock GPS refid PPS
refclock SOCK /var/run/chrony.satpulse.sock offset 0.1 delay 0.2 refid GPS noselect
</code></pre></div></div>

<p>The refclock PPS line makes chrony use the kernel PPS API to read PPS samples from /dev/pps0.
These are accurate but lack time-of-day information.
The refclock SOCK line makes chrony read samples generated by satpulsed.
These are inaccurate but include time-of-day.
The <code class="language-plaintext highlighter-rouge">offset 0.1</code> corrects for serial messages coming 0.1 second after the top of the second.</p>

<p>Chrony can use the samples from satpulsed to complete the PPS samples.
Chrony can also use samples from other NTP servers to complete the samples,
so if you want to check that this is really working,
you should at least temporarily comment out the <code class="language-plaintext highlighter-rouge">pool</code> line from <code class="language-plaintext highlighter-rouge">/etc/chrony/chrony.conf</code>.</p>

<h3 id="ntpd-rs">ntpd-rs</h3>

<p>Ubuntu has <a href="https://discourse.ubuntu.com/t/ntpd-rs-its-about-time/79154">announced</a> plans to adopt <a href="https://github.com/pendulum-project/ntpd-rs">ntpd-rs</a> as its default NTP server, replacing chrony, primarily because of memory safety.
Since SatPulse is written in Go, the combination of ntpd-rs and SatPulse provides a fully memory-safe timing stack.
Like SatPulse, ntpd-rs uses TOML for its configuration files and supports Prometheus,
so the combination makes for a pleasantly harmonious configuration and observability experience.</p>

<p>To install, download a deb file from the <a href="https://github.com/pendulum-project/ntpd-rs/releases">Releases page</a>.
You need at least version 1.7.2.
(The version in Raspberry Pi OS will not work.)</p>

<p>Next, you will need to remove your existing NTP daemon (e.g. systemd-timesyncd or chrony):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt remove systemd-timesyncd chrony
</code></pre></div></div>

<p>Then install ntpd-rs</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dpkg -i ./ntpd-rs_1.7.2-1_arm64.deb
</code></pre></div></div>

<p>Now you need to set things up so that ntpd-rs can access <code class="language-plaintext highlighter-rouge">/dev/pps0</code>.
Create a file <code class="language-plaintext highlighter-rouge">/etc/udev/rules.d/99-ntpd-rs-pps.rules</code> containing the line</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>KERNEL=="pps0", GROUP="ntpd-rs", MODE="0640"
</code></pre></div></div>

<p>This ensures that when /dev/pps0 is created at boot time it will have a group and permissions
that enables ntpd-rs to access it. To make this take effect without booting, do</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo udevadm control --reload
sudo udevadm trigger /dev/pps0
</code></pre></div></div>

<p>Now we need to configure ntpd-rs.
Put the following in <code class="language-plaintext highlighter-rouge">/etc/ntpd-rs/ntp.toml</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[observability]
log-level = "debug"
observation-path = "/var/run/ntpd-rs/observe"

[[source]]
mode = "pps"
path = "/dev/pps0"
precision = 1e-7
accuracy = 1e-6

[[source]]
mode = "sock"
precision = 1e-2
path = "/run/ntpd-rs/satpulse.ttyAMA0.sock"
accuracy = 0.2

[synchronization]
minimum-agreeing-sources = 1

[[server]]
listen = "0.0.0.0:123"
</code></pre></div></div>

<p>When I tried initially to get this working with version 1.7.1 of ntpd-rs,
it didn’t work. I submitted an <a href="https://github.com/pendulum-project/ntpd-rs/issues/2169">issue</a>,
and within a day one of the developers, David Venhoek, had added a <a href="https://github.com/pendulum-project/ntpd-rs/pull/2171">feature</a> to enable this configuration.
This was included in version 1.7.2, released a few days ago.
It adds the ability to specify the accuracy of sources as distinct from their precision.
Accuracy means how close measurements are to true time.
Precision means how much measurements vary from each other.
The serial timing measurements from satpulsed are quite precise
but are extremely inaccurate.
Specifying a low accuracy for samples from satpulsed
ensures that ntpd-rs uses the PPS samples as the primary source,
and uses the satpulsed samples just to complete the PPS samples.
But note that if you specify an accuracy of 0.25 or worse,
you need to increase <code class="language-plaintext highlighter-rouge">maximum-source-uncertainty</code> from its default of 0.25.</p>

<p>Now you can start ntpd-rs using</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl start ntpd-rs
</code></pre></div></div>

<p>You can verify it’s working by using</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ntp-ctl status
</code></pre></div></div>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[In an earlier post, I described how SatPulse can now work without the specialised PHC hardware it previously required. This means, in particular, that it is possible to use SatPulse to build an NTP server on a regular Raspberry Pi, rather than requiring a Raspberry Pi CM4 or CM5.]]></summary></entry><entry><title type="html">First pre-release of SatPulse 0.2</title><link href="https://satpulse.net/2026/04/01/first-pre-release-of-satpulse-0.2.html" rel="alternate" type="text/html" title="First pre-release of SatPulse 0.2" /><published>2026-04-01T11:15:00+00:00</published><updated>2026-04-01T11:15:00+00:00</updated><id>https://satpulse.net/2026/04/01/first-pre-release-of-satpulse-0.2</id><content type="html" xml:base="https://satpulse.net/2026/04/01/first-pre-release-of-satpulse-0.2.html"><![CDATA[<p>I just made the first pre-release of SatPulse 0.2.</p>

<p>The <a href="https://github.com/jclark/satpulse/releases/tag/v0.2-pre-20260401">release notes</a> have more information.</p>

<p>I would be grateful if you could try it out and let me know how you get on.</p>

<p>I created a <a href="https://github.com/jclark/satpulse/discussions/248">discussion thread</a> where you can ask questions and share experiences.</p>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[I just made the first pre-release of SatPulse 0.2.]]></summary></entry><entry><title type="html">A tour of the GPS modules supported by SatPulse 0.2</title><link href="https://satpulse.net/2026/04/01/a-tour-of-the-gps-modules-supported-by-satpulse.html" rel="alternate" type="text/html" title="A tour of the GPS modules supported by SatPulse 0.2" /><published>2026-04-01T10:15:00+00:00</published><updated>2026-04-01T10:15:00+00:00</updated><id>https://satpulse.net/2026/04/01/a-tour-of-the-gps-modules-supported-by-satpulse</id><content type="html" xml:base="https://satpulse.net/2026/04/01/a-tour-of-the-gps-modules-supported-by-satpulse.html"><![CDATA[<p>For the last 8 months, I have been working on broadening the range of GNSS hardware supported by SatPulse. In 0.1, there was support only for u-blox modules. In 0.2, I have added support for a broad range of modules from other vendors, all of which are Chinese.</p>

<p>Before getting into the details of the supported modules, I want to talk about what supporting a GPS module means.
All GPS modules support NMEA which provides information about navigation solutions as they are computed by the module. GPS modules also support RTCM, which is used both to consume corrections provided to the module and to provide corrections for use by other modules. Apart from that, every GPS module supports one or more vendor-specific protocols. The messages used by these protocols can be divided into two groups. The first group performs a similar function to NMEA: they are messages periodically emitted by the module to provide information about the navigation solutions computed and other aspects of the ongoing operation of the module. The second group performs configuration: these include both requests that are input to the module to query and alter configuration, and messages that are output by the module as responses to these requests. Supporting a module involves enabling SatPulse to make use of both kinds of message.</p>

<p>SatPulse supports vendor-specific periodic messages in the obvious way, by converting vendor-specific messages into a uniform abstraction. In the source code, this abstraction is defined by the <code class="language-plaintext highlighter-rouge">gpsprot</code> (GPS protocol) package. In 0.1, there were abstract messages that provide information about</p>

<ul>
  <li>the current time in either UTC or TAI; also includes sawtooth error/quantization error and time accuracy</li>
  <li>the progress of a survey-in operation</li>
  <li>the next scheduled leap second</li>
  <li>satellite positions and signal strengths</li>
</ul>

<p>In 0.2, there are additional messages that provide information about</p>

<ul>
  <li>the characteristics of a navigation solution; this includes
    <ul>
      <li>the technique used to compute the solution (e.g. ranging code or carrier phase)</li>
      <li>dimensionality of the fix i.e. 3D, 2D or time only</li>
      <li>what sensors beyond GNSS contributed to the solution (e.g. INS)</li>
      <li>kinds of corrections used (e.g. SBAS, RTK, PPP)</li>
      <li>accuracy (position, velocity)</li>
      <li>DOP in all its flavours</li>
      <li>age of differential corrections</li>
      <li>GNSS constellations and bands used in computing the solution</li>
    </ul>
  </li>
  <li>position, in ECEF or geodetic coordinates</li>
  <li>velocity, in ECEF or geodetic coordinates (i.e speed and course-over-ground, or NED)</li>
</ul>

<p>These abstract messages can be serialized as JSON, and can be seen in the event log, which also includes information about pulse edges.
The messages also feed the observability subsystem, which exposes them as Prometheus metrics.</p>

<p>For configuration, there are now two kinds of configuration support as I described in more detail in my <a href="https://satpulse.net/2026/01/29/improving-gps-configuration.html">earlier blog</a>.</p>

<ul>
  <li>high-level configuration, intent-based configuration, where you describe what you want, and SatPulse turns that into the appropriate messages for the specific module</li>
  <li>low-level configuration, which is based on message files</li>
</ul>

<p>High-level configuration in 0.2 provides a similar set of features as in 0.1. There are a couple of new properties that can be set:</p>

<ul>
  <li>minimum elevation angle for satellites to be used in the navigation solution</li>
  <li>RTCM base ID - the reference station ID for the RTCM messages that the module generates</li>
</ul>

<p>I have added a few features to the message file implementation since the last blog:</p>

<ul>
  <li>the message file implementation now understands the request/response patterns of various protocols, which allows it to identify which messages from the module are responses to messages sent</li>
  <li>there is a file inclusion capability, which is useful for allowing model-specific message files to share messages that work across multiple vendor models</li>
</ul>

<p>The high/low-level configuration split leads naturally to two tiers of support. In both tiers, there is</p>

<ul>
  <li>support for generating the full range of abstract messages from the vendor-specific messages</li>
  <li>support for the protocol in the message file implementation</li>
</ul>

<p>The difference is that Tier 1 provides configuration support primarily via the high-level mechanism; message files are used to supplement this to support vendor-specific features that are not exposed via high-level configuration, whereas Tier 2 uses message files to provide configuration support.
For Tier 2, the provided message files use a common set of conventions for tags, which provides a degree of vendor-independence in the user interface.</p>

<p>SatPulse 0.1 had support only for u-blox. In 0.2, there is support for the following vendors</p>

<ol>
  <li>u-blox - tier 1</li>
  <li>Unicore - tier 1</li>
  <li>Quectel - tier 2</li>
  <li>Zhongke/CASIC - tier 2</li>
  <li>Allystar - tier 2</li>
  <li>Techtotop/Taidou - tier 2</li>
  <li>ByNav - tier 2</li>
  <li>ComNav/SinoGNSS - tier 2</li>
</ol>

<p>Apart from u-blox, these vendors are all based in China. There are other Western vendors, but they are all either much more expensive than u-blox (Septentrio, Trimble, NovAtel) or the chips are designed to be integrated into mobile phones and are not available as modules that you can buy separately (e.g. Broadcom). Septentrio has recently come down in price a bit and they also have an excellent reputation for timing, so I hope to add support for Septentrio in the future. I already have some support for NovAtel, because some Chinese vendors have adopted the NovAtel OEM 6/7 series protocol.</p>

<p>I have chosen which Chinese modules to support based on a number of factors:</p>

<ul>
  <li>Are they at least dual-band? Given the low cost of dual-band modules, there are relatively few cases where single-band modules remain interesting.</li>
  <li>Do they have any features that are particularly interesting for precision timing and/or positioning?</li>
  <li>How common and popular are they on Taobao?</li>
  <li>Are there any Western vendors that resell them?</li>
</ul>

<p>Taobao recently started supporting direct shipping to my country of residence,
which makes using Taobao much more convenient.
I have found Taobao vendors to be considerably more knowledgeable about what they are selling than AliExpress vendors.
If you can buy from Taobao, I recommend it.</p>

<h2 id="u-blox">u-blox</h2>

<p>SatPulse supports all u-blox modules starting from LEA-6T all the way through to ZED-X20P. I have tested it on all timing modules:</p>

<ul>
  <li>LEA-6T</li>
  <li>LEA-M8T</li>
  <li>LEA-M8F</li>
  <li>ZED-F9T (both L1/L2 and L1/L5 variants)</li>
  <li>NEO-F10T</li>
</ul>

<p>and on both current high-precision modules:</p>

<ul>
  <li>ZED-F9P</li>
  <li>ZED-X20P</li>
</ul>

<p>and on many standard precision modules, including:</p>

<ul>
  <li>NEO-M9N</li>
  <li>M10050-KB</li>
  <li>NEO-F10N (which is L1/L5)</li>
</ul>

<p>In 0.2, the improvements in u-blox support are:</p>

<ul>
  <li>support for richer information (navigation solution characteristics, position, velocity)</li>
  <li>support for additional configuration properties (minimum elevation and RTCM base id)</li>
  <li>support for ZED-X20P</li>
</ul>

<p>I have done much more testing on u-blox modules than on any other brand.</p>

<h2 id="unicore">Unicore</h2>

<p>There is tier 1 support for the UM980 family, which consists of</p>

<ul>
  <li>UM980, base model, which includes support for Galileo HAS (which is promised but not yet available for u-blox ZED-X20P)</li>
  <li>UM981, adds INS</li>
  <li>UM982, adds dual antennas</li>
  <li>UM960, lower end version, without PPP support</li>
</ul>

<p>These use a protocol which is similar to that used by NovAtel OEM6/7 messages. Periodic data messages have dual binary/ASCII formats;
the packet formats are similar, but the binary packet format has different sync bytes.
Configuration messages are ASCII lines, similar but different from NovAtel.
These modules also have some undocumented support for some periodic data messages that use the same packet format as NovAtel.
This includes the RANGECMPB raw message which can be used by RTKLIB to generate RINEX observation files.</p>

<p>In the West, these modules are distributed by <a href="https://www.ardusimple.com/product/simplertk3b-budget/">ArduSimple</a>, <a href="https://gnss.store/collections/unicore-gnss-modules">gnss.store</a> and <a href="https://www.sparkfun.com/sparkfun-triband-gnss-rtk-breakout-um980.html">SparkFun</a>.</p>

<p>SparkFun have a <a href="https://github.com/sparkfun/SparkFun_RTK_Torch/tree/main/UM980_Firmware">GitHub repo with the latest UM980 firmware</a>.</p>

<p>Unicore have another range of modules UM6XX, which uses a completely different protocol, which is not yet supported.</p>

<h2 id="quectel">Quectel</h2>

<p>Quectel have a bewildering array of GNSS modules. There are two families that I find interesting and that SatPulse supports:</p>

<ul>
  <li>LG290P family - this is Quectel’s high-end module which includes support for Galileo HAS in the most recent firmware
    <ul>
      <li>LG290P is the base model</li>
      <li>LG580P is the dual antenna variant</li>
      <li>LG680P is the LG290P in the 22x17mm u-blox ZED form factor</li>
    </ul>
  </li>
  <li>LC29H family - this is a relatively inexpensive L1/L5 model based on the Airoha AG3335 chipset; there are several variants, of which the most interesting are:
    <ul>
      <li>LC29H(AA) is the best fit for base-station case; it can produce RTCM corrections but does not have the RTK engine that can consume them; it is supposed to get OSNMA support at some point</li>
      <li>LC29H(DA) is the one that is suitable for use as an RTK rover</li>
    </ul>
  </li>
</ul>

<p>Quectel’s vendor specific protocol uses the NMEA proprietary extension mechanism. The NMEA sentences start with <code class="language-plaintext highlighter-rouge">$PQTM</code> (The <code class="language-plaintext highlighter-rouge">P</code> is the NMEA proprietary extension mechanism, and <code class="language-plaintext highlighter-rouge">QTM</code> is Quectel’s 3-letter identifier). The LG290P supports a richer set of PQTM messages than the LC29H.
The LC29H also supports some NMEA proprietary sentences defined by Airoha; these start with <code class="language-plaintext highlighter-rouge">$PAIR</code>.</p>

<p>In the West, SparkFun sell an <a href="https://www.sparkfun.com/sparkfun-quadband-gnss-rtk-breakout-lg290p-qwiic.html">LG290P board</a>.
They also have a <a href="https://github.com/sparkfun/SparkFun_RTK_Postcard/tree/main/Firmware">GitHub repo with the latest LG290P firmware</a>.</p>

<p>On Taobao, a good shop to buy from is <a href="https://mzhtek.taobao.com/">Mozi Technology/Mozihao</a>.</p>

<h2 id="zhongkecasic">Zhongke/CASIC</h2>

<p><a href="https://icofchina.com/">Zhongke Microelectronics</a> is best known for the ATGM332D and ATGM336H series of modules.
The difference between these is the form-factor: ATGM332D uses the u-blox NEO form factor,
whereas the ATGM336H uses the MAX form factor.
The notable feature of these modules is that they are extraordinarily cheap.
The bare module (without a board) is about $1.25 on Taobao.</p>

<p>The vendor-specific protocol is CASIC. CAS stands for Chinese Academy of Sciences,
and Zhongke in Chinese also refers to the Chinese Academy of Sciences. The CASIC protocol is UBX-like.</p>

<p>There are literally dozens of different versions of these modules, and there are some very significant differences between them.</p>

<p>The most common version is ATGM332D-5N31. These module names follow a common pattern.
The ‘5’ indicates the generation of the chip used (in this case, the AT6558).
‘N’ likely stands for navigation. The ‘3’ designates which constellations are supported:
1 means GPS, 2 means BeiDou, 4 means GLONASS, and these are added together to indicate the set of constellations supported. So 3 means GPS+BeiDou and 7 means GPS+BeiDou+GLONASS.
I haven’t figured out how the final digit works.</p>

<p>There is a more recent and less common 6 series (e.g. the ATGM332D-6N74) which adds support for Galileo. This uses the AT6668 chip. More interestingly, there is also an F8 series (e.g. <a href="https://www.icofchina.com/daohang/duopin/2531.html">ATGM332D-F8N76</a>), which is dual-band.
This uses the AT9880 chip.
It is not common yet but I expect it will become common:
like the rest of the ATGM332D series this is extraordinarily cheap, about $2.15 on Taobao;
it seems to be the cheapest dual-band module in the world.</p>

<p>The 5 series use a default baud rate of 9600, whereas the 6 and F8 series use 115200.
More significantly, although they all use the same packet format,
the 6 and F8 series support a different set of messages.
The 5 series uses NAV class messages, whereas the 6 and F8 series uses NAV2 class messages.</p>

<p>There is some English language documentation available for the version of CASIC that uses NAV messages;
there is nothing available on the NAV2 messages.
Fortunately, one of my Taobao sellers was able to supply me with a Chinese language protocol manual which describes this version of CASIC.
SatPulse has support for both the NAV messages and the NAV2 messages.</p>

<p>As well as the low-end ATGM332D/ATGM336H modules, Zhongke also make higher-end modules designed explicitly for timing.
I have the AT632-6T-30. This is similar to the 6 series of the ATGM332D, and is L1 only, but has a full set of timing features, which makes it interesting.
These use a TIM2 class of message. In particular it has a TIM2-TPX message including quantization error.
It is interesting to have a modern L1 timing module; u-blox have not done an L1 timing module since the LEA-M8T.
One noticeable difference is that the peak-to-peak amplitude of the sawtooth error is about 6ns on the AT632 compared to 21ns on the M8T,
presumably reflecting the higher clock speed of the more modern module.</p>

<p>Zhongke provide a GnssToolkit3 application for Windows for configuring their modules.</p>

<h2 id="allystar">Allystar</h2>

<p>I first came across <a href="https://www.allystar.com/en">Allystar</a> because of their TAu1201 module, which has been one of the cheapest L1/L5 modules available.
The <a href="https://xinghewei.tmall.com/">StarRiver store</a> on Taobao sell a variant of their SR1723 board with the TAU1201.
This costs about $7.50. With various fees, shipping and tax to Thailand, the all-in cost is about $10.50.
In the West, it will be a bit more.
The TAU1201 uses their Cynosure III chip. Their latest chip is Cynosure IV, which is available in the TAU951M-P200.
This is a more expensive module that also does RTK.</p>

<p>Allystar provide a Windows app called <a href="https://docs.datagnss.com/rtk-board/files/Satrack_client_V1.31.007.zip">Satrack</a> for working with Allystar modules.</p>

<p>Allystar’s binary protocol is UBX-like. They haven’t done a particularly thorough job of it.
For example, the message that provides information about satellite positions and signals provides
less information than is available via NMEA, and Satrack doesn’t make use of it.</p>

<h2 id="bynav">Bynav</h2>

<p><a href="https://www.bynav.com/en/">Bynav</a> makes M20/M10 series of modules based on their own Alice 22nm SoC.
They are strong in the automotive industry.
I have been mainly testing with the M20, but have also tried the M10.
The M21 adds an IMU on top of the M20; the M22 adds a slightly better grade of IMU.
The M20D adds dual antennas.
The M10 is a smaller-footprint, cheaper, lower-end version of the M20 (update rate of 10Hz vs 50Hz on the M20).
They seem pretty decent for RTK.</p>

<p>Bynav’s vendor-specific protocol is NovAtel-style. Their binary and ASCII packet formats for periodic messages (called logs in NovAtel parlance)
are exactly the same as documented by NovAtel for the OEM6/7 range.
Bynav support some messages defined by NovAtel and add some messages of their own.
The configuration commands are NovAtel-style, but not compatible.</p>

<p>In the West, they are sold by <a href="https://gnss.store/collections/bynav-gnss-modules">gnss.store</a>.</p>

<h2 id="comnavsinognss">ComNav/SinoGNSS</h2>

<p>This company brands itself as <a href="https://www.comnavtech.com/">ComNav Technology</a> for Western audiences,
but uses <a href="https://www.sinognss.com/">SinoGNSS</a> for Chinese audiences.
Their latest modules are the K9xx series.
Base module is <a href="https://www.comnavtech.com/product/oem/k901.html">K901</a>. <a href="https://www.comnavtech.com/product/oem/k902.html">K902</a> adds an IMU. <a href="https://www.comnavtech.com/product/oem/k922.html">K922</a> adds dual antennas.
They all have support for Galileo HAS.
These are widely available on Taobao.</p>

<p>The protocol documentation on the ComNav site is rather out of date.
My Taobao seller gave me the current Chinese-language protocol documentation,
which was from another company called <a href="https://www.qinnav.com/product/module/">Qeetek</a>, which also advertises these modules.
So I suspect these modules are actually made by Qeetek.</p>

<p>The situation with the protocol is very similar to Bynav.
Their binary and ASCII packet formats for periodic messages are exactly the same as NovAtel.
They support some messages defined by NovAtel and some of their own.
The configuration commands are NovAtel-style, but not compatible.
But the configuration system is not in my view well-designed.
One major deficiency is that it does not allow the current configuration to be queried.
Another problem is that responses to configuration commands are not designed to be machine readable:
they are not wrapped in a protocol packet that allows them to be distinguished from other traffic from the module.</p>

<p>Apart from the weakness in the configuration protocol, I found convergence of both Galileo HAS 
and its BeiDou equivalent (B2b-PPP) to be flaky.</p>

<h2 id="techtotoptaidou">Techtotop/Taidou</h2>

<p>I had not heard of <a href="https://www.techtotop.com/enindex.aspx">Techtotop</a>, known in Chinese as Taidou, until a month or so ago.
They make their own GNSS silicon, but they seem to be almost completely unknown outside China.</p>

<p>I find them interesting because they have relatively inexpensive modules specifically designed for timing.
The one I have is the T303-5D, which is an L1/L5 module. This isn’t on their web site, but I believe it is just a smaller footprint
version of the <a href="https://www.techtotop.com/detail.aspx?cid=1088">T302-5D</a>.
I got this module from <a href="https://shop471758324.taobao.com/">this shop on Taobao</a>.</p>

<p>It uses a UBX-style protocol called SDBP, about which there appears to be exactly zero information available in English.
My Taobao seller was able to provide a Chinese language protocol manual with full details.
The quality of documentation and protocol design seem good to me:
it has everything I expect of a timing module, including quantization error reporting.</p>

<p>Techtotop also provide the <a href="https://www.techtotop.com/category.aspx?NodeID=49">TDMonitor</a> application for Windows,
which is similar to u-center (although the UI is all Chinese).</p>

<h2 id="where-satpulse-is-heading">Where SatPulse is heading</h2>

<p>My vision for SatPulse 0.1 was quite narrow: to transfer time from a GPS module to a PTP hardware clock. But it turns out doing a really good job of that requires a
complex and sophisticated GPS subsystem.
The subsystem should work for GPS modules from many vendors not just from u-blox.
When monitoring a timing GPS, you want rich information about the current state of GPS,
including the characteristics of the navigation solution.
And it turns out that getting position information from the GPS is also useful:
for example, you may want to use HAS to establish the fixed position to be used in timing.
If you have done the work to create a GPS subsystem that works well for timing,
you have done at least 80% of the work to create a GPS subsystem that works well for a wide variety of other applications.
It makes sense to do that extra work, because the market for timing is relatively small:
many more people care about precision positioning than care about precision timing.
So this is where I am heading with SatPulse:
precision timing is still a core part of the mission;
but I want to add the few extra features that are needed to make SatPulse useful for
a broader range of applications, in particular precision positioning.</p>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[For the last 8 months, I have been working on broadening the range of GNSS hardware supported by SatPulse. In 0.1, there was support only for u-blox modules. In 0.2, I have added support for a broad range of modules from other vendors, all of which are Chinese.]]></summary></entry><entry><title type="html">Using SatPulse for timing without a PHC</title><link href="https://satpulse.net/2026/04/01/using-satpulse-for-timing-without-a-phc.html" rel="alternate" type="text/html" title="Using SatPulse for timing without a PHC" /><published>2026-04-01T09:45:00+00:00</published><updated>2026-04-01T09:45:00+00:00</updated><id>https://satpulse.net/2026/04/01/using-satpulse-for-timing-without-a-phc</id><content type="html" xml:base="https://satpulse.net/2026/04/01/using-satpulse-for-timing-without-a-phc.html"><![CDATA[<p>Up to now, using SatPulse for timing has required some <a href="/hardware/">very specialized hardware</a>.
Over the last couple of days, I have implemented a feature that removes this requirement.
It works very simply. SatPulse already has the ability to talk to an NTP server using chrony’s refclock SOCK protocol;
this is configured by the <code class="language-plaintext highlighter-rouge">[ntp]</code> section of <code class="language-plaintext highlighter-rouge">satpulse.toml</code>.
SatPulse also has the ability to work without a PTP hardware clock;
this is configured simply by leaving out the <code class="language-plaintext highlighter-rouge">[phc]</code> section of <code class="language-plaintext highlighter-rouge">satpulse.toml</code>.
In this mode, you can use SatPulse for monitoring and for GNSS packet distribution.
What I’ve implemented is to make the <code class="language-plaintext highlighter-rouge">[ntp]</code> section work without a <code class="language-plaintext highlighter-rouge">[phc]</code> section.
In this situation, SatPulse will use the timing of serial messages from the receiver
to generate samples to send to the NTP server.
On its own, the accuracy from serial timing will be very poor: worse than you would typically get from NTP over a network.
But this becomes useful when combined with an NTP server that has the ability to read a PPS signal.
For example chrony has a PPS refclock that uses the kernel PPS subsystem.
Chrony can also read a PPS signal from a PHC using the PHC refclock with the extpps option.
In this case the NTP server uses the much more accurate PPS signal to determine when a second starts,
and will use the information from SatPulse to determine which second it is.</p>

<p>I have hooked this up to SatPulse’s GPS auto-configuration system (enabled with <code class="language-plaintext highlighter-rouge">config=true</code> in the <code class="language-plaintext highlighter-rouge">[gps]</code> section).
So when you have an <code class="language-plaintext highlighter-rouge">[ntp]</code> and no <code class="language-plaintext highlighter-rouge">[phc]</code> section, it will configure the GPS appropriately (e.g enable a PPS,
enable time mode, enable messages reporting UTC time).
I think this will provide quite a nice way of running an NTP server: as well as auto-configuration, you get the
other SatPulse conveniences like a Web dashboard and Prometheus metrics.</p>

<p>I have only tested this very lightly. I used the chrony PHC extpps option to test,
since all my machines are currently set up with the PPS connected to a PHC.
I used the following as the satpulsed configuration:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[serial]
device = "/dev/ttyACM0"
speed = 38400
[gps]
config = true
vendor = "u-blox"
[ntp]
sock.path = "/var/run/chrony.satpulse.clk.sock"
</code></pre></div></div>

<p>and then used this as my chrony configuration:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>refclock PHC /dev/ptp0:extpps:pin=1 width 0.1 poll 2 lock NMEA refid PPS
refclock SOCK /var/run/chrony.satpulse.clk.sock offset 0.1 delay 0.2 refid NMEA noselect
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">lock NMEA</code> option tells chrony to use the samples from SatPulse to complete the PHC samples;
the <code class="language-plaintext highlighter-rouge">noselect</code> option tells chrony not to use the SatPulse samples as an independent time source.</p>

<p>Using chrony to read the PHC like this has one advantage over using SatPulse with a <code class="language-plaintext highlighter-rouge">[phc]</code> section:
SatPulse will adjust the phase and frequency of the PHC, which is what you want for PTP,
whereas chrony will leave it free-running.
This makes it easy to use the hardware-timestamping feature of chrony, which requires
a free-running PHC.
With SatPulse, you would need to use a virtual PHC, which I have not yet implemented support for.
So using chrony like this is right now the best approach if you want NTP hardware timestamping
and are not interested in PTP.</p>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[Up to now, using SatPulse for timing has required some very specialized hardware. Over the last couple of days, I have implemented a feature that removes this requirement. It works very simply. SatPulse already has the ability to talk to an NTP server using chrony’s refclock SOCK protocol; this is configured by the [ntp] section of satpulse.toml. SatPulse also has the ability to work without a PTP hardware clock; this is configured simply by leaving out the [phc] section of satpulse.toml. In this mode, you can use SatPulse for monitoring and for GNSS packet distribution. What I’ve implemented is to make the [ntp] section work without a [phc] section. In this situation, SatPulse will use the timing of serial messages from the receiver to generate samples to send to the NTP server. On its own, the accuracy from serial timing will be very poor: worse than you would typically get from NTP over a network. But this becomes useful when combined with an NTP server that has the ability to read a PPS signal. For example chrony has a PPS refclock that uses the kernel PPS subsystem. Chrony can also read a PPS signal from a PHC using the PHC refclock with the extpps option. In this case the NTP server uses the much more accurate PPS signal to determine when a second starts, and will use the information from SatPulse to determine which second it is.]]></summary></entry><entry><title type="html">Improving SatPulse’s support for GPS configuration</title><link href="https://satpulse.net/2026/01/29/improving-gps-configuration.html" rel="alternate" type="text/html" title="Improving SatPulse’s support for GPS configuration" /><published>2026-01-29T00:00:00+00:00</published><updated>2026-01-29T00:00:00+00:00</updated><id>https://satpulse.net/2026/01/29/improving-gps-configuration</id><content type="html" xml:base="https://satpulse.net/2026/01/29/improving-gps-configuration.html"><![CDATA[<p>In this post, I want to describe some recent improvements in how SatPulse supports GPS configuration.</p>

<h2 id="how-gps-receivers-communicate">How GPS receivers communicate</h2>

<p>To understand why this feature is useful, it helps to understand how GPS receivers work at the protocol level. There are three layers: packet format, periodic messages, and configuration.</p>

<p>GPS receivers communicate over a serial connection by sending and receiving a byte stream consisting of a sequence of packets. Each packet has a specific format, and the stream can mix packets in different formats. A packet format defines how to represent a message type and a type-specific structured payload as a sequence of bytes with a checksum.  NMEA 0183 is the most important standard packet format. A NMEA packet is called a sentence: it starts with <code class="language-plaintext highlighter-rouge">$</code> and ends with a two-character hex checksum and CR/LF. The payload is represented as comma-delimited ASCII fields. RTCM 3 is another standard packet format, used for GPS correction data such as RTK base station observations. It uses a binary encoding with a 24-bit checksum. Many vendors define their own proprietary binary packet formats. For example, u-blox defines the UBX format.</p>

<p>GPS receivers compute position-velocity-time (PVT) solutions periodically, typically once per second. For each solution, they output messages with the results of the solution, and other information about how the solution was computed (such as the satellites used). These messages will be represented as message types in a packet format. NMEA defines standard sentence types, such as RMC, GGA, GSV for this and all GPS receivers support them. Almost all GPS receivers can also emit other periodic messages that provide information beyond what can be expressed by standard NMEA sentences. Some GPS receivers make use of the extensibility mechanism provided by NMEA (“proprietary sentences” beginning with <code class="language-plaintext highlighter-rouge">$P</code>), but many others use messages in proprietary binary packet formats.</p>

<p>All GPS receivers provide a configuration mechanism. At a minimum they provide a way to control the speed of the serial connection and which messages are emitted. But most GPS receivers allow for many aspects of their operation to be configured, for example, which constellations they should use or the width of the PPS pulse that they should generate. For GPS configuration, it is completely vendor-dependent: there is no standard. Configuration requires communication in both directions: from host to GPS receiver and well as from GPS receiver to host. As with periodic messages, configuration uses packets. RTCM 3 messages go both from host to receiver as well as from receiver to host, so GPS receivers need to be able to deal with a mix of packet formats in the input they receive.</p>

<p>I have seen three different approaches to configuration.</p>

<ul>
  <li>Configuration uses the same binary packet format used for periodic messages. UBX is the most common example of this. A few other vendors use UBX-like protocols: CASIC (Zhongke Microsystems) and Allystar</li>
  <li>Configuration requests (from host to receiver) use NMEA proprietary sentences; responses may be either standard NMEA TXT sentences or proprietary sentences. Quectel is one vendor that uses this approach.</li>
  <li>Configuration requests use plain ASCII lines (usually CRLF terminated); responses are typically some sort of ASCII packet. Septentrio and NovAtel both use this approach. Their products are both very high-end, but several Chinese vendors have adopted protocols based on NovAtel, notably Unicore, SinoGNSS (ComNav) and ByNAV, in more affordable products.</li>
</ul>

<h2 id="satpulse-01">SatPulse 0.1</h2>

<p>SatPulse 0.1 internally uses protocol-independent interfaces for all three layers, but they are implemented only for UBX.</p>

<p>It might seem unnecessary to have such abstraction for only one protocol, but in fact UBX has many versions which have major differences.
Only the packet layer of UBX remains constant between versions. The periodic message layer has significant differences: different versions of receivers support different messages. With the configuration layer, u-blox receivers from generation 9 onwards use a completely different approach from receivers of generation 8 and earlier. The earlier generations have separate message types for each aspect of configuration. The later generation uses a single message that wraps a completely separate key-value configuration system: this allows multiple configuration changes to be performed atomically by a single message.</p>

<p>The fundamental idea in 0.1 is to create protocol independent representation of all the configuration that needs to be done. This representation has two parts:</p>
<ul>
  <li>it specifies the desired values for various properties; for example, it might say that the time pulse should have a pulse width of 0.1s,</li>
  <li>it specifies various operations that should be performed; for example, it might say that the configuration of the receiver should be saved to non-volatile memory
This representation is handed over to a protocol-specific subsystem, which does as much of what was requested as the receiver supports.
It then returns the actual properties that the receiver was able to implement. For example, there is a property for the set of GNSS signals that should be enabled. If you request that L1 and L5 signals should be enabled, but the receiver only supports L1, then it will enable L1 and return a property with only L1 being enabled.</li>
</ul>

<p>You might ask: why bother with all this? SatPulse works best if the receiver is configured in very specific ways. My goal is to provide a “just works” experience: so I want SatPulse to be able to detect what kind of receiver it is and automatically configure it so that it works optimally. SatPulse 0.1 does achieve this for a broad range of UBX receivers.</p>

<h2 id="multi-protocol-support">Multi-protocol support</h2>

<p>One of my main goals for SatPulse after version 0.1 is to make it truly multi-protocol. It is one thing to design an interface to be protocol-independent. It is another thing for it to actually work well for a range of protocols. In choosing the second protocol to support, I wanted a protocol that was rich and sophisticated, completely different from UBX and has decent documentation.</p>

<p>I decided on the protocol implemented by the UM98x series of GPS receivers from Unicore, which is based on the NovAtel OEM 6 and 7 protocols. This is primarily designed for the RTK market, but it works well for timing also. Periodic messages (which are called logs in the NovAtel world) have dual binary/ASCII representations. Configuration requests use plain ASCII lines; responses are NMEA-like.</p>

<p>In implementing the configuration support for the UM98x, I substantially reworked the configuration interface.
The objective is to make the protocol-specific code as simple and testable as possible.
The protocol-specific code is completely deterministic and does no IO.
An intermediate protocol-independent orchestration layer does IO and manages retries and timeouts.</p>

<p>The documented UM98x packet formats and logs are similar but distinct from the NovAtel OEM6/7 ones. But it turns out that UM98x can also be configured to generate packet formats and logs that are exactly as defined in the NovAtel documentation. These packet formats and logs are also implemented by a couple of other recent Chinese GNSS receivers (Bynav M20 and SinoGNSS K901), so I also implemented support for these.</p>

<h2 id="gps-message-files">GPS message files</h2>

<p>The approach to configuration in 0.1 has some fundamental limitations:</p>

<ul>
  <li>it’s a lot of work to implement configuration support</li>
  <li>if this work has not been done for a receiver, then SatPulse provides no help at all in configuration</li>
  <li>if you want to configure a receiver feature that SatPulse does not support, then again SatPulse provides no help</li>
</ul>

<p>Most vendors provide a proprietary, but free-to-use Windows GUI app that can be used to configure their receivers.
For example, u-blox provides u-center.
In practice, for many configuration tasks with SatPulse 0.1, users would have to rely on these Windows apps.
For users living on Linux or macOS, this is not a good situation.</p>

<p>The multi-protocol support is a nice addition but does not address these limitations.</p>

<p>In the last week, I have implemented an alternative, more pragmatic approach to configuration that tries to address these limitations.
The approach is a simple one: have a file that defines messages that can be sent to the GPS receiver.
There is a new <code class="language-plaintext highlighter-rouge">-m</code>/<code class="language-plaintext highlighter-rouge">--msg-file</code> option for <code class="language-plaintext highlighter-rouge">satpulsetool gps</code> that specifies the message definition file.
This does not use the configuration abstraction at all and allows arbitrary messages to be sent to the receiver.
Since the main SatPulse configuration file is TOML, I also chose TOML for the format of this definition file.</p>

<p>Here’s a simple example. The Unicore UM980 supports Galileo HAS, but SatPulse configuration abstraction does not yet have anything for this.
To enable Galileo HAS, create a file <code class="language-plaintext highlighter-rouge">um980-has.toml</code>.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[[line]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"CONFIG PPP ENABLE E6-HAS"</span>
<span class="nn">[[line]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"CONFIG PPP CONVERGE 10 20"</span>
</code></pre></div></div>

<p>Each <code class="language-plaintext highlighter-rouge">[[line]]</code> block defines a text command. Run it with:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>satpulsetool gps -d /dev/ttyUSB0 -s 115200 -m um980-has.toml
</code></pre></div></div>

<p>This will send each line, terminated with CR/LF by default.
When used in this simple way, the main problem that this solves is enabling the user to see how the GPS receiver responded to the message.
Since SatPulse knows about packet formats, it can intelligently identify which packets might be responses to the message and display only those.
If you use <code class="language-plaintext highlighter-rouge">cat</code> and <code class="language-plaintext highlighter-rouge">stty</code>, you have no idea how the receiver responded.
If you try to use a terminal emulator, the periodic messages being continually output by the receiver (which may include binary) makes it difficult to see the responses.
There is also a <code class="language-plaintext highlighter-rouge">--packet-log</code> option that allows you to capture all packets sent and received.</p>

<h3 id="nmea-message-type">NMEA message type</h3>

<p>The <code class="language-plaintext highlighter-rouge">nmea</code> message type is similar to <code class="language-plaintext highlighter-rouge">line</code>, but it knows about NMEA checksums.
For example, this is how to configure PPS on a Quectel LG290P.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[[nmea]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"PQTMCFGPPS,W,1,1,100,2,1,0"</span>
</code></pre></div></div>

<p>The tool prepends <code class="language-plaintext highlighter-rouge">$</code> if missing and appends the checksum automatically.</p>

<h3 id="message-libraries">Message libraries</h3>

<p>The message file can also be used to create a library of messages.
Each message can be given a tag and a description.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[[nmea]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"PQTMCFGMSGRATE,W,RMC,1"</span>
<span class="py">tag</span> <span class="p">=</span> <span class="s">"nmea-daemon"</span>
<span class="py">description</span> <span class="p">=</span> <span class="s">"Enable NMEA messages understood by satpulse daemon"</span>
<span class="nn">[[nmea]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"PQTMCFGMSGRATE,W,GGA,1"</span>
<span class="py">tag</span> <span class="p">=</span> <span class="s">"nmea-daemon"</span>
<span class="nn">[[nmea]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"PQTMCFGMSGRATE,W,GSA,1"</span>
<span class="py">tag</span> <span class="p">=</span> <span class="s">"nmea-daemon"</span>
<span class="nn">[[nmea]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"PQTMCFGMSGRATE,W,GSV,1"</span>
<span class="py">tag</span> <span class="p">=</span> <span class="s">"nmea-daemon"</span>
<span class="nn">[[nmea]]</span>
<span class="py">text</span> <span class="p">=</span> <span class="s">"PQTMSAVEPAR"</span>
<span class="py">tag</span> <span class="p">=</span> <span class="s">"save"</span>
<span class="py">description</span> <span class="p">=</span> <span class="s">"Save configuration to NVM"</span>
</code></pre></div></div>

<p>Run specific tags with <code class="language-plaintext highlighter-rouge">-t</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>satpulsetool gps -d /dev/ttyUSB0 -s 460800 -m quectel.toml -t nmea-daemon,save
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">--show-tags</code> flag lists available tags with descriptions.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>satpulsetool gps -m quectel.toml --show-tags
</code></pre></div></div>

<h3 id="binary-messages">Binary messages</h3>

<p>Sending binary messages is a less straightforward.</p>

<p>You can specify the exact bytes to send. For example, with u-blox L5 receivers,
there is a special command you need to send get GPS L5 signal to work.
The u-blox docs give it as a hex string.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[[binary]]</span>
<span class="py">hex</span> <span class="p">=</span> <span class="s">"B562068A0900000100000100321001DEED"</span>
<span class="py">tag</span> <span class="p">=</span> <span class="s">"gps-l5-health"</span>
<span class="py">description</span> <span class="p">=</span> <span class="s">"Use GPS L5 signal regardless of health status"</span>
</code></pre></div></div>

<p>But usually it is not convenient to specify the full byte sequence directly.</p>

<p>When SatPulse has support for a binary packet format, you can use this to specify things in a more readable way.
For example, let’s take the CASIC binary protocol, which is similar to UBX.
SatPulse has support for the packet format layer, but does not have configuration support.
There’s a CFG-TP message for controlling the time pulse.
The CASIC specification describes it like this.</p>

<p><strong>CFG-TP (0x06 0x03)</strong></p>

<p><strong>Payload Content</strong></p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Offset</th>
      <th style="text-align: left">Type</th>
      <th style="text-align: left">Name</th>
      <th style="text-align: left">Unit</th>
      <th style="text-align: left">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">0</td>
      <td style="text-align: left">U4</td>
      <td style="text-align: left">interval</td>
      <td style="text-align: left">us</td>
      <td style="text-align: left">Pulse Interval</td>
    </tr>
    <tr>
      <td style="text-align: left">4</td>
      <td style="text-align: left">U4</td>
      <td style="text-align: left">width</td>
      <td style="text-align: left">us</td>
      <td style="text-align: left">Pulse Width</td>
    </tr>
    <tr>
      <td style="text-align: left">8</td>
      <td style="text-align: left">U1</td>
      <td style="text-align: left">enable</td>
      <td style="text-align: left"> </td>
      <td style="text-align: left">Enable Flag (0=Off, 1=On, 2=Auto Maintain, 3=Fix Only)</td>
    </tr>
    <tr>
      <td style="text-align: left">9</td>
      <td style="text-align: left">I1</td>
      <td style="text-align: left">polar</td>
      <td style="text-align: left"> </td>
      <td style="text-align: left">Polarity (0=Rising, 1=Falling)</td>
    </tr>
    <tr>
      <td style="text-align: left">10</td>
      <td style="text-align: left">U1</td>
      <td style="text-align: left">timeRef</td>
      <td style="text-align: left"> </td>
      <td style="text-align: left">0=UTC, 1=Satellite Time</td>
    </tr>
    <tr>
      <td style="text-align: left">11</td>
      <td style="text-align: left">U1</td>
      <td style="text-align: left">timeSource</td>
      <td style="text-align: left"> </td>
      <td style="text-align: left">0=GPS, 1=BDS, 2=GLN, 4=BDS(Main), 5=GPS(Main), 6=GLN(Main)</td>
    </tr>
    <tr>
      <td style="text-align: left">12</td>
      <td style="text-align: left">R4</td>
      <td style="text-align: left">userDelay</td>
      <td style="text-align: left">s</td>
      <td style="text-align: left">User Delay</td>
    </tr>
  </tbody>
</table>

<p>We can use this to define a message as follows:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[[casbin]]</span>
<span class="py">tag</span> <span class="p">=</span> <span class="s">"pps-gps"</span>
<span class="py">description</span> <span class="p">=</span> <span class="s">"Enable PPS aligned to GPS time"</span>
<span class="py">class</span> <span class="p">=</span> <span class="mh">0x06</span>
<span class="py">id</span> <span class="p">=</span> <span class="mh">0x03</span>
<span class="py">payload.types</span> <span class="p">=</span> <span class="s">"U4U4U1I1U1U1R4"</span>
<span class="py">payload.values</span> <span class="p">=</span> <span class="p">[</span><span class="mi">1000000</span><span class="p">,</span> <span class="mi">100000</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">]</span>
</code></pre></div></div>

<p>Each type descriptor in the <code class="language-plaintext highlighter-rouge">payload.types</code> string specifies how to encode corresponding entry in <code class="language-plaintext highlighter-rouge">payload.values</code>.
SatPulse doesn’t know about the CFG-TP message but it does know how CASIC binary packets work, and it can use this
to produce the right packet from this higher-level description (the packet has two sync bytes, the payload length, the class, the message id, the payload with values in little-endian byte-order and then a checksum).</p>

<h3 id="using-ai-to-create-message-libraries">Using AI to create message libraries</h3>

<p>I have had good success using AI to create message libraries. The workflow is:</p>

<ol>
  <li>convert the protocol spec from PDF into a more AI-friendly format such as Markdown</li>
  <li>prompt a coding agent (e.g., Claude Code) to generate a message library, giving it:
    <ul>
      <li>the protocol spec</li>
      <li>description of the message file format</li>
      <li>a few examples of a message library</li>
    </ul>
  </li>
  <li>allow the agent to use satpulsetool to access the GPS receiver:
    <ul>
      <li>try a message in the message library and see whether the receiver ACKs it</li>
      <li>capture output from the receiver before and after to see whether the configuration message has had the expected effect on the output (use <code class="language-plaintext highlighter-rouge">--capture N</code> with <code class="language-plaintext highlighter-rouge">--packet-log</code> to capture packets for N seconds)</li>
    </ul>
  </li>
</ol>

<h3 id="examples">Examples</h3>

<p>The <a href="https://github.com/jclark/satpulse/tree/master/configs/gpsmsg">configs/gpsmsg</a> directory in the repository has some example message libraries.</p>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[In this post, I want to describe some recent improvements in how SatPulse supports GPS configuration.]]></summary></entry><entry><title type="html">SatPulse 0.1 released and onwards to 0.2</title><link href="https://satpulse.net/2026/01/24/satpulse-0.1.html" rel="alternate" type="text/html" title="SatPulse 0.1 released and onwards to 0.2" /><published>2026-01-24T00:00:00+00:00</published><updated>2026-01-24T00:00:00+00:00</updated><id>https://satpulse.net/2026/01/24/satpulse-0.1</id><content type="html" xml:base="https://satpulse.net/2026/01/24/satpulse-0.1.html"><![CDATA[<p>I released version 0.1 of SatPulse today. This is the first stable release of SatPulse. The initial commit was back in December 2022, over 3 years ago.</p>

<p>For the last six months or so, I have been working on some major changes that are potentially destabilising. So I wanted to push out 0.1 based on the code before merging in these changes. I have now merged these changes into the master branch. Before I did the merge I was curious how much had changed.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git diff --stat master..v0.2-pre | tail -1
215 files changed, 41769 insertions(+), 5171 deletions(-)
</code></pre></div></div>

<p>About a third of this is test data, but that’s a pile of code. There are two main changes.</p>

<p>The first change is a complete rewrite of the core functionality of SatPulse: synchronizing the PTP hardware clock (PHC) to GPS time using timestamps and messages from the GPS. Although the current code works, it is a mess and not a good foundation for future improvements. I had been struggling for quite a while to come up with a better approach, but I now have something that feels much cleaner. The most important aspect of this is that there is now a simulator that allows realistic testing to be done without needing GPS or PHC hardware. The simulator has taken a lot of time, but I think it’s an investment that will pay off in the long-term.</p>

<p>The second change is to make the GPS support truly multi-protocol. Version 0.1 supports the u-blox UBX protocol only. Although the code was designed to be protocol-independent, there is a big difference between designing it to be protocol independent and it’s actually working well for other protocols in practice. What makes this hard is that my goal is to provide not only a protocol-independent representation of the relevant information transmitted by a GPS receiver, but also a protocol-independent model for querying and changing the configuration of a GPS receiver. The code on master now supports the Unicore UM980, which uses a protocol based on the protocol used by NovAtel OEM series of GPS receivers. One of the reasons I chose to work on this is that the protocol is both rich and completely different from UBX.</p>

<p>I plan to go into more detail about these changes in future posts.</p>

<p>These changes are working but there is lots of polishing, tweaking and testing still to do before a 0.2 release.</p>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[I released version 0.1 of SatPulse today. This is the first stable release of SatPulse. The initial commit was back in December 2022, over 3 years ago.]]></summary></entry><entry><title type="html">Using the tinyGTC with PTP hardware clocks</title><link href="https://satpulse.net/2026/01/13/tinygtc.html" rel="alternate" type="text/html" title="Using the tinyGTC with PTP hardware clocks" /><published>2026-01-13T00:00:00+00:00</published><updated>2026-01-13T00:00:00+00:00</updated><id>https://satpulse.net/2026/01/13/tinygtc</id><content type="html" xml:base="https://satpulse.net/2026/01/13/tinygtc.html"><![CDATA[<p>The <a href="https://www.tinydevices.org/wiki/pmwiki.php?n=TinyGTC.Homepage">tinyGTC</a> is a delightful little device released towards the end of 2025. It turns out that the tinyGTC is very useful for working with precision network timing. This post is aimed at people who have a tinyGTC and are interested in precision timing, but may not be familiar with what is possible as regards synchronizing computer clocks.</p>

<p>Typical NTP stratum-1 setups connect the PPS output of a GPS to a serial port or GPIO pin, which limits accuracy to the microsecond range. The tinyGTC and GPS generally is capable of much greater accuracy, but taking advantage of this requires a different way of connecting the PPS output to a computer, which takes advantage of hardware designed for use with PTP (Precision Time Protocol). Specifically, it requires an ethernet controller with a PTP hardware clock (PHC) that has pins, sometimes called Software Defined Pins (SDPs), that can be used for PPS input and PPS output. This allows synchronization with an accuracy in the tens of nanoseconds.  Although PHCs are designed primarily for use with PTP, the <a href="https://chrony-project.org/">chrony</a> implementation of NTP allows PHCs to be used with NTP to achieve similar accuracy.</p>

<p>A tinyGTC connected to a PHC can be used in two different ways:</p>
<ul>
  <li>connecting a PHC to the PPS output of a tinyGTC allows the tinyGTC to provide time to a PTP server (often called a grandmaster)</li>
  <li>connecting a PHC to the PPS input of a tinyGTC allows the tinyGTC to measure how accurately a PTP client is synchronized</li>
</ul>

<p>There are two suitable inexpensive ethernet controllers: the Intel i210-T1 and the one on-board the Raspberry Pi CM4/CM5. (The one on-board the Raspberry Pi 5 is not suitable.) The Intel i210-T1 can be used in a PC; the Raspberry Pi CM4/CM5 require a separate IO board. In both cases, this works only with Linux.</p>

<p>Fortuitously, the 3.3V PPS signal level used by the tinyGTC is exactly what the SDPs on these ethernet controllers expect. To connect them up, you need a special cable which has an SMA connector on one end and a pair of Dupont female leads on the other. You can buy these for a few dollars on eBay or AliExpress: search for “SMA Dupont test cable”. The SMA end can be male for direct connection to the tinyGTC, but for a more robust setup it’s better for the SMA end to be a female bulkhead connector: this bulkhead connector attaches to a hole in the case or a wifi bracket, then the SMA male-male cable that comes with the tinyGTC is used to connect from the bulkhead connector to the tinyGTC.</p>

<p>My <a href="https://satpulse.net">SatPulse</a> project provides software and documentation to make it easy to take advantage of the capabilities of PHCs for precision network timing. 
It includes detailed guides on hardware setup both with the <a href="https://satpulse.net/hardware/cm-build.html">CM4/CM5</a> and with the <a href="https://satpulse.net/hardware/intel-build.html">i210</a>.</p>

<p>TinyGTC is still relatively new, and I did find a few issues, but the developer is very responsive to bug reports, so I am hopeful everything will be resolved.
The testing described here was done with firmware version 1.023.</p>

<h2 id="providing-time-to-a-ptp-server">Providing time to a PTP server</h2>

<p>For providing time to a PTP server, the tinyGTC needs to be set up as follows:</p>

<ul>
  <li>GPS disciplining the internal oscillator</li>
  <li>Enable <em>aligned</em> PPS output on the OUT connector (this needs tinyGTC firmware at least 1.023)</li>
  <li>GPS through mode (so NMEA messages from the GPS are available over USB)</li>
</ul>

<p>Two physical connections are needed:</p>

<ul>
  <li>an SMA cable connecting from the OUT on the tinyGTC to the SMA-Dupont cable which is connected to the SDPs of the PHC</li>
  <li>a USB cable connecting from the tinyGTC to a USB port on the host computer</li>
</ul>

<p>The SatPulse <a href="https://satpulse.net/setup/">setup guide</a> explains how to set up the software side of things.
When editing the satpulse.toml configuration file, you will need to specify the serial speed and the serial device.
A tinyGTC exposes three USB serial devices, usually <code class="language-plaintext highlighter-rouge">/dev/ttyACM0</code>, <code class="language-plaintext highlighter-rouge">/dev/ttyACM1</code> and <code class="language-plaintext highlighter-rouge">/dev/ttyACM2</code>.
In my tests, the one that passes through the GPS output is <code class="language-plaintext highlighter-rouge">/dev/ttyACM1</code>.
You need the one with interface number 02, i.e. <code class="language-plaintext highlighter-rouge">cat /sys/class/tty/ttyACM1/device/bInterfaceNumber</code> should show <code class="language-plaintext highlighter-rouge">02</code>.
You can check if you have the right one by using <code class="language-plaintext highlighter-rouge">satpulsetool gps -d /dev/ttyACM1 -s 115200</code>;
it should say <code class="language-plaintext highlighter-rouge">Packet formats detected: NMEA</code>.</p>

<p>So how does a tinyGTC compare to using a GPS module directly? On the positive side, being a GPSDO rather than just a GPS means that much of the short-term jitter is smoothed out, and also gives some holdover capability. There are very few GPSDOs available that tick the same boxes as tinyGTC: relatively inexpensive, 3.3V PPS, aligned PPS, and access to GPS output (the BG7TBL CM55 is the only other one I know of).</p>

<p>On the negative side, the tinyGTC’s GPS module is single-band (L1 only); this limits its ability to compensate for ionospheric effects, which can introduce phase inaccuracies of tens of nanoseconds over a 24 hour cycle. Also SatPulse does not (yet) have support for the CASIC protocol used by the tinyGTC’s GPS module, so you cannot use <code class="language-plaintext highlighter-rouge">satpulsetool gps</code> to configure it.</p>

<h2 id="measuring-ptp-synchronization-accuracy">Measuring PTP synchronization accuracy</h2>

<p>The main point of the tinyGTC is to be a time counter, and this can be used with PHCs to measure synchronization accuracy. There is only one other inexpensive device that I know of that provides this capability, which is the <a href="https://tapr.org/product/tapr-ticc/">TAPR TICC</a>, but that needs a separate 10Mhz reference.</p>

<p>A PHC without PPS input/output SDPs is sufficient for a client to be able to synchronize to a PTP server with high accuracy. Such PHCs are very common: for example, the Raspberry Pi 5 has such a PHC. But if you want to be able to measure the synchronization accuracy, then you need a PHC with an SDP with PPS input/output capability, just as is needed for a PTP server, such as a Raspberry Pi CM4/CM5 or Intel i210.</p>

<p>In order to do measurements, the tinyGTC needs to be set up as follows:</p>

<ul>
  <li>GPS disciplining the internal oscillator (same as when providing time to the PTP server)</li>
  <li>make Counter A DC-coupled with a trigger level of 2V</li>
  <li>measure Channel A against the internal oscillator</li>
</ul>

<p>You also need to physically connect the SDP on the PHC of the PTP client to the A connector on the tinyGTC (in the same way as was described for the OUT connector above).</p>

<p>On the PTP client you need to do two things:</p>

<ul>
  <li>use <a href="https://satpulse.net/setup/ptp4l.html">ptp4l</a> to synchronize the client to the server</li>
  <li>configure the PHC to output a PPS signal; you can use <a href="https://satpulse.net/man/satpulsetool-sdp.1.html">satpulsetool sdp</a> to do this; on a Raspberry Pi CM4/CM5, the command would be simply <code class="language-plaintext highlighter-rouge">satpulsetool sdp -o eth0</code></li>
</ul>

<p>You can do this at the same time as using tinyGTC to provide time to the server. (So you would need two CM4/CM5s or other suitable PHCs and two SMA-Dupont cables.) In this configuration, the same time is being provided to the PTP server and to the reference against which the PTP client is being measured, so you are mostly measuring how well PTP is working. (It will work much better with a direct back-to-back connection than going through a non-PTP aware switch.)</p>

<p>You can also measure the client against a PTP server with an independent source of time. In this case, you would be measuring how accurately your entire PTP setup is synchronizing a client to UTC.</p>

<p>It is also possible to compare the relative synchronization of two clients by connecting one to input A on the tinyGTC and one to input B. For some applications what is important is not the absolute accuracy of the synchronization to UTC but the synchronization of clients relative to each other. You can set things up to have lines showing A against the GPSDO, B against the GPSDO and A against B.</p>

<h2 id="detailed-tinygtc-setup-instructions">Detailed tinyGTC setup instructions</h2>

<p>Enable GPS disciplining of internal oscillator</p>

<ol>
  <li>Tap REFERENCE</li>
  <li>Tap REFERENCE</li>
  <li>Select INT GPS</li>
  <li>Tap BACK</li>
</ol>

<p>Enable PPS output on OUT connector aligned to disciplined oscillator</p>

<ol>
  <li>Tap OUTPUT</li>
  <li>Tap ALIGNED PPS</li>
  <li>Wait for it to say “Touch screen to continue”</li>
  <li>Tap screen</li>
</ol>

<p>Enable GPS Through Mode</p>

<ol>
  <li>Tap CONFIG</li>
  <li>Enable ADVANCED MENUS</li>
  <li>Tap BACK</li>
  <li>Tap REFERENCE</li>
  <li>Tap SETTINGS</li>
  <li>Tap GPS THROUGH</li>
</ol>

<p>Make Counter A DC-coupled with trigger level of 2V</p>

<ol>
  <li>Tap COUNTER</li>
  <li>Tap SOURCE</li>
  <li>Select A</li>
  <li>Select TRIG LVL</li>
  <li>Tap 2</li>
  <li>Tap ENT</li>
</ol>

<p>Measure Channel A against disciplined oscillator</p>

<ol>
  <li>Tap MEASUREMENT</li>
  <li>Tap MEASURE</li>
  <li>Tap PHASE</li>
  <li>Select PHASE</li>
  <li>Tap AGAINST NCO</li>
  <li>Select NCO FREQ</li>
  <li>Tap 1</li>
  <li>Tap x1</li>
</ol>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[The tinyGTC is a delightful little device released towards the end of 2025. It turns out that the tinyGTC is very useful for working with precision network timing. This post is aimed at people who have a tinyGTC and are interested in precision timing, but may not be familiar with what is possible as regards synchronizing computer clocks.]]></summary></entry><entry><title type="html">Time server architecture</title><link href="https://satpulse.net/2025/05/21/time-server-architecture.html" rel="alternate" type="text/html" title="Time server architecture" /><published>2025-05-21T00:00:00+00:00</published><updated>2025-05-21T00:00:00+00:00</updated><id>https://satpulse.net/2025/05/21/time-server-architecture</id><content type="html" xml:base="https://satpulse.net/2025/05/21/time-server-architecture.html"><![CDATA[<p>It can be hard to understand how everything fits together with a PTP/NTP time server. This post explains how things work when using SatPulse.</p>

<h2 id="kernel-devices">Kernel devices</h2>

<p>There are four key kernel devices involved.</p>

<ul>
  <li>System clock. There is an underlying raw hardware counter. The kernel exposes three kinds of clock based on this.
    <ul>
      <li>the raw monotonic clock corresponds to this hardware counter, and is not adjusted for phase or frequency</li>
      <li>the monotonic clock is adjusted by NTP for frequency, so that it can accurately measure time intervals</li>
      <li>the realtime clock is adjusted by NTP for both frequency and phase, so that it corresponds closely to UTC (this implies it can jump)</li>
    </ul>
  </li>
  <li>
    <p>PTP Hardware Clock (PHC). This is a hardware clock within the ethernet controller. It is important to understand that this is completely independent of the system clock. It has a software-defined pin (SDP) connected to the GPS receiver’s PPS output. It has the capability to timestamp a pulse received on the SDP, i.e. record the time of the PHC at which the pulse occurred. The PHC can be adjusted for phase and frequency.</p>
  </li>
  <li>
    <p>Network interface. This is provided by an ethernet controller and is responsible for sending and receiving network packets with PTP or NTP messages. Crucially, it has an associated PHC and can timestamp ingoing and outgoing packets using the PHC i.e. the hardware records the time of the PHC at which each packet is sent or received.</p>
  </li>
  <li>Serial port. This is connected to the serial or USB interface of the GPS receiver. The kernel TTY subsystem exposes this as a /dev/tty<em>X</em> device.</li>
</ul>

<h2 id="processes">Processes</h2>

<p>Apart from satpulsed, which is the main daemon program in SatPulse, there are two other processes involved.</p>

<ul>
  <li>
    <p>ptp4l, which is part of LinuxPTP, implements the PTP standard. The PTP standard focuses on transferring time over the network from the PHC of one device to the PHC of another device. It does not concern itself with the system clock nor does it specify a mechanism for transferring time from an external source such as a GPS receiver into a PHC. When used in a time server with satpulsed, it is satpulsed rather than ptp4l that adjusts the PHC. Ptp4l interacts with the PHC primarily via hardware timestamping: when it sends and receives packets containing PTP messages it instructs the network interface to record the time of the PHC at which the packets were sent or received, and to provide it with those times.</p>
  </li>
  <li>
    <p>chronyd, which is part of chrony, implements the NTP standard. Whereas PTP is focused on the PHC, NTP is focused on the system clock. Chrony accepts information from NTP servers and from reference clocks and uses this to update the system clock and to provide information to NTP clients.</p>
  </li>
</ul>

<p>The fundamental role of satpulsed is to transfer time from the GPS receiver to the PHC. This involves the following.</p>

<ul>
  <li>Reading timestamp events from the PHC. The PHC hardware can record the time of the PHC at which a time pulse occurs on a PHC SDP. The GPS emits time pulses that are precisely aligned with the beginning of a second. So if the timestamp says the pulse is 45 nanoseconds after the beginning of the second, this indicates that the PHC differs from the true time by 45 nanoseconds plus some integral number of seconds.</li>
  <li>Reading messages from the GPS receiver over the serial port. These messages give the approximate current time. By correlating these messages with the timestamp events, it becomes possible to determine which second the pulse is marking the beginning of, and thus calculate exactly how much the PHC differs from the true time.</li>
  <li>Adjusting the PHC. From the timestamp events and GPS messages, satpulsed derives a measurement once per second indicating how much the PHC differs from the true time. When the process starts up, this information is used to set the PHC time. After that, it continually adjusts the frequency of the PHC to keep the difference between the PHC and true time as small as possible. Note that setting the PHC time is relatively imprecise, because this involves separate operations to read and write the PHC time. To get the PHC time precisely right, frequency adjustment is necessary.</li>
</ul>

<p>The PTP standard requires that when time is transferred in from an external source, certain metadata relating to the time is also transferred in. With ptp4l, this metadata is provided using a specific PTP management message defined by ptp4l (called GRANDMASTER_SETTINGS_NP, NP for non-portable). To support this, satpulsed implements a PTP management client and uses it to send GRANDMASTER_SETTINGS_NP messages to ptp4l over a socket. The kinds of metadata that PTP requires can be divided into two.</p>

<p>The first kind of metadata relates to the offset between TAI time and UTC time. The time of the PHC uses the PTP timescale, which is based on TAI. TAI differs from UTC time by an integral number of seconds, which changes when leap seconds occur. It turns out that all GNSS systems (other than GLONASS) work in a similar way: their clocks use a timescale based on TAI, but they also provide metadata about the offset between TAI and UTC time. Usually GPS receivers hide all this complexity and output messages with UTC time, which is suitable for NTP. But for PTP, the GPS receiver should output messages with TAI time (or rather GNSS time, which is a fixed number of seconds from TAI time) and also messages about leap seconds. During start-up, satpulsed sends configuration messages to the GPS receiver to make it do this, and then it uses these messages to provide the appropriate metadata to ptp4l.</p>

<p>The second kind of metadata relates to clock quality, which indicates whether the PHC is synchronized to GNSS at all, and if it is, how accurately it is synchronized. A PTP server broadcasts its clock quality; clients use this information to choose the best available PTP server. The synchronization state is continuously monitored by satpulsed. If there’s a problem with the GPS receiver that prevents satpulsed from keeping the PHC synchronized with the GPS receiver time, then satpulsed notifies ptp4l, so that ptp4l can update the clock quality metadata it sends to clients. This allows clients to switch over promptly to a better-synchronized PTP server or to be aware that they are no longer properly synchronized by PTP.</p>

<p>When used with chrony, satpulsed in addition takes readings of the PHC and the realtime clock that are as simultaneous as the hardware allows. When the PHC is in sync with the GPS receiver time, then these readings can be used to generate a sample saying how much the realtime clock is off from the true time; satpulsed will send these samples to chrony over a socket using the refclock SOCK protocol. Chrony can use this as a reference clock.</p>

<h2 id="comparison-to-classic-ntp-server">Comparison to classic NTP server</h2>

<p>This architecture differs from a classic NTP server, using an NTP server such as ntpd or chrony together with gpsd. Apart from the addition of a PTP implementation, the key difference is in the presence of a PHC. In a classic NTP architecture, the GPS receiver is attached to a seria port pin or GPIO pin. When a GPS receiver emits a pulse, an interrupt will occur and the kernel will record the time of the pulse. The kernel makes these timestamps available to applications via a PPS kernel device, which will be read by either gpsd or the NTP server.</p>

<p>The crucial difference is that with a PPS signal connected to a PHC, the hardware records the time of the PHC at which each pulse occurs without the intervention of the kernel. This enables the timestamps to be accurate to a few nanoseconds, whereas with a PPS device the accuracy is several orders of magnitude worse. The PHC also enables hardware timestamping of network packets, which enables similarly accurate measurement of the offset between the PHC on the server and the PHC on the client.</p>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[It can be hard to understand how everything fits together with a PTP/NTP time server. This post explains how things work when using SatPulse.]]></summary></entry><entry><title type="html">Fitting a GPS in a Raspberry Pi Compute Module 5 IO Case</title><link href="https://satpulse.net/2025/03/24/fitting-a-gps-in-a-raspberry-pi-compute-module-5-io-case.html" rel="alternate" type="text/html" title="Fitting a GPS in a Raspberry Pi Compute Module 5 IO Case" /><published>2025-03-24T00:00:00+00:00</published><updated>2025-03-24T00:00:00+00:00</updated><id>https://satpulse.net/2025/03/24/fitting-a-gps-in-a-raspberry-pi-compute-module-5-io-case</id><content type="html" xml:base="https://satpulse.net/2025/03/24/fitting-a-gps-in-a-raspberry-pi-compute-module-5-io-case.html"><![CDATA[<figure class="half ">
  
    
      <img src="/assets/images/cm5-sr1723-parts.jpg" alt="Parts needed" />
    
  
    
      <img src="/assets/images/cm5-sr1723-assembled.jpg" alt="CM5 assembled" />
    
  
  
</figure>

<p>Here is an example of how you can fit a GPS to the Raspberry Pi CM5 IO board in the official IO case, so that it can be used with SatPulse.</p>

<p>I chose a GPS in the form factor that I have found easiest to fit inside CM4/CM5 cases, which:</p>

<ul>
  <li>is really small, almost thumbnail-sized; it is 17mmx23mm excluding the connectors and 17mmx40mm including the connectors;</li>
  <li>has a single hole that can be used to secure the board; it is small and light enough that a single hole is enough to securely support the board;</li>
  <li>has both U.FL (IPEX) and SMA connectors; the U.FL connector works best inside the case, but the SMA connector is useful for testing outside the case;</li>
  <li>has 5 Dupont pins for connecting power, serial port, and PPS; these are the same kind of pins that are used on the IO board, so is the easiest to work with;</li>
  <li>includes a tiny battery;</li>
  <li>has both power and PPS LEDs.</li>
</ul>

<p>There are quite a number of GPSs on the market in this form factor. The board I am using here is the Star River SR1723U10. I got it from <a href="https://xinghewei.aliexpress.com/">Star River’s official shop on AliExpress</a>. (The shop is called Xing He Wei; Xing He means star river, which is a poetic name for the Milky Way in Chinese.) The SR1723 range has GPS boards in the same factor with a variety of different modules. This one uses the Star River <a href="http://sragps.com/down/SR1612U10%E8%A7%84%E6%A0%BC%E4%B9%A6.pdf">SR1612U10</a> module, which uses the u-blox UBX-M10050-KB chip. So from a software perspective, it behaves like a u-blox 10th generation standard precision module. It costs about $7.</p>

<p>You will of course need an antenna; this module is single-band L1, so a basic GPS puck antenna with a cable ending in a SMA male connector is sufficient.</p>

<p>On the left we have the case assembled but without the GPS. The parts for this are:</p>

<ul>
  <li><a href="https://www.raspberrypi.com/products/compute-module-5/">Raspberry Pi Compute Module 5</a></li>
  <li><a href="https://www.raspberrypi.com/products/compute-module-5-io-board/">Raspberry Pi Compute Module 5 IO board</a></li>
  <li><a href="https://www.raspberrypi.com/products/io-case-cm5/">Raspberry Pi Compute Module 5 IO Case</a></li>
  <li><a href="https://www.raspberrypi.com/products/cooler/">Raspberry Pi Cooler for Compute Module 5</a></li>
  <li>CR2032 coin cell battery for the RTC</li>
</ul>

<p>You will also need a USB PD power supply, which I haven’t shown. Note that 5V3A is plenty for this configuration, so you can use any old USB PD power supply you have handy; there is no need to buy the special Raspberry Pi 27W supply which supports 5V5A.</p>

<p>To fit the GPS to the IO board, I used:</p>
<ul>
  <li>a 15cm 5-pin Dupont female-female ribbon cable; this came with the GPS board; if you don’t have this, then you can just use 5 separate jumpers</li>
  <li>a 10cm U.FL to SMA female bulkhead pigtail (note that it is SMA female without a pin, rather than RP-SMA female with pin; RP-SMA is used for wifi antennas)</li>
  <li>a male-female 10mm M2.5 nylon standoff and screw</li>
</ul>

<p>The assembled result is on the right. The assembly process involves</p>
<ul>
  <li>removing the screw on the upper right that secures the board to the case, and replacing it by the standoff;</li>
  <li>using the hole in the case designed for a wifi antenna for the GPS antenna (rather surprisingly the wifi is still usable with the on-board wifi antenna)</li>
</ul>

<table>
  <thead>
    <tr>
      <th>GPS Pin</th>
      <th>Color</th>
      <th>Pi pin #</th>
      <th>Pi function</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>VCC</td>
      <td>green</td>
      <td>4</td>
      <td>5V</td>
    </tr>
    <tr>
      <td>GND</td>
      <td>yellow</td>
      <td>6</td>
      <td>GND</td>
    </tr>
    <tr>
      <td>TX</td>
      <td>orange</td>
      <td>10</td>
      <td>RXD0</td>
    </tr>
    <tr>
      <td>RX</td>
      <td>red</td>
      <td>8</td>
      <td>TXD0</td>
    </tr>
    <tr>
      <td>PPS</td>
      <td>brown</td>
      <td>J6 - 6</td>
      <td>SYNC_OUT</td>
    </tr>
  </tbody>
</table>

<p>It is important to note here that silkscreen on the IO board labels pin 9 as the SYNC_OUT (as it was on the CM4 IO board), but this is incorrect: the SYNC_OUT pin is actually pin 6 (top row, third from the left, on the J2 14-pin jumper set). The VCC pin on the GPS accepts between 3.3V and 5V, so it could alternatively be connected to a 3.3V pin.</p>

<p>The <a href="/setup/">setup guide</a> explains how to install and configure the necessary software. The initial speed of the serial port is 38400.</p>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Comparing GPS performance with SatPulse</title><link href="https://satpulse.net/2025/03/23/comparing-gps-performance.html" rel="alternate" type="text/html" title="Comparing GPS performance with SatPulse" /><published>2025-03-23T00:00:00+00:00</published><updated>2025-03-23T00:00:00+00:00</updated><id>https://satpulse.net/2025/03/23/comparing-gps-performance</id><content type="html" xml:base="https://satpulse.net/2025/03/23/comparing-gps-performance.html"><![CDATA[<p>I have been doing some more systematic testing on SatPulse. I have 6 different systems set up for testing. I ran SatPulse on them for 3 days, with SatPulse producing its “clock” log, which includes the offsets measured between the PTP hardware clock (PHC) and the pulses emitted every second by the GPS. SatPulse feeds this into a PI servo that continually adjusts the frequency of the PHC. These offsets give an objective indicator of the performance of the GPS and the PHC.</p>

<p>Here is a table of the results:</p>
<ul>
  <li>GPS is the GPS used (configuration detailed below)</li>
  <li>Mean is the mean of the absolute value of the offsets in nanoseconds</li>
  <li>95% is the 95th percentile, i.e. 95% of the absolute values of the offsets are within this</li>
  <li>Std dev is the standard deviation of the offsets</li>
  <li>Cost is the approximate cost in US$ without shipping or tax</li>
</ul>

<table>
  <thead>
    <tr>
      <th>GPS</th>
      <th>Mean</th>
      <th>Std dev</th>
      <th>95%</th>
      <th>Cost US$</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>M8F</td>
      <td>4.2</td>
      <td>5.2</td>
      <td>10</td>
      <td>300</td>
    </tr>
    <tr>
      <td>F9P</td>
      <td>6.2</td>
      <td>7.6</td>
      <td>14</td>
      <td>200</td>
    </tr>
    <tr>
      <td>F9T</td>
      <td>6.2</td>
      <td>7.8</td>
      <td>15</td>
      <td>240</td>
    </tr>
    <tr>
      <td>GPSDO</td>
      <td>9.0</td>
      <td>11.0</td>
      <td>21</td>
      <td>200</td>
    </tr>
    <tr>
      <td>M8T</td>
      <td>14.5</td>
      <td>17.9</td>
      <td>34</td>
      <td>30</td>
    </tr>
    <tr>
      <td>M10</td>
      <td>18.3</td>
      <td>28.7</td>
      <td>67</td>
      <td>7</td>
    </tr>
  </tbody>
</table>

<p>The systems are as follows:</p>

<ul>
  <li>F9T is u-blox ZED-F9T in a <a href="https://gnss.store/zed-f9t-timing-gnss-modules/108-elt0095.html">USB dongle from gnss.store</a> attached to Lenovo M720q Tiny PC (using Intel i3-9100T) with an Intel i210-T1 card, running Fedora 41</li>
  <li>F9P is a u-blox ZED-F9P in a <a href="https://www.ardusimple.com/product/simplertk2b-m-2/">simpleRTK2B M.2 card from Ardusimple</a>, in an Asus S500SD PC (using an Intel i5-12400) with an Intel i225-T1 card, running Debian Bookworm</li>
  <li>GPSDO is a BG7TBL GPSDO with an RS232 connection to a Raspberry Pi CM4 in a Waveshare PoE Board B, running Debian Bookworm</li>
  <li>M8F is a u-blox LEA-M8F in a <a href="https://www.timebeat.app/">Timebeat</a> sandwich board (which they don’t seem to sell any more), in a CM4 running Debian Bullseye</li>
  <li>M8T is a u-blox LEA-M8T in a Huawei board from ebay, in a CM4 running Fedora 41</li>
  <li>M10 is a <a href="http://sragps.com/">Star River</a> SR1612U10 module (using a u-blox UBX-M10050-KB chip) in a SR1723-U10 board, in a Raspberry Pi CM5 running Debian Bookworm</li>
</ul>

<p>SatPulse did its automatic configuration on all of these, apart from the GPSDO. The systems share two antennas (using GPS splitters). The antennas are both survey antennas, which can see about 50% of the sky.</p>

<p>Some thoughts:</p>
<ul>
  <li>The M8F is a bit different. It effectively is a mini-GPSDO. I was surprised to see it beat the F9P and F9T. I am also surprised that it is so much better than the BG7TBL GPSDO, which is physically much bigger.</li>
  <li>The dual-band high-precision models (F9P and F9T) do better than the single-band models, which is to be expected.</li>
  <li>The single-band timing model (M8T) does a bit better than the single-band standard precision model (M10), which is also to be expected.</li>
  <li>The M10 model is pretty decent for its price.</li>
  <li>The F9P, which is designed for RTK (precision positioning), and F9T, which is designed for precision timing, have essentially the same performance. I should mention that I flashed the F9P with older firmware that supports reporting the quantization error (sawtooth correction) in the UBX-TIM-TP message. I haven’t tested whether this makes a difference. The F9T supports a differential timing where multiple F9Ts can work together to provide better precision. But if you don’t need this, the F9P is the more cost-effective option. These are both dual-band. They have separate models which support L1+L2 and L1+L5.</li>
</ul>]]></content><author><name>James Clark</name></author><summary type="html"><![CDATA[I have been doing some more systematic testing on SatPulse. I have 6 different systems set up for testing. I ran SatPulse on them for 3 days, with SatPulse producing its “clock” log, which includes the offsets measured between the PTP hardware clock (PHC) and the pulses emitted every second by the GPS. SatPulse feeds this into a PI servo that continually adjusts the frequency of the PHC. These offsets give an objective indicator of the performance of the GPS and the PHC.]]></summary></entry></feed>