GPSレシーバーでStratum 1なNTPサーバ

GPS 1
こちらは送料込みUS$11.5で購入したU-BLOX 7020チップなGPSレシーバー。USB接続だけど内部的にはシリアルポートにGPSが接続されている扱い。左側のGPSユニット本体部分の裏面は3Mの超強力両面テープになっていて迂闊に貼ってしまうと位置を変えようにも剥がすのが大変。貼る場所は空が開けて見える外壁か窓ガラス。防水ではないので濡れないところ。GPSレシーバーから生えてるUSBケーブルは1メートル程度の長さしか無いのでPCからGPSレシーバー設置場所まで距離があるならどこのご家庭にも余っているUSBの延長ケーブルを使う。ただし、ブースター無しなら一応全部込みで5メートルまで。

GPS 2
まずは動作確認のためにWindows PCに接続してみる。
デバイスマネージャを開き「ポート(COMとLPT)」を見る。「USBシリアルデバイス(COM3)」として正常に認識されている。Windowsのセンサー&ロケーションAPI用のドライバを使っていれば「ポート(COMとLPT)」ではなく「センサー」の方に表示される筈。

GPS 3
仮想シリアルポートの通信内容を見てもドカドカ表示されるだけで何がなんだかなのでublocksのu-center for Windowsというアプリを使用して状態を見る。
左上の方のコネクタアイコン(画像の赤い枠で囲われた部分)を押してGPSレシーバーのデバイスを選択して接続状態にする。今回のレシーバーはCOM3 としてWindowsに認識されているのでそれを選択。 初めてu-centerを使うなら画面左半分が空白なのでビューから表示したい画面を選択。上の画像では見えるべき衛星の位置が表示されている。実際に衛星を掴んでいる受信レベルの状況は画面中央右の少し右下の縦棒グラフの部分で確認できる。上の画像は横800pxに縮めているので窮屈で文字が重なりあってよくわからないがウインドウサイズを大きくすれば分かりやすくなる。

GPS 4
フルHDの画面だとこんな感じ。この画面では左半分にGPSユニットからの受信メッセージが流れている。

とりあえず正常に作動していることが確認できたのでWindowsでの利用は終わり。

FreeBSDのPCにGPSユニットを接続する。どのようなデバイス名で認識されるかわからないので接続前と接続後に ls /dev で確認。一応、シリアルポートはttyまたはcuaで始まる名前の筈。

foobar@example:~ % ls /dev
acpi            ctty            hpet0           pass2           ttyU0.lock      ttyve
ad4             cuaU0           io              pass3           ttyv0           ttyvf
ad4p1           cuaU0.init      kbd0            pass4           ttyv1           ufssuspend
ad4p2           cuaU0.lock      kbd1            pci             ttyv2           ugen0.1
ada0            da0             kbdmux0         pf              ttyv3           ugen0.2
ada0p1          da1             klog            pts             ttyv4           ugen0.3
ada0p2          da2             kmem            random          ttyv5           ugen0.4
apm             da3             log             reroot          ttyv6           ugen0.5
apmctl          devctl          mdctl           sndstat         ttyv7           ugen1.1
atkbd0          devctl2         mem             stderr          ttyv8           ugen1.2
audit           devstat         midistat        stdin           ttyv9           urandom
bpf             fd              nfslock         stdout          ttyva           usb
bpf0            fido            null            sysmouse        ttyvb           usbctl
console         geom.ctl        pass0           ttyU0           ttyvc           xpt0
consolectl      gptid           pass1           ttyU0.init      ttyvd           zero
foobar@example:~ % cu -l /dev/cuaU0 -s 9600
Connected
$GPRMC,054138.00,A,nnnn.nnnnn,N,nnnnn.nnnnn,E,0.015,,110616,,,A*7C
$GPVTG,,T,,M,0.015,N,0.027,K,A*22
$GPGGA,054138.00,nnnn.nnnnn,N,nnnnn.nnnnn,E,1,09,0.90,114.7,M,34.9,M,,*58
$GPGSA,A,3,08,27,16,07,11,26,01,30,10,,,,1.82,0.90,1.58*06
$GPGSV,4,1,13,01,27,200,35,04,03,115,,07,42,300,27,08,67,324,29*7C
$GPGSV,4,2,13,09,04,246,,10,15,082,47,11,44,221,30,16,45,095,40*70
$GPGSV,4,3,13,18,05,050,09,23,00,220,,26,19,113,27,27,52,037,48*7D
$GPGSV,4,4,13,30,19,317,40*41
$GPGLL,nnnn.nnnnn,N,nnnnn.nnnnn,E,054138.00,A,A*60
$GPTXT,01,01,02,u-blox ag - www.u-blox.com*50
$GPTXT,01,01,02,HW  UBX-G70xx   00070000 *77
$GPTXT,01,01,02,ROM CORE 1.00 (59842) Jun 27 2012 17:43:52*59
$GPTXT,01,01,02,PROTVER 14.00*1E
$GPTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*20
$GPTXT,01,01,02,ANTSTATUS=OK*3B

