Raspberry PI Zero WでGPSのPPS信号に同期したntpサーバの構築

ありふれた題材と思いますが、勉強を兼ねて実施したので、備忘

使用機器およびソフトウェア

本体: RaspberryPI Zero W
GPSモジュール: 秋月電子通商製 AE-GYSFDMAXB
OS/ディストリビューション: raspbian lite(stretch)

(* 使ったのがたまたまZero Wだっただけで、2,3,Zeroでもほぼ同じで動くはず。)

前提条件

とりあえずラズパイ自体は動作していて、インターネットに接続できていることが前提です。

パッケージ導入と周辺ソフトウェア設定

ntp, ntpdate,fake-hwclockパッケージを導入

fake-hwclockはntpの設定変更後、起動時に時計がリセットされてしまうため、対策として追加

$ sudo apt install ntp ntpdate fake-hwclock

知らぬ間に時計を修正されると気持ちがわるいので、systemdの時刻同期処理を停止

$ sudo timedatectl set-ntp false

DHCP配布されたIPアドレスを使用している場合,dhcpcdが用意した設定ファイルでntpdが起動するため
dhcpcd.confを変更して、この挙動を阻止

#/etc/dhcpcd.conf

option ntp_servers
↓
# option ntp_servers (コメントアウト)

GPSモジュールの設定

ラズパイ側の設定
UART(シリアルポート)とPPSドライバの有効化
/boot/config.txt に以下を追記

enable_uart=1
dtoverlay=pps-gpio,gpiopin=18,assert_falling_edge=true

enable_uart=1でUARTを有効化
dtoverlayほにゃららでpps信号をGPIO18で扱えるようにします.
また、このGPSモジュールはPPS信号を立ち下がりエッジで送信するため、assert_falling_edgeオプションを指定しています.
(デフォは立ち上がりエッジで検出)

また、bluetoothを使用しないのであれば,以下を追加することで、
ラズパイに搭載されている2つのUARTのうち、機能の良い方を使用することができます.

dtoverlay=pi3-diable-bt

このまま接続すると、シリアルポートからとんでもない値が入力されてOSが悲しいことになるので
/boot/cmdline.txtを変更してシリアルコンソールを無効化します。
具体的には

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 ....

となっているところの console=serial0,115200 を削除します。

ntpがGPSおよびPPS信号を読み込む際に指定するデバイスファイル名が/dev/gpsX, /dev/gpsppsXなため、
udevにルールを追加して、OS起動時に自動的に該当ファイルが生成されるようにします.
/etc/udev/rules.d に以下の内容でファイルを追加

/etc/udev/rules.d/99-pps.rules

KERNEL=="ttyS0",SYMLINK+="gps0"
KERNEL=="pps0",OWNER="root",GROUP="dialout",MODE="0660",SYMLINK+="gpspps0"

bluetoothを無効にした場合は,ttyS0の部分をttyAMA0に変更してください.

追加したデバイスファイルをntpが読めるようにグループdialoutにユーザntpを追加

$ sudo usermod -aG dialout ntp

ここまで設定が終わったら,ラズパイの電源を落として、GPSモジュールとラズパイを配線する
電源ブチ切りするとお亡くなりになる可能性があるので注意

GPSモジュール,ラズパイ間の配線は

モジュール - ラズパイ
5V - P4(5V)
GND - P6(GND)
TXD - P10(GPIO15 UART_RXD)
RXD - P8(GPIO14 UART_TXD)
1PPS - P12(GPIO18)

ラズパイの5VはP2からも出てますが、P4から取ると隙間なく5ピン揃って良いんじゃないでしょか。
配線が終わったらラズパイの電源を入れる.

GPSのPPS信号に同期したNTPDの設定

参考にしたサイトにはPPS信号を扱えるようにntp自体をソースファイルからビルドするとありましたが、
今はパッケージのままで大丈夫そうです。

/etc/ntp.conf に以下を追記

server 127.127.20.0 mode 16 minpoll 4 manpoll 4
fudge 127.127.20.0 flag1 1 flag3 0 flag4 0 time1 0.0 time2 0.488 refid GPS

server 127.127.20.0 … : Generic NMEA GPS Receiver を使用
mode 16 : シリアルポートのボーレート指定 9600bps

fudge 127.127.20.0 … : Generic NMEA GPS Receiver の設定
flag1 1 : PPS信号を扱う
flag3 0 : ntpモード/カーネルモード 0でntpモード, 1にするにはlinuxカーネルのビルドが必要なので、今は0
time1 0.0 : PPS信号の遅延時間(ミリ秒)を指定, とりあえず0.0
time2 0.488 : シリアルポートで受信される電文の遅延時間(ミリ秒)を指定, 0.0だと頻繁に時間飛びが発生したのでとりあえずこのぐらい。要チューニング箇所

設定の詳細はntpの公式を参照のこと.

ntpを一旦停止し,ドリフトファイルを削除,時計合わせを行なって,ntpを起動する.

$ sudo systemctl stop ntp
$ sudo rm -f /var/lib/ntp/ntp.drift
$ sudo ntpdate ntp.nict.jp
$ sudo systemctl start ntp

以上で、問題がなければ、ntpqコマンドで以下のような出力が出てくると思います。
(下記はカーネルモードを有効にしてるので、若干違いますが。)
GPSモジュールから受信した電文で直近採用されたものがtimecode=の箇所にでてくるので、
動作が怪しい時はこの辺り見るとよいかと。

$ watch -n 16 ntpq -crv -ccv -pn

associd=0 status=041d leap_none, sync_uhf_radio, 1 event, kern,
version="ntpd 4.2.8p10@1.3728-o Sat Oct  7 14:29:08 UTC 2017 (1)",
processor="armv6l", system="Linux/4.9.59-pps+", leap=00, stratum=1,
precision=-19, rootdelay=0.000, rootdisp=62.729, refid=GPS,
reftime=dda64fc0.471cca3c  Fri, Nov  3 2017 11:15:28.277,
clock=dda64fc0.d335cd8a  Fri, Nov  3 2017 11:15:28.825, peer=38248, tc=4,
mintc=3, offset=0.000019, frequency=-10.019, sys_jitter=0.001907,
clk_jitter=0.003, clk_wander=0.000
associd=0 status=0000 no events, clk_unspec,
device="NMEA GPS Clock",
timecode="$GPGGA,021528.000,3442.5578,N,13527.6561,E,1,7,1.20,15.5,M,34.2,M,,*68",
poll=7, noreply=0, badformat=0, baddata=0, fudgetime2=488.000, stratum=0,
refid=GPS, flags=5
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
o127.127.20.0    .GPS.            0 l    -   16  177    0.000    0.000   0.002

カーネルpps対応

TODO:

電源ブチ切り対策(リードオンリーなrootfs)

TODO:

参考サイト