Сервер точного времени из TrueTime 1995 года: Raspberry Pi 5, GNSS и chrony

Перевод статьи Rebuilding a 1995 GPS Time Server so I don’t get Telstra’d — Jeff Geerling.
В июне я купил вот этот сервер времени TrueTime XL-AK, чтобы поближе познакомиться с историей точного времени по GPS.
Он приехал 22 июня, а всего через 16 дней похожий GPS-сервер времени на 12 часов положил сотовую связь в Австралии! Я снял об этом короткое видео, но сам TrueTime откладывал на потом… до сегодняшнего дня.

TrueTime XL-AK с Pi внутри: родной ЖК-дисплей Densitron снова показывает время
Последние пару недель я занимался «рестомодом» этого устройства с помощью Raspberry Pi, чтобы собрать NTP-сервер первого уровня (stratum 1), который со временем хочу добавить в NTP Pool. Мне удалось уместить внутрь Pi 5 с GNSS HAT, выводить время и состояние GPS на встроенный ЖК-дисплей 16×2, показывать статус двухцветным светодиодом — в общем, снова сделать эту коробку полезной.
Подробнее про железо я рассказываю в сегодняшнем видео на YouTube: смотреть.
А в этом посте я покажу, как настроил Raspberry Pi в роли NTP-сервера первого уровня. В деле:
- L1 GNSS HAT от Jimmy Paputto;
- chrony (синхронизация времени и NTP-сервис);
- протоколы Time и Daytime;
- ЖК-дисплей Densitron, встроенный в TrueTime XL-AK;
- двухцветный светодиод, встроенный в TrueTime XL-AK;
- мой кронштейн для крепления Pi внутри TrueTime.
Я взял Raspberry Pi 5 с 4 ГБ памяти, но ничто не мешает использовать для этого проекта Pi 5 с 1 ГБ за 44 доллара (если на нём будут крутиться только сервисы времени для небольшой сети).
Прямо сейчас сервер нужен мне, чтобы раздавать время на VCF Midwest в следующие выходные. Как и во всех моих проектах, детали копились месяцами, а сборка, как обычно, дождалась последней пары недель перед дедлайном :)
L1 GNSS HAT от Jimmy Paputto#