上の例ではデバイス名がcuaU0 (ttyU0)として認識されていることがわかる。
購入したGPSユニットは通信速度が9600bpsのみ対応なので cu -l /dev/cuaU0 -s 9600を実行するとGPSユニットからのメッセージが表示される。終了させたいなら ~. を入力するが、コンソールからではなくSSHでFreeBSDにリモート接続して操作している場合はそのセッションが切れる場合があるので[~]の後に[Ctrl]+[D]

これでFreeBSDでも正常に認識されて使えることが確認できた。

GPSモジュールが複数の通信速度に対応している場合は、GPSモジュール側とPC側の両方で利用可能な範囲でなるべく速い速度に合わせる。 uBlox系のチップを搭載したGPSモジュールでシリアルポートcuaU0で57600 bpsにするなら echo "B5 62 06 00 14 00 01 00 00 00 D0 08 00 00 00 C2 01 00 07 00 03 00 00 00 00 00 C0 7E" | xxd -r -p > /dev/cuaU0
GPSモジュールが設定を記憶するタイプならとりあえずこれで良いが、設定を記憶する能力がないモジュールもあるのでFreeBSDのシステム起動時にコマンドをGPSモジュールに送信するようにする。

/etc/rc.local (新規作成または追加等) 中のバイナリコマンドはublox(互換)専用なので注意
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/sh

#9600 bps : '\xB5\x62\x06\x09\x0D\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x17\x2F\xAE'
#19200 bps : '\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xD0\x08\x00\x00\x00\x4B\x00\x00\x07\x00\x03\x00\x00\x00\x00\x00\x48\x57'
#38400 bps : '\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xD0\x08\x00\x00\x00\x96\x00\x00\x07\x00\x03\x00\x00\x00\x00\x00\x93\x90'
#57600 bps : '\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xD0\x08\x00\x00\x00\xE1\x00\x00\x07\x00\x03\x00\x00\x00\x00\x00\xDE\xC9'
#115200 bps : '\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xD0\x08\x00\x00\x00\xC2\x01\x00\x07\x00\x03\x00\x00\x00\x00\x00\xC0\x7E'

echo 'B5 62 06 00 14 00 01 00 00 00 D0 08 00 00 00 C2 01 00 07 00 03 00 00 00 00 00 C0 7E' | xxd -r -p > /dev/cuaU0
/etc/rc.localは実効可のパーミッションであること。
なお、ntpd実行中にはntpdがシリアルポートを専有していて制御させて貰えないので9行目の前後でntpdを停める・開始するという処理が要る筈。

今回購入したGPSレシーバーはNMEAだけで1PPS信号は取れないので精度は低いけど逆に使うのはとても簡単。

ntpdでNMEA GPS Clockを利用するにはデバイス名がgps0などである必要があるので
/etc/devfs.confに下の1行を追記。

link cuaU0 gps0
#または
link ttyU0 gps0

cua*とtty*のどちらが良いかというのは今現在は不明。かなり以前は主に受信用ならtty、発信アリならcuaって住み分けがあったみたいに記憶しているが、実際はどちらも同じに使える。GPSモジュールを繋ぐという使用方法なら受信onlyなのでttyかなとも思うけど・・

変更したらdevfsを再起動。

# /etc/rc.d/devfs restart

/etc/ntp.confに下の2行を追記。

1
2
server 127.127.20.0 mode 17 minpoll 4 prefer
fudge 127.127.20.0 refid GPS flag1 0

127.127.20.0はIPアドレスではない。
127.127.の後の20はリファレンス クロック ドライバのタイプ。ドライバのタイプが何を指すかは本来はこちらを見るのだと思うけどこちらの方が各タイプで何を設定して良いかがわかりやすいかも。今回は Generic NMEA GPS Receiverを使うので127.127.20.x。

127.127.20.xのmodeはNMEAセンテンスと通信速度の指定(合算)。
速度
4800bps: 0
9600bps: 16
19200bps: 32
38400bps: 48
57600bps: 64
115200bps: 80

NMEAセンテンス
$GPRMC: 1
$GPGGA: 2
$GPGLL: 4
$GPZDA/$GPZDG: 8

9600bpsで$GPRMCなら16 + 1 = 17となる。mode 17という指定。
115200bpsで$GPRMCなら80 + 1 = 81となる。mode 81という指定。

ntpdを再起動する。

# service ntpd restart

動作確認 (ntpdの起動・再起動後暫くしてから)

# ntpq -c clockvar
associd=0 status=0000 no events, clk_unspec,
device="NMEA GPS Clock",
timecode="$GPRMC,134444.00,A,nnnn.nnnnn,N,nnnnn.nnnnn,E,0.010,,251116,,,A*70",
poll=92, noreply=0, badformat=0, baddata=0, stratum=0, refid=GPS,
flags=0

