用 Raspberry Pi + GPS 模組建立 stratum 1 的 NTP server

2017/05/15 更新:因為某些跟閏秒有關的原因,我把 NTP server 從 ntp 換成 chrony 了,之後補上怎麼切換到 chrony。


緣由:區網內有個裝置不能讓它連上外網,而手邊剛好有個正在吃灰的樹莓派,所以想利用 RasPi 讓那些裝置有辦法校時。這個 RasPi 後來也加入了 NTP Pool。由於類似的文章已經很多(我在最後有列出參考的連結),故本文主要紀錄我遇到的問題及解決方法。

本文假設你會設定 Raspbian、會使用 Google 找資源補足本文缺少的內容。


材料

  • Raspberry Pi 1 Model B (1 Model B+, 2 Model B 也行,3 Model B 因為內建藍牙模組的關係在設定 UART 的部分有些差異)
  • GPS 模組,我選擇 u-blox NEO-6M,再加購外接天線
  • (optional) RTC,我買了個專給樹莓派用的 DS3231,使用的時候整個插下去就好了,非常緊湊
  • (optional) switching regulator (e.g., MP2359,重點是要能把電壓從 5V 轉 3.3V) 用來替換 Raspberry Pi 1 Model B 的 linear regulator,可以省點電。Raspberry Pi 1 Model B+ 以及之後的型號都是使用 switching regulator,所以只有 B 需要改裝
  • 相關背景知識,推薦閱讀使用 Orange Pi One 和 GPS 搭建1级时间源,對 NTP 的架構以及為什麼需要 PPS 訊號等都有完整的解答,而且是中文寫的。英文的資源推薦Building a Raspberry-Pi Stratum-1 NTP Server

東西準備好,開始動工


硬體部分

  • (only for Raspberry Pi 1 Model B) 參照教學換上 switching regulator
  • RTC 安裝,參考 Adding a Real Time Clock to your Raspberry Pi
  • GPS 接線,如果用跟我同款GPS模組的話記得不要把 VCC 接到 5V,要接到 3V3,除非你買的模組類似 Adafruit 出品的這款會自動降壓。將 PPS 訊號接到 GPIO18 上 (樹莓派那兩排針的第12根腳)

軟體部分

OS 使用 Raspbian Jessie Lite,初始設定部分請參考別的教學。

RTC 安裝部分,參考 Adding a Real Time Clock to your Raspberry Pi

更新 & 安裝等下會用到的 package,並更新 firmware

sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get install build-essential wget rpi-update ntp vim gpsd gpsd-clients python-gps pps-tools && sudo rpi-update
reboot

關閉 DHCP client 有關 NTP 的部分,或著直接設定靜態 IP,參考 How to give your Raspberry Pi a Static IP Address — UPDATE

移除不必要的服務

# 用 "apt-get remove triggerhappy" 會移除 raspi-config,所以用 insserv
sudo insserv -r triggerhappy
sudo insserv -r alsa-utils
sudo apt-get remove cifs-utils samba-common

讓 UART 可以給 GPS 傳資料,參考Using UART instead of USB | Adafruit Ultimate GPS on the Raspberry Pi | Adafruit Learning System

UART 設定完並重開機之後,先確定 GPS 模組抓的到訊號

sudo gpsd /dev/ttyAMA0 -n -F /var/run/gpsd.sock
cgps -s
# cgps 內等一段時間,確認可以取得 3D Fix

接著啟用 pps-gpio,編輯 /boot/config.txt,新增一行

# 我試過 GPIO18 以外的 pin (GPIO22),可是拿不到 PPS 輸出,稍微注意一下
dtoverlay=pps-gpio,gpiopin=18

再編輯 /etc/modules 並新增一行

pps-gpio

然後重開機

sudo reboot
/boot/config.txt
/etc/modules

Raspbian 官方的 ntp 包不支援 PPS 訊號,所以必須再 compile 一個可以用 PPS 訊號的 ntp,參考這篇教學,基本上就是抓檔案下來下個指令然後等一陣子就好了。

