-
Notifications
You must be signed in to change notification settings - Fork 0
5. Configure LinuxPTP
LinuxPTP is a group of applications designed to implement the Precision Time Protocol (PTP) IEEE 1588 over a local network. It delivers sub-microsecond timing accuracy over Layer 2 or Layer 3 infrastructure. With adequate switch hardware, you can have syncronisation in the 10s of nanosecond range. The RPi 5 (and RPi 4) both feature the Broadcom BCM54213PE PTP enabled NICs, although only the CM5/4 breaks out the external timepulse pin.
There are a few ways that LinuxPTP can integrate into the timeserver. See the below diagrams for setup permutations. Options 2-4 are preferred.
The below configuration steps will create a topology that mirrors Option 2. If you progress to the Alternative options, you will end up with Option 3 or Option 4.
I am currently running Option 4.
This option is useful if you are running a RPi 5/4 or are not using the SYNC_OUT pin on your CM5/4. I will not cover this setup here as there are other examples online that use this.
flowchart TD
A[Uputronics HAT]-->|"/dev/ttyAMA0"|C["GPSD"]
C-->|"/run/chrony.ttyAMA0.sock"|E["Chrony Synchronisation"]
F["PPS"]-->|"/dev/pps0"|E
A-->F
E-->H["NTP Service"]
I["ptp4l"]-->J["eth0 Timestamp"]
K["phc2sys"]-->J
E-->K
J-->L["PTP Service"]
This option uses GPSD + Chrony to derive the current time, and uses the SYNC_OUT pin exposed on the CM5 board to timestamp the NIC in hardware rather than software.
flowchart TD
A[Uputronics HAT]-->|"/dev/ttyAMA0"|B["GPSD"]
B-->|"/run/chrony.ttyAMA0.sock"|C["Chrony Synchronisation"]
A-->D["PPS"]
D-->|"/dev/pps0"|C
D-->|"SYNC_OUT"|E["ts2phc"]
E-->F["eth0 Timestamp"]
G["ptp4l"]-->F
C-->H["NTP Service"]
C-->G
F-->I["PTP Service"]
This option uses GPSD + Chrony to derive the current time which is served over NTP. It uses ts2phc to read a copied version of the NMEA sentence and the SYNC_OUT pin exposed on the CM5 board to parallel the PPS sync. NIC time will be slightly different than system time although largely more stable as it relies on the NIC clock (also hardware based) rather than the system clock (kernel interrupt based).
flowchart TD
A[Uputronics HAT]-->|"/dev/ttyAMA0"|B["GPSD"]
B-->|"/run/chrony.ttyAMA0.sock"|C["Chrony Synchronisation"]
A-->D["PPS"]
D-->|"SYNC_OUT"|F["ts2phc"]
D-->|"/dev/pps0"|C
B-->E["gpspipe"]
E-->|"/run/gpsd.ttyAMA0.sock"|F
F-->G["eth0 Timestamp"]
H["ptp4l"]-->G
G-->I["phc2sys"]
I-.->|"/run/chrony.PTP0.sock"|C
C-->J["NTP Service"]
G-->K["PTP Service"]
This option uses ts2phc to supply both time and PPS data to Chrony. It only uses the PPS and GPSD SOCK as a sanity check. In my experience there is a 40us delay on the kernel time when using GPSD SOCK and PPS time compared to the NIC time. This option is simpler as Chrony serves the time found on the NIC. This has the advantage of avoiding kernel-level precision (18ns) errors, and keeps both NTP time and PTP time uniform.
flowchart TD
A[Uputronics HAT]-->B["GPSD"]
B-->|"gpspipe Socket"|C["ts2phc"]
B-->|"GPSD SOCK"|E["Chrony Synchronisation"]
C-->|"PHC, prefer"|E
A-->D["PPS"]
D-->|"SYNC_OUT"|C
D-->|"/dev/pps0"|E
E-->F["NTP Service"]
C-->G["PTP Service"]
H["PTP4l"]-->G
You may want to back up the default configuration here for future reference.
Adjust your configuration file, save and exit:
sudo nano /etc/linuxptp/ptp4l.conf
[global]
summary_interval 10
use_syslog 0
verbose 1
# Increase priority to allow this server to be chosen as the PTP grandmaster.
priority1 0
priority2 0
clock_servo linreg
serverOnly 1
time_stamping hardware
ptp_minor_version 0
tx_timestamp_timeout 100
dscp_event 46
dscp_general 46
[eth0]
#network_transport UDPv4
delay_mechanism Auto
network_transport L2
Don't forget to enable ptp4l for eth0:
sudo systemctl enable ptp4l@eth0
And restart ptp4l
sudo systemctl restart ptp4l@eth0
The above configuration is important. It uses L2 (MAC based) PTP rather than UDP based PTP. You can change this if you prefer using L3 based PTP. It also compels ptp4l to use PTPv1 to communicate with the NIC. Due to quirks with the Broadcom NIC, if you do not include the ptp_minor_version directive, you will run into timeout issues and all sorts of headache. Again, this prioritises packets with a DSCP value of 46 (high priority, low latency).
You may also want to increase the priority at which ptp4l runs on your system. I have not modified mine to restrict the process to a particular CPU core, but I have changed the scheduler and priority of the process, and changed the startup sequence. This compels ptp4l to operate with sub-hardware level interrupt priority.
Create a systemd unit file if it does not already exist
sudo nano /etc/systemd/system/ptp4l@.service
[Unit]
Description=Precision Time Protocol service
Documentation=man:ptp4l
After=chronyd.service coalesce@.service
[Service]
Type=simple
ExecStart=/usr/local/sbin/ptp4l -A -i %i -f /etc/linuxptp/ptp4l.conf
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=40
Restart=on-failure
[Install]
WantedBy=multi-user.target
Don't forget to reload your unit file, enable ts2phc@, and start the service.
sudo systemctl daemon-reload
sudo systemctl enable ptp4l@eth0
sudo systemctl restart ptp4l@eth0
Here ts2phc is used to timestamp the clock information on the NIC. It uses the SYNC_OUT pin that is exposed via the CM5 connected directly to the PPS out signal from the Uputronics GPS HAT. The internal CM5 Pulldown resistor is enabled here (specified through the overlay in config.txt), however the pulldown strength is notoriously weak. I will consider adding a 10k pulldown resistor directly to this line off the HAT however I have not yet attempted this. So far, I have not had any issues with the internal pulldown resistor.
You may want to back up the default configuration here for future reference.
Create a configuration file, save and exit:
sudo nano /etc/linuxptp/ts2phc.conf
[global]
ts2phc.tod_source generic
leapfile /usr/share/zoneinfo/leap-seconds.list
logging_level 7
ts2phc.pulsewidth 100000000
[eth0]
ts2phc.pin_index 0
sudo nano /etc/systemd/system/ts2phc@.service
[Unit]
Description=Synchronize PTP Hardware Clock from System Time
Documentation=man:ts2phc
Requires=ptp4l@.service
After=ptp4l@.service
[Service]
Type=simple
ExecStart=/usr/sbin/ts2phc -c %i -f /etc/linuxptp/ts2phc.conf
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=40
[Install]
WantedBy=multi-user.target
Don't forget to reload your unit file, enable ts2phc@, and start the service. You must have ptp4l@ running in order for this service to start.
sudo systemctl daemon-reload
sudo systemctl enable ts2phc@eth0
sudo systemctl restart ts2phc@eth0
If the NIC is receiving the PPS pulse, you should see the timestamp converge to within nanoseconds:
sudo systemctl status ts2phc@eth0
This next section is an alternate configuration that allows ts2phc to read the NMEA data directly from GPSD or directly from the GPS serial device. There are several configuration permutations as outlined above.
In the previous configuration, Chrony supplies timing information to the PHY and the timestamp is applied via a PPS pulse. If there is a lapse in NMEA information, such as a loss of 3D fix, the NMEA sentence that ts2phc reads is incomplete and will fail to sync. This is a limitation in ts2phc; that it relies on complete RMC sentences only. The most robust correction for this is using GPSD as a serial source directly to ts2phc as it can fill in the blanks and supply pseudo-complete RMC sentences. For example, if you are using a u-blox device and enable only the NAV-PVT message, GPSD will convert this into a valid NMEA RMC sentence for gpspipe.
Allow ts2phc to read GPSD serialised data. This allows GPSD to receive and parse the GPS data. It is then relayed along a virtual serial device to ts2phc. While this level of indirection introduces delays, it is compensated for by the PPS pulse that ultimately syncronises the timestamp. The gpsd-tools package contains gpspipe which, aptly named, pipes the serial data received by GPSD somewhere. Here it is piped to a low latency FIFO socket that mimics a serial line device that ts2phc can interact with.
Create a gpspipe@ systemd service file, save and exit:
sudo nano /etc/systemd/system/gpspipe@.service
[Unit]
Description=gpspipe Service for %i
Requires=gpspipe@.socket
Requires=gpsd.service
[Service]
ExecStart=/usr/bin/gpspipe -B -r -o /run/gpsd.%i.sock
CPUAffinity=3
CPUSchedulingPolicy=rr
CPUSchedulingPriority=20
Restart=on-failure
[Install]
WantedBy=multi-user.target
Create a gpspipe@ systemd socket file, save and exit:
sudo nano /etc/systemd/system/gpspipe@.socket
[Unit]
Description=GPSD Serial Socket for %i
[Socket]
ListenFIFO=/run/gpsd.%i.sock
NoDelay=true
AmbientCapabilities=CAP_NET_ADMIN
Priority=7
[Install]
WantedBy=sockets.target
Reload your systemd unit files, enable and start your gpspipe socket and service:
sudo systemctl daemon-reload
sudo systemctl enable gpspipe@ttyAMA0.socket gpspipe@ttyAMA0.service
sudo systemctl start gpspipe@ttyAMA0.socket gpspipe@ttyAMA0.service
Edit ts2phc.conf, save and exit:
sudo nano /etc/linuxptp/ts2phc.conf
[global]
ts2phc.tod_source nmea
ts2phc.nmea_serialport /run/gpsd.ttyAMA0.sock
ts2phc.nmea_baudrate 460800
leapfile /usr/share/zoneinfo/leap-seconds.list
logging_level 7
ts2phc.pulsewidth 100000000
[eth0]
ts2phc.pin_index 0
ts2phc.channel 0
ts2phc.extts_polarity rising
Restart ts2phc:
sudo systemctl restart ts2phc@eth0
To take advantage of a topology like Example 4 (currently used) make sure you have the following lines in your chrony.conf file:
#SOCK GPS with PPS
refclock SOCK /run/chrony.ttyAMA0.sock refid SOCK precision 1e-9 offset 0 poll 0 maxsamples 10 noselect
refclock PPS /dev/pps0 refid PPS precision 1e-9 offset 0 poll 0 maxsamples 10 lock SOCK noselect
#PHC eth0 Refclock
refclock PHC /dev/ptp0:extpps:pin=0 tai refid PHC precision 1e-9 poll 0 rate 1 width 0.1 maxsamples 10 prefer
Restart relevant services (note that the order of service restarts is important here; GPSD requires chrony to create a socket for time data, and gpspipe requires gpsd to be running. ts2phc requires gpspipe to receive NMEA timing information):
sudo systemctl restart chrony gpsd setpps gpspipe@ttyAMA0 ts2phc@eth0
Allow ts2phc to read GPS serial data directly from the device; this is at the expense of GPSD. You will not be able to run both concurrently. This removes the indirection between GPSD and the serial device. Note that your GNSS device must serve a valid RMC message for this to work!
Disable GPSD:
sudo systemctl stop gpsd
sudo systemctl disable gpsd
Edit ts2phc.conf, save and exit:
sudo nano /etc/linuxptp/ts2phc.conf
[global]
ts2phc.tod_source nmea
ts2phc.nmea_serialport /dev/ttyAMA0
ts2phc.nmea_baudrate 460800
leapfile /usr/share/zoneinfo/leap-seconds.list
logging_level 7
ts2phc.pulsewidth 100000000
[eth0]
ts2phc.pin_index 0
ts2phc.channel 0
ts2phc.extts_polarity rising
Restart ts2phc:
sudo systemctl restart ts2phc@eth0