NMEA GPS Clockで取得していること。timecodeでRMCセンテンスを使っていることなどが解る。badformatやbaddataが0なので、つまり異常なくGPSで時刻が取れている筈。

# ntpq -p -ccv -crv -ckern -csysinfo
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+ntp1.jst.mfeed. 133.243.236.17   2 u   39   64  177   18.088   69.996   2.953
+ntp2.jst.mfeed. 133.243.236.17   2 u   35   64  177   14.488   70.424   4.932
*GPS_NMEA(0)     .GPS.            0 l   11   16  377    0.000   -6.629   9.759
associd=0 status=0000 no events, clk_unspec,
device="NMEA GPS Clock",
timecode="$GPRMC,141030.00,A,nnnn.nnnnn,N,nnnnn.nnnnn,E,0.012,,110616,,,A*73",
poll=27, noreply=0, badformat=0, baddata=0, stratum=0, refid=GPS,
flags=0
associd=0 status=0415 leap_none, sync_uhf_radio, 1 event, clock_sync,
version="ntpd 4.2.8p8-a (1)", processor="amd64",
system="FreeBSD/10.3-RELEASE-p4", leap=00, stratum=1, precision=-21,
rootdelay=0.000, rootdisp=17.219, refid=GPS,
reftime=db069a4b.c21f8f7c  Sat, Jun 11 2016 23:10:19.758,
clock=db069a56.e84ebeaf  Sat, Jun 11 2016 23:10:30.907, peer=7628, tc=4,
mintc=3, offset=-6.628976, frequency=19.274, sys_jitter=9.759054,
clk_jitter=6.493, clk_wander=0.055
associd=0 status=0415 leap_none, sync_uhf_radio, 1 event, clock_sync,
pll offset:            -6.34964
pll frequency:         19.2738
maximum error:         0.022554
estimated error:       0.006493
kernel status:         pll nano
pll time constant:     4
precision:             1e-06
frequency tolerance:   495.911
pps frequency:         0
pps stability:         0
pps jitter:            0
calibration interval   0
calibration cycles:    0
jitter exceeded:       0
stability exceeded:    0
calibration errors:    0
associd=0 status=0415 leap_none, sync_uhf_radio, 1 event, clock_sync,
system peer:        GPS_NMEA(0)
system peer mode:   client
leap indicator:     00
stratum:            1
log2 precision:     -21
root delay:         0.000
root dispersion:    17.219
reference ID:       GPS
reference time:     db069a4b.c21f8f7c  Sat, Jun 11 2016 23:10:19.758
system jitter:      9.759054
clock jitter:       6.493
clock wander:       0.055
broadcast delay:    -50.000
symm. auth. delay:  0.000

結果の最初の方のmfeedはGPS以外に時刻を取っている公開NTPサーバ

# ntptime
ntp_gettime() returns code 0 (OK)
  time db069b74.9f79207c  Sat, Jun 11 2016 23:15:16.622, (.622942655),
  maximum error 8699 us, estimated error 2551 us, TAI offset 0
ntp_adjtime() returns code 0 (OK)
  modes 0x0 (),
  offset -1336.472 us, frequency 19.099 ppm, interval 1 s,
  maximum error 8699 us, estimated error 2551 us,
  status 0x2001 (PLL,NANO),
  time constant 4, precision 0.001 us, tolerance 496 ppm,

offset
ちなみに精度はこんな程度。
1PPS無しはやはり酷いね。オフセットがμ秒ではなく数ミリ秒のレベル。それでも多くは±5ミリ秒に収まるみたい。

私のNTPサーバは今Stratum幾つなの?

基本的には同期している時刻ソースのStratumの値 + 1
ntpq -c rl とか ntpq -c rv で出てくる中にStratum ○という値があるのがそれ。
似たようなオプションでntpq -c cl とか ntpq -c cv は同期している時刻ソースの値なので間違えないようにする。GPSならStratum 0になってるので時刻ソースだとわかる筈だけど、外部NTPサーバに同期していると例えばStratum 1を参照しているのを見て自分のNTPサーバがStratum 1だと勘違いすることがある。
NTPサーバのStratumの値は同期している時刻ソースによって変動するので例えばGPSに同期していればStratum 1だけどGPSから時刻が取れなくなってStratum 2のNTPサーバに同期するとStratum 3になる。

ということで?、わずか千円ちょっとでなんちゃってStratum 1なNTPサーバの完成。
(精度が普通にStratum 1に求められるレベルより3桁ほどゆるいので「なんちゃって」。しかし、精度は低くてもGPSから取ってるので一応Stratum 1ではある。)
そして、精度が低いといってもpool.ntp.orgで下手なNTPサーバに当たるとかネットワーク的に遠いNTPサーバから時刻を取るよりは遥かにマシ。時刻精度を求められないウェブサーバとかメールサーバ用程度ならこんなもんでも十分すぎるほど。

でも、いつか1PPS取れるものを何か買う。

関連記事: