These instructions were copied with permission with some light editing, mainly in the form of updated install instructions.
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.
If you want to skip all the manual work and just install chrony
and gps
, see the Github. This has all the necessary configuration that works for the TimeHat, ready to go, using the default software installs rather than the manual build and config listed below.
This is mainly for those of you who would rather not do that work and just want to fire things up, especially from a freshly installed Pi with a DietPi SSD.
Having issues with TimeHat? See the Errors section at the bottom of this page.
Any linux distribution should work, but DietPi works best. Additionally, I am assuming that you will only be using the Pi as a network time server, as John did. 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. Therefore, adjust accordingly if not using a Debian-based system.
The original guide was for an older version. Therefore, this guide has been updated for Bookworm.
/boot/config.txt
modifications:
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 Pi 3, 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
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@tick:~# 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
These are required for future steps to successfully get GPSD
working on your time server.
sudo apt install python3-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: -- -- -- -- -- -- -- --
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
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, but if it looks different, that's fine:
/sbin/hwclock --rtc=$dev --systz --badyear
And line 32:
/sbin/hwclock --rtc=$dev --systz
As of Debian 12, /lib/udev/hwclock-set
, this file now looks like the following:
#!/bin/sh
# Reset the System Clock to UTC if the hardware clock from which it
# was copied by the kernel was in localtime.
dev=$1
#if [ -e /run/systemd/system ] ; then
# exit 0
#fi
#/sbin/hwclock --rtc=$dev --systz
/sbin/hwclock --rtc=$dev --hctosys
Lastly, to prevent getty
from sitting on the ttyAMA0
interface and preventing chrony as well as gpsd from connecting successfully, this needs to be done:
systemctl disable serial-getty@ttyAMA0.service
Then to prevent it from coming back up, ever:
systemctl mask serial-getty@ttyAMA0.service
After rebooting, verify the RTC's operation with
hwclock --show --verbose
hwclock from util-linux 2.38.1
System Time: 1741559450.901052
Trying to open: /dev/rtc0
Using the rtc interface to the clock.
Assuming hardware clock is kept in UTC time.
Waiting for clock tick...
ioctl(4, RTC_UIE_ON, 0): Invalid argument
Waiting in loop for time from /dev/rtc0 to change
...got clock tick
Time read from Hardware Clock: 2025/03/09 22:30:51
Hw clock time : 2025/03/09 22:30:51 = 1741559451 seconds since 1969
Time since last adjustment is 1741559451 seconds
Calculated Hardware Clock drift is 0.000000 seconds
2025-03-09 22:30:50.902532+00:00
Look at the other hwclock options to see how to set it, change from URC to local time, etc.
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 chose 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 python3-dev gnuplot pps-tools git python3-cairo-dev libgtk-3-dev python3-serial asciidoctor python3-matplotlib -y
git clone https://github.com/ntpsec/gpsd
cd gpsd
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.11.2
or even later versions!
scons target_python=python3.11.2 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
Run the included install scripts:
sudo scons install
Install the systemd files:
sudo cp gpsd-3.25.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
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.
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
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!
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
Now that chrony's set up, it should eventually show something like this:
root@tick:~# chronyc sources
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
#* GPS 0 4 377 14 +46ns[ +69ns] +/- 133ns
^- ntp2.flashdance.cx 2 8 377 11 -801us[ -801us] +/- 84ms
^- ntp2.flashdance.cx 2 7 377 74 -1893us[-1892us] +/- 90ms
^- ntp1.flashdance.cx 2 8 377 11 -2391us[-2391us] +/- 85ms
^- time100.stupi.se 1 8 377 7 -2278us[-2278us] +/- 90ms
If it refuses to show #* GPS
, something is wrong and you'll need to debug that.
Errors with /dev/i2c-1? Seeing something like the following?
root@tick:~# i2cdetect -y 1
Error: Could not open file `/dev/i2c-1' or `/dev/i2c/1': No such file or directory
This is because /etc/modules
needs to have i2c_dev
loaded for it to work correctly.
Errors with Failed to connect to bus: No such file or directory
If you're trying to test timedatectl
to validate time synchronization and encounter
this error, it is because dbus
wasn't installed and needs to be installed.
Context: In most cases, dbus
is already installed, as per this post.
I have set up a Github repository to make life easier. Note, /etc/chrony/chrony.conf
in Github makes use of the default Debian install of chrony
with slight modifications and not the built chrony
configuration above.
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 HC2 and that was a miserable failure. 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.
You can get in touch with John to buy one, if you're interested. This guide was updated to work with Debian 12.
We've worked together in testing and making sure that the statistics work as expected.