Skip to navigation

Time Synchronization with PPS

Discipline the system clock with GNSS time to get accurate timestamps
Open in ClaudeOpen in ChatGPT

Besides the position, most GNSS receivers can output a PPS (Pulse Per Second) signal: a hardware pulse emitted at the beginning of every UTC second. Used together with the time contained in the NMEA sentences, it lets the operating system align its clock to UTC within about a microsecond.

Why synchronize the clock?

The ZED SDK Fusion module matches camera data and GNSS data by timestamp. Camera images are timestamped with the system clock of the host, while GNSS measurements are usually timestamped with the GNSS time given by the receiver. If the system clock drifts away from UTC, the two timestamps no longer refer to the same instant, which degrades the fusion result or causes the data to be dropped. See Data Synchronization for more details on how the matching is performed.

The time contained in the NMEA sentences is affected by the latency and the jitter of the serial link, which limits its accuracy to a few milliseconds. The PPS edge, instead, is aligned to UTC with an accuracy in the order of tens of nanoseconds.

Disciplining the system clock with PPS is recommended when:

  • your device has no reliable network time source (NTP), for example on a robot operating outdoors without connectivity,
  • you fuse camera and GNSS data with the Global Localization module and need the best possible timestamp alignment,
  • you record datasets that must be replayed or compared with data coming from other machines,
  • you run a multi-camera setup where several devices must share a common time base.

The PPS signal is generated only when the GNSS receiver has acquired a valid fix. If no antenna is connected, or if the sky visibility is poor, no pulse is produced.

Hardware requirements

On Linux, the PPS signal is handled by the kernel PPS subsystem, which exposes it as a /dev/ppsN character device. The most common setup is:

  • a GNSS receiver with a PPS output pin,
  • a connection from this pin to a GPIO of the host, with compatible voltage levels,
  • a device tree node that binds this GPIO to the Linux pps-gpio driver, and a kernel built with CONFIG_PPS and CONFIG_PPS_CLIENT_GPIO.

The GPIO to use and the device tree configuration depend on your board. Consult your carrier board vendor to know which pins support this use and how to enable the pps-gpio driver.

On the ZED Box Orin with the GNSS option, the PPS signal of the GNSS module is already wired and enabled: /dev/pps0 is available out of the box. See Using GNSS with ZED Box Orin.

USB receivers usually do not transmit the PPS signal over USB. Some USB-to-serial adapters can carry it on the DCD line of the serial port, which gpsd can use, but with a lower accuracy than a GPIO because of the USB latency.

Checking the PPS device

Once the PPS input is configured, the kernel exposes the signal as a PPS character device:

ls /dev/pps*

You should get /dev/pps0, or another index if several PPS sources are declared.

A quick check that does not require any additional package is to read the timestamp of the last pulse:

cat /sys/class/pps/pps0/assert
1758012345.000000123#42

The value is the system time of the last rising edge, followed by the number of pulses received since boot. Reading it twice one second apart should show the counter increasing.

To monitor the pulses continuously, install the pps-tools package:

sudo apt install pps-tools

Then monitor the device:

sudo ppstest /dev/pps0

The output should look like this:

trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1758012345.000000123, sequence: 42 - clear 0.000000000, sequence: 0
source 0 - assert 1758012346.000000118, sequence: 43 - clear 0.000000000, sequence: 0

The assert value is the system time at which the kernel detected the rising edge of the pulse, and sequence increments once per second. A value close to a whole second means that the system clock is already well aligned to UTC.

Synchronizing the system clock

This section assumes that gpsd is installed and configured as described in Setting up GNSS / RTK on Linux.

Feeding the PPS signal to gpsd

gpsd can read the PPS device together with the serial port of the GNSS receiver. Add /dev/pps0 to the list of devices passed to gpsd:

sudo pkill gpsd # Kill gpsd if it is already running
sudo gpsd -nG -s 115200 /dev/ttyACM0 /dev/pps0

If you are also using RTK corrections through NTRIP, the PPS device is simply appended to the NTRIP command:

sudo gpsd -nG ntrip://<username>:<password>@<url>:<port>/<mountpoint> -s 115200 /dev/ttyACM0 /dev/pps0

Replace ttyACM0 with the communication port of your GNSS receiver, and pps0 with your PPS device.

You can check that gpsd is actually receiving the pulses with:

ntpshmmon

Samples labeled NTP0 come from the NMEA time, while the ones labeled NTP1 come from the PPS signal.

To make the configuration persistent, update the gpsd line of your cron job so that it also includes the PPS device (see the warning below).

Configuring Chrony

gpsd publishes these two time sources in shared memory, where an NTP daemon such as Chrony can read them to discipline the system clock. Install it with:

sudo apt install chrony

And add the two reference clocks to /etc/chrony/chrony.conf:

# Time from the NMEA sentences, coarse but always available
refclock SHM 0 refid NMEA offset 0.0 delay 0.2 poll 3
# Time from the PPS signal, high precision
refclock SHM 1 refid PPS precision 1e-7 poll 3 prefer

Restart the service and verify that the reference clocks are used:

sudo systemctl restart chrony
chronyc sources -v

The PPS source should be marked as the selected one (*) after a few tens of seconds.

Reading the PPS device and writing to the shared memory segments 0 and 1 require root privileges. That is why the commands above start gpsd with sudo. If you start it from a cron job, add the job to the root crontab with sudo crontab -e.

These two segments are readable by the root user only. If chronyd cannot read them on your system, configure gpsd and Chrony to use segments 2 and 3 instead, which are world-readable.

Once the system clock is disciplined by PPS, every timestamp generated on the device, including the ZED camera timestamps, shares the same UTC-aligned time base.