有了自己 compile 的 ntp 之後,接下來要設定使用 GPS 作為 reference。我先啟用 PPS clock discipline & shared memory driver,確認 NTP 可以用 GPS 訊號取得時間。編輯 /etc/ntp.conf 並增加下列內容

server 127.127.22.0 minpoll 4 maxpoll 4
fudge 127.127.22.0
server 127.127.28.0 minpoll 4 maxpoll 4 iburst
fudge 127.127.28.0 flag1 1

執行 /etc/init.d/ntp restart 重新啟動 ntp 後用 ntpq -p 看一下能不能取得時間, 可以看到 jitter 偏大。

ntp + gpsd (shared memory)

在我的案例中,因為 jitter 實在太大了,所以在確定 NTP 可以從 GPS 拿到時間跟 PPS 後,我就改成使用 Generic NMEA GPS Receiver 取得 GPS & PPS 訊號。新增一個文字檔 /etc/udev/rules.d/09-pps.rules,並加入這些內容

# "ttyAMA0" 這段字可能會隨著型號的不同 (e.g., raspi 3) 而需要更改
KERNEL=="ttyAMA0", SYMLINK+="gps0"
KERNEL=="pps0", OWNER="root", GROUP="tty", MODE="0777", SYMLINK+="gpspps0"

重開機之後,確定 gpsd 沒有啟動

sudo systemctl stop gpsd
sudo systemctl disable gpsd
sudo killall gpsd

編輯 /etc/ntp.conf 並把剛剛加入的內容刪掉,改加入這些內容 (取自 https://bart.motd.be/node/79)

server 127.127.20.0 mode 17 minpoll 4 maxpoll 4
fudge 127.127.20.0 time2 0.115
fudge 127.127.20.0 flag1 1
fudge 127.127.20.0 flag2 0
fudge 127.127.20.0 flag3 0

執行systemctl restart ntp 之後過一段時間用 ntpq -p 看一下能不能取得時間,可以看到 GPS_NMEA(0) 的 jitter 蠻小的 (PPS 是截圖的時候忘了刪掉 ntp.conf 的設定造成的)

來處理閏秒。編輯 /etc/ntp.conf 並在新的一行加入 leapfile /etc/leap-seconds.list。下載並且更新 /etc/leap-seconds.list 的部分,我寫了一個 script 去處理,使用 root 權限將下列內容儲存為/usr/local/bin/update_leapseconds_list.sh

#!/bin/bash
# refresh leap-seconds.list
# author: phutidus <phutidus at gmail dot com>
LEAP_SEC_LIST='/etc/leap-seconds.list'
old_md5=`md5sum $LEAP_SEC_LIST`
wget -O $LEAP_SEC_LIST ftp://time.nist.gov/pub/leap-seconds.list -q
new_md5=`md5sum $LEAP_SEC_LIST`
if [ ! -f $LEAP_SEC_LIST ]; then
exit
fi
if [ "$new_md5" != "$old_md5" ]; then
echo leap-seconds.list has changed, restart ntp server
systemctl restart ntp
else
echo leap-seconds.list did not change
fi

執行 sudo chmod +x /usr/local/bin/update_leapseconds_list.sh之後,再以 root 權限加下列內容加入到 /etc/crontab 就可以自動更新/etc/leap-seconds.list

# get leap-seconds.list every 2 months
5 3 * */2 * root /usr/local/bin/update_leapseconds_list.sh >/dev/null 2>&1

NTP 到這裡已經完成設定了,但還可以做一些設定降低 jitter。編輯 /etc/rc.local,在 exit 0之前加入

echo "performance" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
setserial /dev/ttyAMA0 low_latency

降低 jitter 的優化:編輯 /boot/config.txt 並新增一行

disable_pvt=1

降低 jitter 的優化:編輯 /boot/cmdline.txt 在第一行後面接上 (nohz前面記得打上空格與前面隔開)

nohz=off smsc95xx.turbo_mode=0

加入 NTP Pool

懶得打字,需要分享的請留言,我收到留言後會更新上來,包括防火牆設定以及 logging 的設定等等。