NavSpark GPS+GLONASS Stratum-1 NTP Time Source with a Raspberry Pi

Intro
Here are my notes for getting a NavSpark GPS+GLONASS module working with a Raspberry Pi (Model B) for Timing/NTP purposes. They have since come out with a specific timing module that would probably be better suited for this. But alas, it's not what I have right now :)

This idea isn't new or original. Obviously time servers have been around for a while. Since the Pi has come out, many people have linked GPS and the Pi together for a low cost time solution. This is my take on it.

Keep in mind, that this is how I set mine up. There are many configurations and options that you can do with just the parts you see here. It can be more simplistic, or it can include a LOT more, and be really awesome.

What is this all about?
For a long time now, I've wanted to setup a Stratum 1 time source. It allows better matching of logs for debugging, and allows a more accurate time source for local network devices.

This page goes over the module, getting it wired up to a Pi, then getting the Pi working with the module. I've also added a i2c TCXO RTC (DS3231) for timekeeping while the unit does not have power. This was added as the Pi does not have an RTC built in, and it may take some time for the Pi to sync with peers and/or GPS to get time set.

This will give me a time source on my network that is extremely accurate for my needs.

I created a custom PCB for attaching the NavSpark module, DS3231, and associated parts to the Pi. This makes it nice and compact to put to the side to just run. Gives it a nice SAF. (Spouse Approval Factor)

What does this page assume?

 * You have a NavSpark (or similar) GPS Module that outputs RS232 TTL NEMA GPS code, as well as a 1PPS (Pulse per second) output.
 * You have a Raspberry Pi Model B Variant (Original, B+, or Pi 2 Model B should all work. However the B is recommended over the A variant due to the hardwire network.)
 * You know how to wire/breadboard to the Pi
 * Fresh install of Raspbian (either directly or through NOOBS)

Other

 * Logic Level Converter - If your GPS module outputs 5v, as the Pi need 3v in. (Note: The NavSpark outputs 3v)
 * Solder
 * Soldering Iron
 * Materials in BOM below if using custom adapter board
 * Pi Case
 * Good, solid/stable output, 5v USB Power Supply (a good quality power supply can make a big difference, especially when used in timing situations. I happen to be using a HP TouchPad supply, as I have a few around.)
 * Set up a cross-compile environment. Highly recommended for recompiling the kernel.
 * Comfortable compiling and installing software/kernel

My Testing Environment



 * Raspberry Pi Model B
 * GPIO Breadboard Breakout
 * NavSpark GPS+GLONASS Module
 * Internal GPS/GLONASS Active Antenna
 * Maxim DS3231 i2c TCXO RTC (Temperature Compensated Oscillator)

Physical Connections
The NavSpark module is referenced with the USB socket on the right side, GPS RF Connection on the left.

For the NavSpark Module
 * Connect the first pin on the bottom row, closest to the USB socket, to the 5V power rail from the Pi.
 * Connect the fourth pin on the bottom row, moving away from the USB socket, to the GND rail from the Pi.
 * Connect the fourth pin on the top row, moving away from the USB socket, to the GPIO pin you are using for PPS (I use 23).
 * Connect the last pin on the top row, closest to the GPS RF U.FL connector, to the Serial RXD pin on the Pi.

For the i2c RTC
 * Connect pin 2, with a capacitor to the 5V power rail from the Pi.
 * Connect pin 16 (SCL) to GPIO3 (SCL) on the Pi.
 * Connect pin 15 (SDA) to GPIO2 (SDA) on the Pi.
 * Connect pin 14 to a Battery, so it keeps time between power-cycles.
 * Connect pin 13 to GND rail from the Pi.

Or optionally, use the PCB mentioned above, which makes all these connections for you, in addition to the i2c RTC.

This does require soldering the components to the board.

NOTE: This setup does not require the i2c 3v pull-up resistors, as the Pi provides that, however the PCB does have the pads for them, just in case.

The Code
You'll want to start with a fresh install of Raspbian, Either directly, or from NOOBS. This makes sure you're on a even base. Once you have a fresh install, just follow the steps below.

If you already have a tweaked install that you want to keep, just pick and choose the commands below that suit what you're trying to do.