GNSS HAT выдаёт секундные импульсы (1 PPS) — видно на осциллографе
Я установил GNSS HAT поверх Pi с помощью комплектной GPIO-проставки и крепёжных винтов. Снизу винтами прикрутил Raspberry Pi Bumper.
Записав на microSD-карту Pi OS Lite (без графической оболочки), я собрал из исходников софт для GNSS HAT от Jimmy Paputto:
sudo apt -y install build-essential cmake libgpiod-dev python3-dev
git clone https://github.com/jimmypaputto/gnsshat.git
cd gnsshat
mkdir -p build && cd build
cmake .. -DBUILD_PYTHON=ON -DBUILD_EXAMPLES=ON
make -j$(nproc)
sudo make install
В комплект входит мост данных GNSS, который передаёт NMEA-сообщения (информацию о времени и дате) в gpsd и chrony. Можно поступить и иначе — воткнуть USB-C разъём GNSS HAT в USB-A порт Pi и обращаться к u-blox NEO-M9N напрямую.
После установки я прошёл по инструкции Raspberry Pi Time Server with PPS.
chrony#
Когда дошло до настройки chrony, я прописал в /etc/chrony/chrony.conf следующее:
# GNSS + PPS time server
# GPS time via shared memory from gpsd
refclock SHM 0 offset 0.0 delay 0.05 refid NMEA noselect
# PPS — precise edge timing
refclock PPS /dev/pps0 refid PPS lock NMEA prefer trust poll 3 filter 16
# The Pi's XO is fairly stable, but not quite TCXO-level.
maxclockerror 0.5
# Hardware timestamping for more precision on Pi 5
hwtimestamp *
# Fallback internet pools
pool 0.pool.ntp.org iburst
pool 1.pool.ntp.org iburst
# Allow all LAN clients
allow 10.0.0.0/16
# Allow requests routed through Twingate container.
allow 172.17.0.0/16
maxupdateskew 100.0
makestep 1000 3
rtcsync
# Ignore clock updates >100ms (fixes gpsd reporting time 1s off every ~31 min)
maxchange 0.1 1 -1
К демо-стенду NTP я подключаюсь удалённо через Twingate: во время тестов у меня несколько WAN-подключений, и пробиваться через вещи вроде двойного NAT проще с проксирующим сервисом.
После правки конфигурации перезапустите chrony:
sudo systemctl restart chrony
Проверить, что всё работает, можно так:
chronyc sources -v
chronyc tracking
Понаблюдав за метриками chrony несколько дней, я подправил часть настроек под своё железо: например, maxclockerror я уменьшил относительно значения по умолчанию, потому что сумел стабилизировать частоту генератора, немного подкрутив настройки самого Pi.
Тонкая настройка Pi для точного времени#
Особенно полезными оказались такие изменения:
- вентилятор, работающий с постоянной скважностью;
- принудительная работа SoC на полной частоте через
force_turbo; - привязка прерываний PPS в ядре к четвёртому ядру процессора (предварительно изолированному);
- теплоизоляция нижней стороны Pi, чтобы кварцевый генератор находился в более стабильных тепловых условиях;
- теплоизоляция остального Pi — за счёт того, что он закрыт в корпусе TrueTime.
Кое-что ещё из опробованного заметного эффекта не дало — по крайней мере, на тестовых интервалах по 6–12 часов.
Если вам интересна эта тема, очень советую вот этот пост в блоге Austin’s Nerdy Things.
Стабильная частота Pi#
Чтобы Pi всё время работал на 2,4 ГГц и тепловой режим был стабильнее (кварц стоит рядом с SoC, и всплески нагрузки на процессор сильно на него влияют), можно включить опцию force_turbo. Это постоянно съедает примерно на 1 Вт больше, но оно того стоит, если вам нужна стабильная частота генератора (а для точного времени это важно).
Добавьте в /boot/firmware/config.txt следующее и перезагрузите Pi:
# Force performance governor so CPU maintains a more stable temperature.
force_turbo=1
Постоянная скорость вентилятора#
Я довольно долго экспериментировал с инструментами вроде NTPheat, которые стабилизируют температуру SoC. Такие инструменты работают, особенно если Pi теплоизолирован. Потоки воздуха — особенно от систем отопления и кондиционирования — заставляют генератор Pi «гулять» как попало. А стабильные часы любят постоянный дрейф, потому что его можно измерить и учесть.
Но после долгих тестов я пришёл к тому, что теплоизоляция нижней стороны Pi (именно там физически находится кварц), закрытый корпус для остальной платы, постоянная скорость вентилятора и большой радиатор дают более стабильную частоту — по крайней мере, на нескольких циклах по 6–12 часов1.
Маленькую утилиту fan-control, которой я пользуюсь, я выложил в свой репозиторий Time Pi — вдруг пригодится. Скважность 75% на деле работала чуть лучше… но вентилятор на таких оборотах шумел ровно настолько, чтобы раздражать меня, пока я тестировал всё это у себя на столе… так что пусть будет 50%.
Зарядка батарейки встроенных часов RTC#
Если у вас установлена официальная батарейка ML2020 для RTC Raspberry Pi, настройте цепь её зарядки на плате:
- Подключите батарейку Pi 5 RTC к соответствующему разъёму.
- Добавьте
dtparam=rtc_bbat_vchg=3000000в/boot/firmware/config.txtи перезагрузите Pi. - (Напряжение батарейки можно посмотреть командой
vcgencmd pmic_read_adc BATT_V.)
На стабильность хода часов Pi батарейка RTC не влияет, но помогает держать время в пределах секунды-двух от реального, если Pi какое-то время был выключен. (Только не оставляйте его выключенным на месяцы!)
Изоляция прерываний PPS на отдельном ядре#
Мне хотелось проверить, поможет ли выделение целого ядра процессора исключительно под обработку прерываний PPS. Помогло. Одна из причин, по которой серьёзное оборудование точного времени строят на ПЛИС (или используют PHC, как в специализированных сетевых картах для синхронизации времени), — детерминированная обработка PPS. Если пользоваться PPS-подсистемой ядра Linux, то даже в лучшем случае приходится мириться с задержками в сотни наносекунд, пока прерывание дождётся обработки!
Чтобы дать PPS шанс, можно отдать его обработку одному выделенному ядру. В моём случае я хотел, чтобы весь PPS обрабатывался на CPU3 (четвёртом ядре).
- Отредактируйте
/boot/firmware/cmdline.txt, добавив к параметрамisolcpus=3. - Перезагрузитесь:
sudo reboot. - Создайте
/usr/local/sbin/pin-pps-irq.sh(и сделайтеchmod +x) с таким содержимым:
#!/bin/sh
irq=$(awk '/pps@/ {sub(":","",$1); print $1}' /proc/interrupts)
[ -n "$irq" ] && echo 3 > /proc/irq/$irq/smp_affinity_list
- Создайте
/etc/systemd/system/pin-pps-irq.serviceс таким содержимым:
[Unit]
Description=Pin PPS GPIO IRQ to isolated CPU3
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/pin-pps-irq.sh
[Install]
WantedBy=multi-user.target
- Запустите сервис и включите его автозапуск:
systemctl enable --now pin-pps-irq.service.
Проверьте, что всё работает:
$ watch grep pps /proc/interrupts
# You should see the fourth column incrementing once per second:
166: 860 0 0 34 pinctrl-rp1 5 Edge pps@5.-1
Счётчик в четвёртом столбце должен увеличиваться раз в секунду. Понаблюдайте за статистикой chrony час–шесть и оцените, насколько это помогает. В моём случае эффект был заметным, но не революционным.
Мониторинг и физический вывод времени#
Для мониторинга своего Pi (к этому моменту я уже называл его «TrueTime Pi» — в честь корпуса, в котором он жил) я с помощью Claude сделал chrony-dashboard.

