One of the things I’ve always loved to tinker with is time sources and synchronization. Typically this has been tied to sensible things like the Network Time Protocol and designing and maintaining time distribution systems for broadcast networks. Lately though I’ve been toying with ‘real’ time sources – GPS and MSF broadcasts. This is a quick tutorial on how to set up a Raspberry Pi, which at only a few watts makes for an economical time server, to talk to a Venus 638FLPx GPS receiver (available from Sparkfun on a suitable breakout board here).
It’s worth noting that the GPS chip in question is not really ideal for timing applications. There are nicer boards for this purpose. But the great thing is that because most of these things are pretty standard, almost all this guide will still work. Venus do make an IC, the 638LPx-T, which is a variant of this chip designed for timing with an accuracy of plus/minus 30us, but I can’t find that on a breakout board and I had the FLPx board spare from a UAS platform.
The basic requirements for a GPS time source/server are:
- GPS module which outputs NMEA strings on serial and has a 1PPS output
- Computer with GPI pin for 1PPS and serial connectivity running Linux
- GPS antenna in a place that can see a good chunk of sky with a feed back to the module
The pinout on the Venus board has serial RX/TX, +3.3V, GND and PPS. For my timeserver I’m using the Raspberry Pi – the GPI to use is pin 23 on the headers, RX goes to TX and TX goes to RX also on the header, and the header power for 3V3 works perfectly for powering the module. I used an Adafruit Pi Cobbler to breadboard this, but will use a Ciseco Slice of Pi board to make something a bit more solid later. It’s pretty simple to hook up, in any case.
The tricky bit comes with the software. Let’s talk about how GPS time sourcing works.
The GPS satellites rely on highly accurate timing, and broadcast UTC time information as part of their signals. The difference in the timestamps is what, largely, allows your receiver to figure out where you are. If you know where you are, though, you can use those timestamps to come up with a very accurate fix for time.
The NMEA strings that come off the GPS describe all sorts of things. They look a bit like this:
pi@ntpi ~ $ cat /dev/gps0 $GPGGA,201705.000,5644.4848,N,00142.3529,W,1,10,0.9,64.4,M,49.0,M,,0000*75 $GPGLL,5644.4848,N,00142.3529,W,201705.000,A,A*43 $GPGSA,A,3,07,26,08,19,15,05,09,21,18,24,,,1.5,0.9,1.2*3B $GPZDA,201705.000,29,12,2012,00,00*5E
They’re a bit obscure but they have the time encoded into them along with things like how many satellites are used in the solution, velocity/heading, and so on. The first chunk, $GPGGA for instance, is a preamble for each string – the last three characters are used to refer to a string of that type, eg GGA.
Sadly this won’t get us a very accurate reference. Let’s look at what the PPS looks like, for starters, with an oscilloscope:We can see quite clearly these are small, 5ms pulses exactly once a second. Great! So how about those NMEA strings being sent over serial?
We can see in the top half of this trace a full NMEA transmission occurring just after (1.18ms) the PPS pulse – but the NMEA transmission is huge! And if we follow it, we see it move around, too.
This is a zoomed in view of a PPS pulse against the NMEA transmission (1.6ms after the pulse, this time – plenty of variance). This was taken with the GPS in ‘send NMEA in sync with PPS’ mode!
So if we want to get our device accurate we need to use PPS. But if we just use PPS, we don’t know which second we’re in! So we have to use both. Fortunately, ntpd will manage this for us.
Diving back to the software, then – we need a kernel that can deal with the PPS pulses on our GPI port. Let’s install one:
git clone https://github.com/davidk/adafruit-raspberrypi-linux-pps.git cd adafruit-raspberrypi-linux-pps/ sudo mv /boot/kernel.img /boot/kernel.img.orig sudo cp kernel.img /boot sudo mv modules/* /lib/modules sudo sh -c "echo 'pps-gpio' >> /etc/modules"
We need to disable the default usage of the Pi’s serial, which is to provide a tty. Open /boot/cmdline.txt and remove all elements referring to ttyAMA0. Mine looks like this:
dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
Next, open /etc/inittab and comment this line out:
#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
We also want to configure udev to put our GPS module and PPS where ntpd will expect it to be:
sudo nano /etc/udev/rules.d/80-gps-to-ntp.rules # Change MODE of ttyAMA0 so it is readable by NTP and provide a symlink to # /dev/gps0 KERNEL=="ttyAMA0", SUBSYSTEM=="tty", DRIVER=="", SYMLINK+="gps0", MODE="0666" # Symlink /dev/pps0 to /dev/gpspps0 KERNEL=="pps0", SUBSYSTEM=="pps", DRIVER=="", SYMLINK+="gpspps0", MODE="0666"
Next we’re going to install then remove ntpd – we’ll be back with an updated version.
sudo apt-get install ntp sudo apt-get remove ntp sudo apt-get install libcap-dev
Time to install and compile a PPS-enabled ntpd.
wget http://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-dev/ntp-dev-4.2.7p340.tar.gz tar zxf ntp-dev-4.2.7p340.tar.gz cd ntp-dev-4.2.7p340/ ./configure --prefix=/usr --enable-all-clocks --enable-parse-clocks --enable-SHM --enable-debugging --sysconfdir=/var/lib/ntp --with-sntp=no --with-lineeditlibs=edit --without-ntpsnmpd --disable-local-libopts --disable-dependency-tracking --enable-ipv6 && make && sudo make install
Now we’ve got all this we need to reboot to load the new kernel:
First, we can verify that we can see the NMEA sentences:
You should see NMEA strings! Ctrl+C to kill it. Next, let’s test the PPS.
pi@ntpi ~ $ dmesg | grep pps [ 0.000000] Linux version 3.1.9adafruit-pps+ (davidk@bender) (gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #21 PREEMPT Sun Sep 2 10:57:58 PDT 2012 [ 1.148909] usb usb1: Manufacturer: Linux 3.1.9adafruit-pps+ dwc_otg_hcd [ 14.707851] pps_core: LinuxPPS API ver. 1 registered [ 14.712724] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <email@example.com> [ 14.728437] pps pps0: new PPS source pps-gpio.-1 [ 14.731832] pps pps0: Registered IRQ 108 as PPS source
Looks like we’re good to go. Check the pps-utils package if you need to verify more completely.
Next up: telling ntp about this shiny new local clock!
We need to do one thing first – if your local network has a DHCP server that announces time servers, you want to use your local NTP config despite this. You need to edit /etc/init.d/ntp and comment the following lines out:
#if [ -e /var/lib/ntp/ntp.conf.dhcp ]; then # NTPD_OPTS="$NTPD_OPTS -c /var/lib/ntp/ntp.conf.dhcp" #fi
Open up /etc/ntp.conf and adjust it to taste. The following stanza should get you up and running:
server 127.127.20.0 mode 24 minpoll 3 maxpoll 4 iburst true prefer fudge 127.127.20.0 flag1 1 flag2 0 flag3 1 flag4 1 time2 0.093
The settings here are documented here and can be configured to suit your module. The time2 value may need to be larger (0.4 or so), and you may want to keep or remove the existing servers configured in ntp.conf – or just reconfigure them to be geographically local. You can get an idea of the time2 offset by adding 128 to whatever mode value you’re using, then looking at the timings in /var/log/ntpstats/clockstats after a ntpd restart. Pick the time offset for the first NMEA string seen and there’s your number.
Restart ntpd, wait a few minutes for it all to settle down, and verify things are working:
pi@ntpi ~ $ ntptime ntp_gettime() returns code 0 (OK) time d489d596.2b0cfe14 Sat, Dec 29 2012 20:48:22.168, (.168167784), maximum error 2000 us, estimated error 1 us, TAI offset 0 ntp_adjtime() returns code 0 (OK) modes 0x0 (), offset 2.518 us, frequency 79.473 ppm, interval 1 s, maximum error 2000 us, estimated error 1 us, status 0x2007 (PLL,PPSFREQ,PPSTIME,NANO), time constant 3, precision 0.001 us, tolerance 500 ppm, pi@ntpi ~ $ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== oGPS_NMEA(0) .GPS. 0 l 8 8 377 0.000 0.002 0.004 +ns0.luns.net.uk 126.96.36.199 2 u 63 64 17 44.161 3.389 0.709 -ntp.oceanmediag 188.8.131.52 2 u 5 64 37 34.806 2.048 0.585 +dns1.rmplc.co.u 184.108.40.206 2 u 63 64 17 36.375 2.488 0.282 *mail1.itdojo.or 10.10.120.2 2 u 56 64 17 51.280 3.977 0.270 pi@ntpi ~ $ ntptrace localhost: stratum 1, offset 0.000001, synch distance 0.001090, refid 'GPS'
Note the o before GPS_NMEA(0) in ntpq -p – this indicates the PPS is being used. If you don’t have the PPS enabled your time will kinda suck, as we saw a bit with the scope traces. To illustrate this, here’s a comparison of system performance between NMEA-only and PPS.
Another thing I ran into was the GPS reporting the wrong time or not reporting its timing offset – just ensure you have enabled the GPZDA statement to avoid this. Symptoms of this can include huge offsets and being marked as a falseticker (x) in peer lists of other peers. Just make sure your module is configured properly. This is what I did to configure my module:
- Enabled position pinning
- Disabled all NMEA strings except for GGA, GSA, GLL and ZDA
- Set update rate to 1Hz and output sync to UTC
- Set baud rate to 9600 (higher is not better!)
This seems to work quite well, and I’m very happy with the performance given the low cost of the solution – it just needs popping in a box and it’ll be a nice stable time reference for my home network. I’m still interested in sourcing time from MSF with a receiver but we have quite poor reception of the MSF signal here, so we’ll see if that’s even feasible with the Pi. Given how important it is to maintain accurate time in broadcast networks, especially with external programme sources, this is a really nice thing to have gotten working and documented. Kudos to the guys over at this thread on the Raspberry Pi forums who did a lot of the legwork figuring this all out!
Update from a little later
I sat the NTPi on my workbench in a corner with a GPS antenna with fairly good visibility (logging/plotting GPS satellites in view is a future extension I want to do). Even running other things (munin) the NTPi performs well, with only occasional large deviations, at least from this minimally-complex and not-entirely-thought-through amateur measurement!