Initial Setup
This picks up at first boot after Raspbian install.
 * 1) Set Pi User Password
 * 2) Under Advanced:
 * 3) Set Hostname: NTP1
 * 4) Change GPU Memory to 16M (Not really used, normally headless)
 * 5) Enable Kernel i2c
 * 6) Disable Kernel/Shell over Serial
 * 7) Select Finish and reboot
 * 8) Login as Pi
 * 9) sudo -s
 * 10) passwd   (Set root password)

Update & Tools Install

 * 1) apt-get update
 * 2) apt-get dist-update
 * 3) rpi-update
 * 4) apt-get install pps-tools snmp libcap-dev i2c-tools

Add Boot Options
Edit /boot/config.txt, and add the following to the bottom

Enable PPS over GPIO pin 23
 * dtoverlay=pps-gpio,gpiopin=23

Enable i2c RTC DS3231
 * dtoverlay=i2c-rtc,ds3231

Reboot, and during the following boot, you should see something similar to: [ 5.401032] pps_core: LinuxPPS API ver. 1 registered [ 5.506199] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti  [ 5.638198] pps pps0: new PPS source pps.-1 [ 5.741468] pps pps0: Registered IRQ 417 as PPS source [ 5.822304] bcm2708_i2c_init_pinmode(1,2) [ 5.961135] bcm2708_i2c_init_pinmode(1,3) [ 6.260785] rtc-ds1307 1-0068: rtc core: registered ds3231 as rtc0 [ 6.369424] bcm2708_i2c 20804000.i2c: BSC1 Controller at 0x20804000 (irq 79) (baudrate 100000)

To verify the HWClock: If you haven't set it yet, it may not return a proper value. Once we get NTP setup, you can set the HWClock, and this will return properly.
 * hwclock -r

To verify the PPS: You should see output similar to   trying PPS source "/dev/pps0" found PPS source "/dev/pps0" ok, found 1 source(s), now start fetching data... source 0 - assert 1424653025.000001374, sequence: 74588 - clear 0.000000000, sequence: 0 source 0 - assert 1424653026.000000374, sequence: 74589 - clear 0.000000000, sequence: 0 source 0 - assert 1424653027.000000375, sequence: 74590 - clear 0.000000000, sequence: 0 source 0 - assert 1424653028.000001374, sequence: 74591 - clear 0.000000000, sequence: 0
 * ppstest /dev/pps0

If all was successful, Congrats, the system now sees the PPS and has a RTC!

Kernel Time
Now, the stock Pi kernel should work, however it is missing an option to tell the kernel itself to sync to the PPS Signal. This is the CONFIG_NTP_PPS kernel option. If you were to just go into menuconfig on the kernel, you won't see this however. That is because the kernel has the CONFIG_NO_HZ (tickless) system option set. These two don't like working with each other. So, to overcome this, I highly recommend recompiling the kernel to set the proper options.

It is highly recommended, due to the speed of the Raspberry Pi, that you setup another, faster, machine to do the compiling. This will probably not be an ARM system, so you'll want to setup CrossCompiling. I use a Gentoo system, so I'm going to base setting up the crosscompile system using commands available in Gentoo.

Cross Compiling
Install crossdev, if you already haven't Install the arm environment
 * emerge crossdev
 * crossdev -S -v -t arm-unknown-linux-gnueabi

If you already have both of these installed, verify your build environment with gcc-config.

Use "gcc-config -l" to list the environments, and then "gcc-config set " the appropriate ARM version

Export a variable to make the commands easier
 * export CCPREFIX=arm-unknown-linux-gnueabi-

Grab the latest Raspberry Pi Kernel into the 'pikern' directory cd to the pikern directory for the rest of the compiling
 * git clone --depth 1 git://github.com/raspberrypi/linux.git pikern

Get the config.gz file from /proc on your Pi, and transfer it to your build machine's pikernel directory. Then uncompress it to the kernel config
 * zcat config.gz > .config

