TimeHat

The TimeHat is a Raspberry Pi "Hardware Attached on Top" or "HAT" module intended for turning a Raspberry Pi (or like single board computer with a compatible GPIO header) into a highly precise, accurate, and stable time source - often used as a network time server. For short term stability, the TimeHat uses a DS3231SN# module as a battery backed real time clock for the system, connected over I²C. This module provides an incredible accuracy of ±2ppm per year, and because of the included CR2032 battery, will maintain time (and precision!) when the rest of the machine is disconnected from power. Long term stability of the TimeHat is provided by a u-blox MAX-M8Q GNSS receiver. The MAX-M8Q provides the system with typical GNSS data - time, date, position, speed, satellites in view, and most importantly - a 1PPS output. When fed into a GPIO pin on the Pi, this allows software to fix the system clock (including the RTC!) to be incredibly accurate.

The TimeHat I decided to build the TimeHat in January, 2021. After building a couple of rudimentary prototype units I realized that having a PCB made would be really nice, so I spent about an hour fiddling in EasyEDA and ordered a batch. Initially I was only going to create one, but after talking with a number of people I figured there would probably be some wider interest, so I decided to order enough parts to make a first run of 20 complete units. The PCB is marked version 0.1, and that very much how I feel about this initial revision. It works perfectly well, but it could be improved upon in some ways.

For example, the next revision will not use breakout boards for the RTC and GNSS receiver, but rather those parts will be mounted directly to the PCB - though this is much more time-consuming to produce, the aesthetic is nicer.

Software and Configuration

Any linux distribution should work, but I like to use DietPi because it's lightweight, very easily configured, and well supported. The instructions for DietPi can be applied to any debian-based OS, and that's what I will be assuming. Additionally, I am assuming that you will only be using the Pi as a network time server. This is recommended because we want to minimize load on the the system that can cause CPU or network jitter that could limit its accuracy.

/boot/config.txt

We need to make a number of modifications to config.txt in order to enable the features we want, and disable those we don't. I have added comments to the relevant lines to clarify their purpose. You can leave wifi enabled if you need it. On the Pi3, bluetooth interferes with our usage of ttyAMA0 and must be disabled.

# disable things we don't need
hdmi_ignore_hotplug=1
disable_splash=1
dtparam=audio=off
dtoverlay=dietpi-disable_vcsm
dtparam=spi=off
dtoverlay=disable-wifi
dtoverlay=disable-bt

# enable i2c for the RTC
dtparam=i2c_arm=on
dtparam=i2c1=on

# configure the DS3231 as the RTC
dtoverlay=i2c-rtc,ds3231

# UART is used for the GNSS receiver
enable_uart=1

# Prevent the CPU from turboing, and lock 
#  its frequency lower than stock to increase stability
force_turbo=1
arm_freq=800

# configure GPIO4 as the input for /dev/pps0
dtoverlay=pps-gpio,gpiopin=4

# set the activity LED to operate as a heartbeat
dtparam=act_led_trigger=heartbeat

Test PPS

One of the first things you can do after connecting the TimeHat to your system is to verify that PPS output is being read by the system. When the green LED on the GNSS module is flashing once per second, you have a valid lock and are receiving timepulse data.

Install the package pps-tools and run ppstest /dev/pps0 as root or with sudo.

root@TimeHat:~# ppstest /dev/pps0 
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1612494793.999998758, sequence: 286 - clear  0.000000000, sequence: 0
source 0 - assert 1612494794.999997028, sequence: 287 - clear  0.000000000, sequence: 0
source 0 - assert 1612494795.999996287, sequence: 288 - clear  0.000000000, sequence: 0
source 0 - assert 1612494796.999998568, sequence: 289 - clear  0.000000000, sequence: 0
source 0 - assert 1612494797.999996733, sequence: 290 - clear  0.000000000, sequence: 0
source 0 - assert 1612494798.999994065, sequence: 291 - clear  0.000000000, sequence: 0

Configure the DS3231

