A guide to build a NTP server with GPS/PPS

  • Operation confirmed with testing in our ODROID XU4 on 4.14.15-103 kernel with our custom setup.

You can build your own NTP server with GPS and PPS on your ODROID.
We will use modern NMEA driver on NTP service.


This gives you very accurate time and would be helpful for specific use cases.
The atomic clocks in the GPS satellites are monitored and compared to 'master clocks' by the GPS Operational Control Segment; this 'GPS time' is steered to within one microsecond of Universal Time.
Our GPS receiver provides 1PPS output signal but you need a wire soldering.
This pulse has a rising edge aligned with the GPS second, and is used to discipline local clocks to maintain synchronisation with Universal Time (UT).
So our local server can have a very accurate time with less than 10 microseconds of tolerance hopefully.

Before you start, you have to fix your USB GPIO module to put PPS via GPIO.

Required tools are:

  • A nipper
  • A screw driver
  • An insulating tape
  • A cutter
  • A jump cable
  • And soldering tools (Not in the screenshot above)

There are 4 screws on the back of the GPS module. They are covered by a back sticker, so you should find out where they are at by rubbing that.
And after that, cut the sticker and detach the cut part to unscrew the 4 screws.
May you can see the following photos to get help.

Uncover the module and you can see a PCB board, which is what you have to soldering out.

Very important part of this guide. You have to soldering out a jump cable to the exact pin of the chip.
Location of the pin is shown on the following photo.
Be careful of the short circuit. PPS comes out of that pin.

If you've done so, you may clean up the cable as shown the photo below.

Place it like before and screw out again.

Connect the jump cable to the GPIO pin #18 of the XU4 shifter shield.

Connect the USB cable to the ODROID XU4, and connect LAN, power cable as well.

Well done.

Our mainline kernel doesn't fully support PPS from GPIO. Some required setups should be done by you.
So you should build your own kernel on your ODROID XU4.

  • This guide is for native build on your ODROID XU4. Don't proceed with your PC.
  • Install Ubuntu Minimal and update first.

Install some tools to build a new kernel.

$ sudo apt update && sudo apt install git gcc g++ build-essential libncurses5-dev bc

Get the Linux kernel source from our official Github repository.

$ git clone --depth 1 https://github.com/hardkernel/linux.git -b odroidxu4-4.14.y odroidxu4-4.14.y
$ cd odroidxu4-4.14.y

Edit arch/arm/boot/dts/exynos5422-odroidxu4.dts file to add a new device which getting PPS from GPIO #18.

$ vi arch/arm/boot/dts/exynos5422-odroidxu4.dts