Дашборд мониторинга chrony для TrueTime Pi
Быстро накидать дашборд — лёгкая победа для ИИ-инструментов. Я никогда не любил возиться с фронтенд-библиотеками для графиков, а писать скрипт, который периодически выдёргивает данные из вывода консольной команды и складывает их в SQLite, — тоже не то, что меня вдохновляет.
Пусть этим занимается ИИ, а я сосредоточусь на интересном — подаче и производительности!
Веб-интерфейс — это одно, но Claude помог мне ещё и сопоставить выводы GPIO со старинным ЖК-дисплеем Densitron на передней панели TrueTime, и в итоге получился проект densitron-lcd.

Соединения выполнены накруткой (wire wrap)
На фото в начале поста ЖК-дисплей работает при уверенном приёме GPS. Для соединений я применил технику, которую раньше никогда не пробовал, — монтаж накруткой (wire wrap).
Вместе с Pi и GNSS HAT по высоте внутри корпуса TrueTime не хватало места для дюпоновских разъёмов — да и не то чтобы я их любил! Я уже подумывал припаять провода прямо к выводам GPIO, но, изучая другие низкопрофильные способы подключения к стандартным квадратным штырькам, узнал про накрутку.
Оказывается, в этом процессе на углах штырька образуется что-то вроде сварки, и получается газонепроницаемое электрическое соединение с меньшим сопротивлением, чем у пайки! Если соединения переживут поездку в Чикаго, возможно, буду почаще использовать этот способ для прототипирования.
Я пользовался вот этим инструментом для накрутки и вот этим проводом 30 AWG для накрутки.
Протоколы Time и Daytime#
В качестве пасхалки — раз уж этот TrueTime Pi будет работать на VCF Midwest — я решил поднять в сети ещё и сервисы Time и Daytime по RFC 867/868.
Настраивается это совсем просто:
sudo apt install xinetd
sudo systemctl enable xinetd
sudo nano /etc/xinetd.d/time
-> service time tcp set disable to "no"
-> service time udp set disable to "no"
sudo nano /etc/xinetd.d/daytime
-> service daytime tcp set disable to "no"
-> service daytime udp set disable to "no"
sudo systemctl restart xinetd
Историю этих двух протоколов, появившихся ещё до NTP, я разбирал в предыдущем посте.
AppleTalk / netatalk для сервера Timelord#
Ещё одна забавная демонстрация — старые Маки, получающие время по AppleTalk! Это возможно благодаря timelord, который легко запустить в составе netatalk — рядом с другими сервисами, например файловым сервером AppleShare для винтажных Маков.
На моём Pi с Pi OS всё настроилось просто. Сначала включаем поддержку AppleTalk в ядре Linux:
# Enable AppleTalk protocol and ensure it's loaded.
sudo modprobe appletalk && lsmod | grep appletalk
# Add AppleTalk protocol to boot modules.
echo appletalk | sudo tee /etc/modules-load.d/appletalk.conf
# Create a folder for netatalk.
mkdir -p ~/netatalk/afpshare
Затем, если у вас установлен Docker, создайте файл Docker Compose (nano ~/netatalk/compose.yml) с таким содержимым:
services:
netatalk:
image: netatalk/netatalk:latest
container_name: netatalk
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
volumes:
- ./afpshare:/mnt/afpshare
- /var/run/dbus:/var/run/dbus
environment:
AFP_USER: myname
AFP_PASS: mypass
AFP_GROUP: afpusers
ATALKD_INTERFACE: eth0
INSECURE_AUTH: "1"
AFP_EXTMAP: "1"
DISABLE_TIMEMACHINE: "1"
SERVER_NAME: "TrueTime Pi"
TZ: America/Chicago
Перейдите в созданный каталог netatalk и запустите контейнер:
cd ~/netatalk && docker compose up -d
Любой классический Мак в сети теперь сможет получать время через Tardis (это плагин для Chooser, он срабатывает один раз при загрузке) или подключаться к серверу TrueTime Pi по AppleShare.
Проверить, что всё работает, можно командой docker exec [имя контейнера] nbplkup. Она должна вернуть список сервисов, среди которых будут AFPServer и TimeLord.
Итоги#
В конце концов, я доволен тем, что получилось. Pi надёжно закреплён так, что вернуть в корпус оригинальное железо TrueTime можно, просто переткнув два разъёма внутри.

Готовое крепление TrueTime Pi, вид сверху сзади
Если захотите повторить эту сборку — вперёд. Мне она настолько нравится, что я, возможно, разовью её дальше. Есть ещё две вещи, которые я хотел бы сделать:
- развести плату с SMT-разъёмом GPIO, чтобы сохранить низкий профиль, но менять начинку было ещё проще (накрутка — это весело, но переделывать её утомительно);
- заставить работать клавиатуру TrueTime, чтобы переключать на дисплее экраны со временем, данными GPS, chrony и сетевой информацией.
Чем больше я измеряю, тем лучше понимаю, что работа с точным временем порой бывает медленной и скучной. Вносишь изменение — и потом нужно минимум 6–12 часов, чтобы всё устоялось и можно было снова нормально измерять! А большинство измерений вообще имеют смысл только на интервале не меньше суток — из-за влияния окружающей среды! ↩︎