Configuring a hardware real time clock on the Pi is documented in many places, but here are the steps I took.

Install the requirements

sudo apt install python-smbus i2c-tools

Confirm the presence of the i2c devices

sudo i2cdetect -y 1

There should be two, 57 and 68. 57 is an EEPROM and 68 is the RTC. If you see UU instead of 68 that simply means that the RTC is in use by the system already.

Not in use, but detected:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- 57 -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- -- 

In use:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- 57 -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- -- 

Disable the fake hwclock

Since the Pi does not have a hardware RTC, it uses a fake software clock. We need to disable this in order to use a real hardware clock.

sudo apt-get -y remove fake-hwclock 
sudo update-rc.d -f fake-hwclock remove 
sudo systemctl disable fake-hwclock

Enable the real hardware clock

Edit the following configuration file:

sudo nano /lib/udev/hwclock-set

Comment (#) out the following three lines, as of Debian 10 they are 7, 8, and 9:

if [ -e /run/systemd/system ] ; then
  exit 0
fi

Also comment out line 29:

/sbin/hwclock --rtc=$dev --systz --badyear

And line 32:

/sbin/hwclock --rtc=$dev --systz

Reboot

After rebooting, verify the RTC's operation with

sudo hwclock --show --verbose
hwclock from util-linux 2.34
System Time: 1612490451.248926
Trying to open: /dev/rtc0
Using the rtc interface to the clock.
Assuming hardware clock is kept in UTC time.
Waiting for clock tick...
...got clock tick
Time read from Hardware Clock: 2021/02/05 02:00:52
Hw clock time : 2021/02/05 02:00:52 = 1612490452 seconds since 1969
Time since last adjustment is 1612490452 seconds
Calculated Hardware Clock drift is 0.000000 seconds
2021-02-05 02:00:51.233278+00:00

Look at the other hwclock options to see how to set it, change from URC to local time, etc.

Install and configure GPSD

GPSD is the tool that converts the stream of data from the GPS into something useful and allows it to be used a time source for the rest of the system. The package from the system repositories will work just fine, simply specify /dev/ttyAMA0 as the input device and set -n as the startup option to make sure it starts even if a client doesn't ask for data.

I choose to manually build GPSD because I want to make sure to include a few options and clients that are not present in the version that can be found in the package repositories. If you're interested in building GPSD, read on, if you want to stick with the packaged version, feel free to skip this section. Definitely read INSTALL and build.

Install dependencies

sudo apt install build-essential manpages-dev pkg-config scons libncurses-dev \
python-dev gnuplot pps-tools git python-gi-dev python-cairo-dev python-gobject-2-dev \
libgtk-3-dev python3-serial asciidoctor python3-matplotlib

Clone the repository

git clone https://github.com/ntpsec/gpsd

Build!

I use a number of custom options to build GPSD, mostly disabling receiver types I know I don't need. The options below work well with the TimeHat, but run scons -H to see all available options.

Note! Match target_python= to whatever you get from python3 -V! It may be 3.6 or 3.7.

scons target_python=python3.7 pps=yes bluez=no tsip=no tripmate=no tnt=no \
superstar2=no skytraq=no sirf=no oncore=no navcom=no mtk3301=no itrax=no \
geostar=no fv18=no fury=no evermore=no earthmate=no ashtech=no aivdm=no

Install!

Run the included install scripts:

sudo scons install

Install the systemd files:

sudo cp gpsd-3.22.1~dev/systemd/gpsd.{service,socket} /lib/systemd/system/

Create /etc/default/gpsd with the following lines:

GPSD_OPTIONS="-n -b -s 9600"
DEVICES="/dev/ttyAMA0"

Enable and start the service:

sudo systemctl enable --now gpsd

Test the service with cgps or gpsmon

Install and configure chrony

I prefer chrony to NTPd for a number of reasons. This document compares them well. A chrony time server will work perfectly fine with standard NTP clients. Chrony is incredibly well documented. If you choose to run it as your network time server I highly recommend you familiarize yourself with it.

Install dependencies and build

If you built GPSD you may already have all of these in place. Chrony is very simple and easy to build.

sudo apt install git bison build-essential asciidoctor -y
git clone https://github.com/mlichvar/chrony
cd chrony
./configure
make ; make docs
sudo make install

systemd service

Here is a very simple systemd service file you can use to get started:

[Unit]
Description=chrony, an NTP client/server
Documentation=man:chronyd(8) man:chronyc(1) man:chrony.conf(5)
Conflicts=openntpd.service ntp.service ntpsec.service
Wants=time-sync.target
Before=time-sync.target
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/sbin/chronyd -f /etc/chrony.conf

[Install]
Alias=chronyd.service
WantedBy=multi-user.target

Name it chronyd.service and place it in /lib/systemd/system/ , then enable it:

sudo systemctl daemon-reload
sudo systemctl enable chronyd

Make sure that gpsd starts AFTER chronyd! chronyd creates the sock that gpsd writes to!

config file

Below is a basic chrony.conf configuration file. Place it at /etc/chrony.conf

server time-a.nist.gov
server time-a-wwv.nist.gov
server time-a-b.nist.gov 

# This is the critical line that has chronyd create a sock that GPSD can write to.
# This is the most accurate way to get timing data from GPSD into chrony.
# You do not need to create separate sock files for your UART and PPS.
refclock SOCK /var/run/chrony.ttyAMA0.sock refid GPS

rtcsync

logchange 0.5

logdir /var/log/chrony

# optionally if you wish to enable logging, uncomment:
# log measurements statistics tracking

dumpdir /var/log/chrony

driftfile /var/log/chrony/chrony.drift

allow all

Frequently Asked Questions

Q: How do I get one? A:** I don't have many left in stock, and am making a few by hand here and there as requests come in. Email me and I'll let you know what's available.

Q: Some DS3231 modules have a design flaw! Did you account for this?

A: Yes! Many DS3231 modules were designed for a rechargeable LR2032 which are not very common. If a CR2032 cell is used with these, you may have problems including premature death of the battery or even a fire. Thankfully this problem is easily resolved by removing a jumper resistor between the positive power rail and the battery. All of my modules have been corrected in this way and are safe to use with a CR2032.

Q: Why did you use breakout boards instead of buying the bare chips?

A: Speed and price, mostly. Both the RTC and GPS need supporting hardware, and by using pre-built breakout boards they are much easier to integrate into a hat like this. I also got good prices for both so it made sense to stick with this design. In the future I would like to make a more integrated version.

Q: What Raspberry Pi models will this Hat support?

A:Any Raspberry Pi with a 20 pin GPIO header will work. Basically every model other than the first B and A.

Q: Are other single board computers supported?

A: Any SBC that uses the same pinout as the Pi should work. The only one that I have personally tested is the Odroid-C2. The biggest catch with using other boards is configuring the timing pulse input from the GPS. The Raspberry Pi makes this very easy, others may be harder. I am willing to help if I can.

Q: What pins does the TimeHat use?

A:The DS3231 and MAX-M8Q are both powered from the 3v3 rail. The DS3231 is connected over i2c, using SDA and SCL. The MAX-M8Q uses UART TX/RX and GPIO4 for the timing pulse.

Q: What software do you recommend?

A: I am partial to DietPi (due to its light weight and quick boot time) alongside gpsd and chrony. Chrony has a few features that I prefer over NTPSec, but the version packaged with debian/ubuntu/Raspbian/dietpi has a small bug that can be remedied easily by building a newer version. I'll gladly provide instructions for this to anyone interested.

Q: Will you sell kits or bare boards?

A: In the future, if there is enough interest, sure.

Q: Why didn't you do X, Y, or Z?

A: Maybe I had a reason, maybe I just didn't think about it? Send me and email and we can talk about it.

Q: Will you make resources available to those who are interested?

A: Yes! I will gladly share parts info, gerbers, and configuration files with anyone that wants them!