Add following contents.

        dummy_codec : spdif-transmitter {
        /* add for pps-gpio */
        pps {
                compatible = "pps-gpio";
                gpios = <&gpx1 2 GPIO_ACTIVE_HIGH>;
                status = "okay";

And make a custom menuconfig.

$ make odroidxu4_defconfig
$ make menuconfig

Find and enable with space key:

  • Device Drivers→[*]PPS Support
  • Device Drivers→[*]PPS Support→<M>PPS client using GPIO

Save end exit.


$ make -j 8

Install a new kernel referring to this guide: A Guide to install a new kernel.

If your custom settings works well, that would make new devices at /dev. Let's check them.

$ ls -al /dev/{ttyACM*,gps*,pps*}
# results
crw------- 1 root root    248, 0 Jan 31 14:21 /dev/pps0
crw-rw---- 1 root dialout 166, 0 Jan 31 14:53 /dev/ttyACM0
lrwxrwxrwx 1 root root         7 Jan 31 14:21 /dev/ttyACM99 -> ttySAC0

If any one on the example above doesn't exist, you've done on something wrong way, try to set/build the kernel again.
If all of them exist, make soft link files to use further after.

$ sudo ln -sF /dev/ttyACM0 /dev/gps0
$ sudo ln -sF /dev/pps0 /dev/gpspps0
$ ls -al /dev/{ttyACM*,gps*,pps*}
# results
lrwxrwxrwx 1 root root        12 Jan 31 15:50 /dev/gps0 -> ttyACM0
lrwxrwxrwx 1 root root         9 Jan 31 15:51 /dev/gpspps0 -> /dev/pps0
crw------- 1 root root    248, 0 Jan 31 15:50 /dev/pps0
crw-rw---- 1 root dialout 166, 0 Jan 31 15:50 /dev/ttyACM0
lrwxrwxrwx 1 root root         7 Jan 31 15:50 /dev/ttyACM99 -> ttySAC0

Make sure your result like above.

Install GPS related packages and configure it.

$ sudo apt install gpsd gpsd-clients
$ sudo dpkg-reconfigure gpsd

And test.

$ sudo gpsmon /dev/gps0

An example screenshot using “gpsmon /dev/gps0”.
Wait more than 5 minutes to get GPS information properly.

Install PPS tools.

$ sudo apt install pps-tools


$ sudo ppstest /dev/gpspps0
# results
trying PPS source "/dev/gpspps0"
found PPS source "/dev/gpspps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1517363638.431673232, sequence: 130 - clear  0.000000000, sequence: 0
source 0 - assert 1517363639.431676649, sequence: 131 - clear  0.000000000, sequence: 0

A new row starting with “source 0 - assert …” will be added for every each second.

Install NTP service.

$ sudo apt install ntp

Edit /etc/ntp.conf file to use GPS/PPS.
Back up original file, and create a new configuration file including setup below.

$ sudo mv /etc/ntp.conf /etc/ntp.conf.bak
$ sudo vi /etc/ntp.conf
# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help

# Drift file to remember clock rate across restarts
driftfile /var/lib/ntp/ntp.drift

# Server from generic NMEA GPS Receiver
# server: NMEA serial port (/dev/gps0), mode 16 = 9600 baud + 2 = $GPGGA
# fudge:  flag 1 for use PPS (/dev/gpspps0), time2 for calibration time offset
server mode 18 minpoll 3 maxpoll 3 prefer
fudge flag1 1 time2 0.000 refid gPPS

Note that time2 parameter(0.000) is for editing time offset for calibrating the result time.
Lastly, restart NTP service.

$ sudo service ntp restart
$ sudo service ntp status
# results
● ntp.service - LSB: Start NTP daemon
   Loaded: loaded (/etc/init.d/ntp; bad; vendor preset: enabled)
   Active: active (running) since Wed 2018-01-31 17:44:58 KST; 3s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 744 ExecStop=/etc/init.d/ntp stop (code=exited, status=0/SUCCESS)
  Process: 754 ExecStart=/etc/init.d/ntp start (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/ntp.service
           └─765 /usr/sbin/ntpd -p /var/run/ntpd.pid -g -u 111:115
Jan 31 17:44:58 odroid ntp[754]:    ...done.
Jan 31 17:44:58 odroid systemd[1]: Started LSB: Start NTP daemon.
Jan 31 17:44:58 odroid ntpd[765]: proto: precision = 1.375 usec (-19)
Jan 31 17:44:58 odroid ntpd[765]: Listen and drop on 0 v6wildcard [::]:123
Jan 31 17:44:58 odroid ntpd[765]: Listen and drop on 1 v4wildcard
Jan 31 17:44:58 odroid ntpd[765]: Listen normally on 2 lo
Jan 31 17:44:58 odroid ntpd[765]: Listen normally on 3 eth0
Jan 31 17:44:58 odroid ntpd[765]: Listen normally on 4 lo [::1]:123
Jan 31 17:44:58 odroid ntpd[765]: Listen normally on 5 eth0 [fe80::4db2:ce0b:48f3:26af%2]:123
Jan 31 17:44:58 odroid ntpd[765]: Listening on routing socket on fd #22 for interface updates

Wait for a while, at least 20 minutes to become stable state.
The PPS output is enabled only when it gets several satellites stably.
And you can see the results like below.

$ ntpq -p
# results; Check that 'o' character exists before IP numbering and reach value is increasing up to 377.
     remote           refid      st t when poll reach   delay   offset  jitter
oGPS_NMEA(0)     .gPPS.           0 l    1    8  377    0.000    0.008   0.002
$ ntptime
# results; Check that estimated error is just 1 us(Microsecond).
ntp_gettime() returns code 0 (OK)
  time de1bee1d.49adfb50  Wed, Jan 31 2018 16:26:21.287, (.287811636),
  maximum error 2000 us, estimated error 1 us, TAI offset 0
ntp_adjtime() returns code 0 (OK)
  modes 0x0 (),
  offset -3.606 us, frequency 1.000 ppm, interval 1 s,
  maximum error 2000 us, estimated error 1 us,
  status 0x2001 (PLL,NANO),
  time constant 3, precision 0.001 us, tolerance 500 ppm,
  • odroid-xu4/application_note/gpspps_ntp_server.txt
  • Last modified: 2018/03/05 16:20
  • by joshua