The Gig of Ham

One geek's contributions to the series of tubes

Jun 2, 2016 - 10 minute read - Comments - 905S ARM Blog Internet linux ODROID-C2 sysadmin

Sub $200 Stratum 1 NTP Server with ODROID and Adafruit parts

Time is important. In modern computing, I would say time is second in importance only to good entropy. Unfortunately, getting accurate, reliable time is getting harder. The global NTP pool is under attack, and worse it’s also being used as an amplification vector for DDOS attacks. Primarily because of those two reasons, I’ve decided to block NTP traffic at the border of all Data Center. But this leave a problem: how to get time. The easy answer is an off-the-shelf NTP server that uses CDMA or GPS. The easiest is CDMA because it requires no external antenna. The problem is the major CDMA carriers have committed to turning off the CDMA networks in the next few years. So that leaves GPS. No big deal, until you try to get pricing information for a standalone GPS backed NTP server. Cheapest I could find was only rated at about 10 clients and costs well over three thousand dollars. So I kept digging.

The commercial products have some fancy features: Oven controlled oscillators, 10Mpps outputs, sometimes even rubidium oscillators. I don’t really need any of that, I just need a descent NTP source that supports the 1PPS signal for synchronization. The 1PPS signal is important because you get messages from the GPS receiver once every second, including time. That message is long, and it’s over a 9600bps serial link. That means it can have a variance well in the hundreds of miliseconds. That’s too much. The 1PPS system in the Linux kernel, tied to the 1PPS signal from a GPS receiver gets you down into the 5 microsecond range for variance. Much better.

It turns out, gpsd does almost everything you need to be a stratum 1 NTP server. Pair that with NTPd or chrony and you’re in great shape. Several people have done this with Raspberry Pi systems, but networking on the Pi is miserable since it’s over a USB interface and now most of that work you did to eliminate variance is gone. Some people have also tried to do this with a BeagleBone based system, which has worked but is a bit hacky because the BeagleBone was never really as popular as the RPi. But, I was going in the right direction. Cheap board, simple GPS module, done and done.

Adafruit stocks and ships a number of lovely modules with a 1PPS out, and I started with their Ultimate GPS breakout board because it’s the cheapest and most flexible. I then tried to tie that to various systems. First I got the module working and talking to gpsd over a USB Serial interface. Worked great. The serial interface devices I have lying around are not spectacular so I had no way to test the 1PPS. No big deal. I did have several pcDuino 3 Nano boards, and Adafruit also ships a GPS module for the arduino family. Cool. The levels are wrong, because an Arduino is 5V logic, and the pcDuino is 3.3 but you can get adapter boards. I also wanted a simple display to show status so it could run headless (and also because the serial port would be consumed by the GPS module). So get all those parts in, assemble, and…nothing. Turns out the serial port on the pcDuino 3 Nano is a non-starter. Not wired correctly or something. OK, back to the drawing board.

While the RPi is out, there are a lot of boards that are “RPi compatible” in terms of form factor and the expansion ports. One such is the ODROID series from HardKernel. I had a couple ODROID-C2 boards, so I grabbed the Adafruit Ultimate GPS Hat and a simple display hat and tied them to the C2. Initially, there was much better success than with the pcDuino 3 nano. The C2 also has two serial ports, one for a consle and a second on the RPi connector. It could talk to the GPS reciever no problem, and gpsd got a lock and time. Next was 1PPS, and that’s where I hit a brick wall. At the time, the C2 kernel did not support an IRQ for the GPIO pins (I’m told it does now) so there was no way to use the pps-gpio driver in the kernel. Drat. However, the ODROID-C1+ didn’t have that problem so I ordered a pair of those and waited.

Finally got the boards in, tied everything together and had…minimal success. It took weeks to get the boards, so this project started as a October timeframe “Hey! I have an idea!” and I just got it working last week the way I want. The first issue was the image I was using, odrobian, didn’t have the gpio-pps module and had problems booting on every MicroSD card I tried. Turns out, this is mostly a kernel and u-boot issue on this board. Switching to armbian fixed all those (and it ships with the needed module).

The serial port on the C1+ has an interesting bug, in that it won’t work unless you run a specific command:

stty -F /dev/ttyS2 9600

This forces the port to 9600bps and then everything is fine. Other tools can later change the baud rate, but for whatever reason gpsd cannot. Adding that line to /etc/rc.local solved the problem permanently.

The next issue is the 1PPS signal. The pps-gpio module uses the device tree to find which port the 1PPS signal should monitor. After a lot of research I found that the correct port for the Adafruit hat is GPIOY_3. This means you need to add the following stanza to the dts:

pps {
 compatible = "pps-gpio";
 gpios = "GPIOY_3";

This conflicts with the 1-Wire interface defined earlier in the file (search for w1 and comment out the whole stanza, or change the pin used to a different value like GPIOY_8). Or.. you can just download my compiled dtb that’s a drop in replacement.  (SHA256 sum: 21a16397fcd1248f2fa79ce94f567cd1b230bae4926c1b83bdaaad12bde82b6c ) I also have the source dts available if you want to compare.

A few software tweaks, and it all works. I haven’t finished the software for the display yet, but you can at least get time. Here’s the HOWTO:

Building a ODROID-C1+ based NTP server with Adafruit Ultimate GPS Hat v3

Parts List:

Software used:

  • Armbian 5.10 (beware, 5.11 has a busted kernel, but 5.12 seems to be fine)
  • dtb with the pps stanza for the 1PPS line from the GPS module
  • chrony (it’s easier to integrate with gpsd than ntpd, more secure, and will keep things moving along during bad weather when you may not have GPS lock. It’s also compatible with regular ntp clients.)
  • gpsd from Debian Testing (the version in Jessie has broken 1PPS support)

Hardware notes:

  • The GPS antenna needs a good view of the sky. I’ve had good luck just placing the external antenna on a window sill.
  • If you are going to add the I2C Display module, the external GPS antenna is a must because the onboard antenna will be obstructed
  • You may not want to put the whole device on a window, it could overheat with the sun shining on it directly. Use the external antenna instead and keep the rest of it cool and safe.
  • If your antenna is going to be outdoors, don’t forget GPS compatible lightning arrestors!
  • The ODROID boards have a lovely heatsink on them, so the header bundled with the GPS hat will not work. Use the extra tall or stacking header and make sure there is clearance between the board and the heatsink. I placed the stacking header on the ODROID, and some business cards between the GPS Hat and the heatsink, then tacked both sides of the header with my soldering iron to ensure a gap. YMMV.


  • Flash the Armbian image onto the MicroSD card from a Linux box using a USB adapter (using dd)
  • Boot the device. Note the messages about a reboot, wait 5min for the reboot to occur.
    • If the reboot does not occur:
      • Login with the default root password of “1234” and follow the prompts to change the password and add a user account. Then, shutdown the system.
      • Place the MicroSD card some other Linux box over a USB device and run e2fsck and then resize2fs on the 2nd partition (ex: sudo e2fsck -f /dev/sdb2 && sudo resize2fs /dev/sdb2 )
      • Remove the card from the USB adapter, and place back into the ODROID and power up.
      • The system should boot and the root filesystem should be almost the same size of the MicroSD card used.
  • Login with the default root password of “1234” and follow the prompts to change the password and add a user account (if not already done)
  • Backup the existing dtb file: sudo cp /boot/dtb/meson8b_odroidc.dtb /boot/dtb/meson8b_odroidc.dtb.orig
  • Download the custom dtb file above, and then place on the system as /boot/dtb/meson8b_odroidc.dtb
  • Edit the file /etc/rc.local and add the line stty -F /dev/ttyS2 9600 before the line exit 0
  • Reboot
  • Login with the new user account from now on (it’s good practice). You can also login over SSH for easier cut and paste.
  • Create a new file /etc/apt/preferences.d/testing_unstable with the following contents:
Package: *
Pin: release a=stable
Pin-Priority: 700

Package: *
Pin: release a=testing
Pin-Priority: 650

Package: *
Pin: release a=unstable
Pin-Priority: 600
  • Create a new file /etc/apt/sources.list.d/testing_unstable.list with the following contents:
deb testing main contrib non-free
deb-src testing main contrib non-free

deb unstable main contrib non-free
deb-src unstable main contrib non-free
  • Pin the current kernel image (it works, and the one currently available from armbian did not for me at all, causing a kernel panic at boot. You can try now, if it fails just repeat up to this step) with the following command: sudo apt-mark hold linux-image-odroidc1
  • Get the new package list and perform a security update: `sudo apt-get update && sudo apt-get dist-upgrade -y`
  • Install chrony and the prerequisites for gpsd (we have to do it this way to not confuse the package manager when we pull the newer gpsd): sudo apt-get install chrony python-gtk2 python-cairo pps-tools
  • Install gpsd from Debian Testing: apt-get install gpsd gpsd-clients -t testing
  • Stop the gpsd service: sudo service gpsd stop
  • Edit the /etc/default/gpsd file changing the following values:

    • USBAUTO="false"
    • DEVICES="/dev/ttyS2 /dev/pps0"
    • GPSD_OPTIONS="-n"
  • Test the 1PPS link from the gps reciever using the command sudo ppstest /dev/pps0

  • Modify the /etc/chrony/chrony.conf file to contain the following:

refclock SHM 0 refid GPS precision 1e-1 offset 0.280 delay 0.2
refclock SOCK /var/run/chrony.pps0.sock refid PPS
keyfile /etc/chrony/chrony.keys
commandkey 1
driftfile /var/lib/chrony/chrony.drift
log tracking measurements statistics
logdir /var/log/chrony
maxupdateskew 100.0
dumpdir /var/lib/chrony
local stratum 10
allow 10/8
allow 192.168/16
allow 172.16/12
logchange 0.5
  • Restart chrony: sudo service chrony restart
  • Next, we are going to pre-load the latest almanac into the GPS module so it will lock on MUCH quicker (for details, see the github repo for the tools). Run the following commands:
cd mt3339-utils-gps
eporetrieve MTK14.EPO /tmp/MTK14.EPO
epoinfo /tmp/MTK14.EPO
  (You should get some formatted output and not an error)
epoloader @epoloader.conf /tmp/MTK14.EPO /dev/ttyS2
  • Edit the gpsd init script located at /etc/init.d/gpsd, and add the word ‘chrony’ to the Should-Start line.
  • Start the gpsd service
  • Check the gps status using gpsmon, it should lock within a few moments and show a time and the PPS block should have numbers. Type quit to exit.
  • Now you can check the chrony service to see if it’s getting GPS information using chronyc sources
  • That’s it! You can adjust the allow lines in chrony.conf and deploy. Don’t forget to give the device a static IP as well.
comments powered by Disqus