Tell the kernel to use the existing config (This shouldn't prompt you for anything, but if it does, it means there are newer options in the kernel you are compiling, than the current running kernel. Answer as you desire.)
 * make ARCH=arm CROSS_COMPILE=${CCPREFIX} oldconfig

Configure the kernel
Enter the kernel's menuconfig system Change the following options
 * modify kernelmake ARCH=arm CROSS_COMPILE=${CCPREFIX} menuconfig
 * General Setup
 * Timers subsystem
 * Change "Timer tick handling" to "Periodic timer ticks (constant rate, no dynticks)"
 * Deselect "Old Idle Dynticks Config" (CONFIG_NO_HZ)
 * Device Drivers
 * PPS Support
 * Select "PPS Kernel Consumer Support" (CONFIG_NTP_PPS)

Optional, but I would also change the CPU Frequency Governor to Performance
 * CPU Power Management
 * CPU Frequency Scaling
 * Change "Default CPUFreq Governor" to "Performance"
 * Deselect Powersave, Userspace, Ondemand, and Conservative

Note: Personally I like a kernel that doesn't really have modules, and only uses the options that are needed. This also reduces compile time, as not everything is built. Because of this, I have pruned down a kernel config and modified it to suite the more basic timekeeping, and less user interactive. This will be available here.

Now save and exit the menuconfig system.

Building the kernel
Now compile the kernel, and take a break (it takes a while....)
 * make ARCH=arm CROSS_COMPILE=${CCPREFIX} -j4

Once the kernel has compiled, create a temporary directory to 'install' the modules to:
 * mkdir modtmp

Now 'install' the modules to that directory:
 * make ARCH=arm CROSS_COMPILE=${CCPREFIX} INSTALL_MOD_PATH=./modtmp modules_install

Compress the modules for transfer to the Pi:
 * tar -cjf modules.tar.bz2 modtmp

Compress the Device Tree files for transfer to the Pi:
 * tar -cjf dts.tar.bz2 arch/arm/boot/dts

Transfer the files to the Pi (note: you'll probably want to use the IP address, instead of the hostname):
 * scp modules.tar.bz2 root@ntp1:~/
 * scp dts.tar.bz2 root@ntp1:~/
 * scp arch/arm/boot/Image root@ntp1:/boot/Image-New

Installing the kernel, modules, and Device Tree definitions
Now move to the pi for the following. This was done as the root user, since that's where the files were transferred to.

Extract the modules
 * tar -xjf modules.tar.bz2

Move your current kernel's modules out of the way (At the time of writing, the kernels referenced are 3.18.7+)
 * mv /lib/modules/3.18.7+ /lib/modules/3.18.7+-old

Move the new modules into place
 * mv modtmp/lib/modules/3.18.7+ /lib/modules

Copy firmware
 * cp -R modtmp/lib/firmware/* /lib/firmware

Extract the Device Tree files
 * tar -xjf dts.tar.bz2

Move the files where needed
 * mv arch/arm/boot/dts/*.dtb /boot/overlays
 * mv /boot/overlays/bcm*.dtb /boot

Kernel Trailer & Install
You will need to add a trailer to the kernel for it to boot and use the Device Tree successfully. Information about this can be found under Section 4.1.

Download mkknlimg
 * wget https://raw.githubusercontent.com/raspberrypi/tools/master/mkimage/mkknlimg -O mkknlimg

Move the old kernel to the side
 * mv /boot/kernel.img /boot/kernel-old.img

Sign the new kernel (which creates a new file, and we'll call it kernel.img!) Output should look like Version: Linux version 3.18.7+ (root@XXXXXXX) (gcc version 4.8.3 (Gentoo 4.8.3 p1.1, pie-0.5.9) ) #3 Sat Feb 21 23:06:15 EST 2015 DT: y
 * perl mkknlimg /boot/Image-3187 /boot/kernel.img

If you want to verify the /boot/kernel.img file, download knlinfo and run it Output shoudl look like Kernel trailer found at 8317848/0x7eeb98: KVer: "Linux version 3.18.7+ (root@XXXXXXX) (gcc version 4.8.3 (Gentoo 4.8.3 p1.1, pie-0.5.9) ) #3 Sat Feb 21 23:06:15 EST 2015" DTOK: true
 * wget https://raw.githubusercontent.com/raspberrypi/tools/master/mkimage/knlinfo -O knlinfo
 * perl knlinfo /boot/kernel.img

Optional: Now if you reboot, it should boot with your kernel. You can verify by the buildtime from uname Output should look like Linux NTP1 3.18.7+ #3 Sat Feb 21 23:06:15 EST 2015 armv6l GNU/Linux Note the matching build times to the trailers.
 * uname -a

If you don't do the signing, and just slap the kernel in place, it'll work, but you won't have any of the device tree stuff. This means the DTOverlay stuff you added to config.txt is ignored.

NTP
The stock install does include NTP. However, it does not include ATOM support, which is needed for PPS. So, we shall build our own!

Now, I haven't figured out a way to cross-compile NTP and have it work on the Pi, so I just do all the building there. This also just installs over top of the Debian Package. Keep this in mind if you update your system and it updates NTP, it could revert your changes, and PPS will stop working (won't even show up in 'ntpq -p')

Download the latest source from I recommend version 4.2.8 or higher. If you're brave, you can try the development track, 4.3.x.
 * http://www.ntp.org/downloads.html

Extract (using example 4.2.8p1) & enter the directory
 * tar -xzf ntp-4.2.8p1.tar.gz
 * cd ntp-4.2.8p1

Configuring & Building NTP
Configure NTP with the options you would like. I chose the following to suite me. Namely ATOM, native NMEA, and GPSD, SHM, and WWV for possible later use. I also enable SNMP, IPv6, and some other options needed for it to work right with the kernel (linuxcaps and clockctl) This will take some time. Well over 20 minutes I believe.
 * ./configure --prefix=/usr --enable-ATOM --enable-NMEA --enable-GPSD --enable-SHM --enable-WWV --with-ntpsnmpd --enable-ipv6 --enable-linuxcaps --enable-clockctl

Once this is done, build NTP, and install it
 * make && make install

ntp.conf Configuration
Add (or adjust) the following settings to your ntp.conf.

We're basically adding the server configurations for the GPS/NMEA and PPS. Also, you want to configure some other NTP servers to sync against, as the GPS time tends to be off/drift. The more the better. If you only sync with one or two, you'll have what happened a few years ago, where if that time gets off, and you only have a limited selection to pick from, you trust the wrong time. This lead to some interesting problems. So... select as many as you want (within reason).

driftfile /var/lib/ntp/ntp.drift enable calibrate enable kernel # Enable this if you want statistics to be logged. statsdir /var/log/ntpstats/ statistics loopstats peerstats clockstats filegen loopstats file loopstats type day enable filegen peerstats file peerstats type day enable filegen clockstats file clockstats type day enable # You do need to talk to an NTP server or two (or three). #server ntp.your-provider.example # GPS Module via NMEA server 127.127.20.0 mode 82 minpoll 4 maxpoll 6 prefer fudge 127.127.20.0 flag1 1 flag2 0 flag3 1 time2 +0.2100 # PPS of GPS Module server 127.127.22.0 minpoll 1 maxpoll 1 fudge 127.127.22.0 flag3 1 # Some Pool servers, from different sub-pools as well # General NTP Pool (mainly for IPv6) server 2.pool.ntp.org # General US NTP Pool server 0.us.pool.ntp.org server 1.us.pool.ntp.org # Gentoo NTP Pool server 0.gentoo.pool.ntp.org # pool.ntp.org maps to about 1000 low-stratum NTP servers. Your server will # pick a different set every time it starts up. Please consider joining the # pool:  server 3.debian.pool.ntp.org server 2.debian.pool.ntp.org

The rest of the config goes on about access control. The defaults are (now) fairly restrictive, and only allow time requests. So anyone can set time against this server. Adjust this as needed, but don't make it too open.

Also, as noted on the Debian pool comments, if you set this up, please consider joining the NTP Pool.

Networking
I highly recommend setting a static IP address. Generally, you want all your machines on the network to use this device as it's timesource. You don't want it changing IPs on you.

Edit /etc/network/interfaces

Change the line 'iface eth0 inet dhcp' to be something like (to support both IPv4 and IPv6 Static) iface eth0 inet static address 10.10.10.2 netmask 255.255.255.0 gateway 10.10.10.1 iface eth0 inet6 static address 2001:db8::10 netmask 64 gateway 2001:db8::1

If you use DHCP, make sure to remove "ntp-servers" out of the 'request' field in /etc/dhcp/dhclient.conf

GPS & UDev Rules
Edit/Create file /etc/udev/rules/09-gps.rules with the following information # Adds symlink from serial interface to /dev/gps0 for ntpd KERNEL=="ttyAMA0", SYMLINK+="gps0" As noted by the comment, this adds the symlink to /dev/gps0 for NTP to pick up on.

Timezone
What's a good timekeeping box without the correct timezone?

Remove the existing file /etc/localtime
 * rm /etc/localtime

Link in your correct timezone (I use New_York)
 * ln -s /usr/share/zoneinfo/America/New_York /etc/localtime

Configuring HW Clock
Edit /etc/default/hwclock and uncomment the following: HWCLOCKACCESS=YES HCTOSYS_DEVICE=rtc0

Edit /etc/init.d/hwclock.sh and make the following changes: Starting at line 64, you want to COMMENT out the following check for udev. If you don't, the clock fails to set at startup. So it should look something like this, with added comment #i2c RTC doesn't rely on udev, so this will fail out if left in. #if [ -d /run/udev ] || [ -d /dev/.udev ]; then #return 0 #fi

You now have a Hardware clock, so get rid of the pesky fake hardware clock
 * dpkg --purge fake-hwclock

Now enable hwclock.sh at boot
 * update-rc.d hwclock.sh enable

While you're at it, disable X11-common and alsa-utils from starting. Not needed.
 * update-rc.d alsa-utils disable
 * update-rc.d x11-common disable

Finished
Now with any luck, you can reboot the system, and it will come back with it's static IP, several other NTP sources configured, and then sync to PPS. You can verify all this with ntpq -p Output similar to: remote          refid      st t when poll reach   delay   offset  jitter ==============================================================================   xGPS_NMEA(0)     .GPS. 0 l  14   16  377    0.000  -96.765   8.635 oPPS(0)         .PPS. 0 l   5    8  377    0.000    0.000   0.004 -taranis        209.51.161.238   2 u   50   64  377    0.506    2.603   0.108 -blue.1e400.net 91.189.89.199    3 u   50   64  377   15.990    1.320   6.374 -fairy.mattnordh 200.98.196.212  2 u   12   64  377   30.491    3.736   0.197 *74.117.238.11 ( 1.80.17.144     3 u   27   64  377   34.860    2.174   1.333    -209.118.204.201 149.20.64.28     2 u    3   64  377   87.911    4.254   1.502
 * ntpq -p

Caveats
This isn't perfect by any means. Between the GPS module not being meant for timing, to the ethernet being connected over USB which introduces delays and possibly jitter. Also temperature changes affect it (both the GPS module and RTC have Temperature Compensated crystals).

There are many more precise solutions. Some of them you can even use with the Pi and modify it to be more precise.

Barring the above, it's still better than relying on your PCs built in clock!

Resources Used to Complete this
Here's some sites I used, in no specific order. Some are referenced inline above, others are not.
 * http://www.satsignal.eu/ntp/Raspberry-Pi-NTP.html
 * http://www.satsignal.eu/ntp/Raspberry-Pi-quickstart.html
 * https://support.ntp.org/bin/view/Sandbox/HowtoPpsOnRaspberryPi
 * http://elinux.org/Raspberry_Pi_Kernel_Compilation
 * https://github.com/raspberrypi/documentation/blob/master/configuration/device-tree.md
 * http://ntpi.openchaos.org/pps_pi/
 * http://www.catb.org/gpsd/gpsd-time-service-howto.html
 * http://www.raspberrypi.org/forums/viewtopic.php?f=44&t=16218
 * https://support.ntp.org/bin/view/Dev/Cross-compilingNTP

Fin
Overall, I'm very happy with how this turned out. Granted, some things could still be done to improve upon this, and maybe over time I will do those things. I do want to do the same setup with the BeagleBone Black, and see how it compares. I also have a Soekris Net4501 that I plan to throw into the mix as well.

Also, it's another project complete, and documented! Woo!

Did I mention you should consider joining the NTP Pool? If you can spare some Internets, I highly recommend it.