使い古されたネタだとは思うが、意外と情報は少なく躓いたところも多かったので記録に残しておきます。
GPS受信機
秋月電気で売っているGPS受信機キット 1PPS出力付き 「みちびき」3機受信対応を使いました。GPSモジュールに太陽誘電のGYSFDMAXBを使い、受信機にはMedia Tek のMT3339が使われているようです。この受信機でGPSのL1電波を受信して、NMEAのフォーマットでシリアル信号にして出してくれます。
電池ホルダーとピンヘッダだけはんだ付けしました。ピンヘッダーは5V,GND,RXD,TXD,1PPSの順に並んでいます。RXD,TXDをシリアルに、1PPSは3次元測位をすると1秒毎にパルスが発信されるので、Rasberry Piの適当なGPIOにつなぎます。
電池はGPSをコールドスタートさせると時間がかかるので、衛星のデータ(アルマナックとエフェメリス)を保持しておくためのものです。なくても動きます。
Raspberry Piとの接続
Raspberry Pi のGPIOは2,4,6,8,10番ピンがうまい具合に、5V,GND,UART_TXD0,UART_RXD0,GPIO18と並んでいます。今回のGPSモジュールを順番につなげて、GPIO18を1PPSの入力に使えば、なんの工夫もなくフラットケーブルでちょうど繋げれます。
Raspberry Pi の準備
Raspberry Piのシリアルポート
Raspberry Pi (3とzero WなどのBluetoothがあるRasPi)のシリアルポート用に使う半導体は、PL011とmini UARTという回路で実装されています。Raspbian OSでは、シリアルポート(ttyS0)はデフォルトでオフになっていて使えないが内部的にはminiUARTにつながっています。一方PL011はBluetooth用に使っています。
/dev/serial0 (primary UART) -> /dev/ttyS0 -> mini UART (通常は有効化されていない)
/dev/serial1 (secondary UART) -> /dev/ttyAMA0 -> PL011 -> bluetooth
ちなみにAMAとはarmのAMBAというアーキテクチャの略でAdvanced Microcontroller Bus Architectureのことらしい。この実装がPL011ということ。
mini UARTはGPUとコアクロックを共有しています。GPUのクロックは動的に変化するため、mini UARTをシリアルポートに使うとボーレートが変化してしまい、これでは外のデバイスからデータを取得するには不都合があります。一方PL011はGPUのクロックの影響を受けません。前述のとおり、PL011はBluetoothで使用中のため、次の3つのいずれかの方法を取る必要があります。
a) Bluetoothの運用を止めPL011をシリアルポート用に使う。
b) Bluetoothはmini UARTを使い、PL011をシリアルポート用に使う。
c) Bluetoothはそのままにして、mini UARTのクロックを固定して使う。
最後のc)の方法はmini UARTをシリアルポートとして使うには制限が多いので今後の事を考えて使わないことにします。取れる方法はa)かb)の二択になります。
Raspbianでは/dev/serial0はLinuxのコンソールに割り当てらていて、これを別の目的で使うには/dev/serial0とコンソールを切り離さなければなりません。
Rassoberry Pi の設定
コンソールとシリアルの切断
Linuxのコンソールがシリアルに割り当てられているので、これを切り離します。(実際にはttyサービスが動いていないので、シリアル経由でログインはできない設定になっている。)切り離す方法として、1)raspi-configコマンドでやる方法と、2)マニュアルで/boot/cmdline.txtを編集する方法法があります。これをやることによって、シリアルでのコンソール接続が不能になりますが、通常はsshなどでログインしていると思うのでなんの問題もないと思います。
1)raspi-configコマンドを管理者権限で立ち上げて、Interfacing Options、Serialと進みNoを選択する。
2)/boot/cmdline.txtを編集する場合は適当なエディタで開いて、console=serial0,115200
を探し出してこれを削除する。他の記述はそのまま残しておく。
どちらの方法でも有効化するには再起動が必要となります。次に先に述べたGPSをシリアルにつなぐいずれかの方法を実行します。今回はb)を選びました。
a) Bluetoothを停止する方法
/boot/configを変更する必要があります。dtoverlay=pi3-disable-bt
を追加します。
再起動後これによりBluetoothが停止されます。一方、GPIOの6,8番ピンが自動的に/dev/serial0 -> /dev/ttyAMA0 として再割り当てされます。その後にモデムの初期化とUARTとの通信の無効化のためにsudo systemctl disable hciuart
をします。
b) Bluetoothをmini UARTで使う方法
もう一つの方法が、mini UARTを使ってBluetoothを動かす方法です。これも/boot/config.txtを編集して、dtoverlay=pi3-miniuart-bt
を追加します。更にクロックを固定するためにcore_freq=250
の記述も追加しておきます。
再起動後miniUARTとPL011が入れ替わり、GPIOの6,8番ピンが/dev/serial0 -> /dev/ttyAMA0として再割り当てされます。
余談ですがc)のmini UARTをserial0として使うときは、GPIOのUARTを有効化(enable_uart=1
)することでmini UARTのクロックは自動的に固定化されるため、core_freq=250
の記述は必要ありません。
b)の操作によってシリアルとBluetoothの関係は以下のようになります。今回のGPSモジュールは上段のserial0につながることになります。
/dev/serial0 (primary UART) -> /dev/ttyAMA0 -> PL011 (GPIOのUART)
/dev/serial1 (secondary UART) -> /dev/ttyS0 -> mini UART -> bluetooth
PPS信号の受信
PPS信号の受信は前述したとおりGPIO18(ピン番号12)で入力する。後でわかったことですが、PPSのありなしで、NTPの安定度(offsetやJitter)にほとんど影響はありませんでした。
設定は/boot/configにdtoverlay=pps-gpio,gpiopin=18,assert_falling_edge=true
を追加するのと、/etc/modulesにpps-gpio
を追加してppsのカーネルモジュールを読み込んでおきます。assert_falling_edge=true
はアクティブLowで、つまりパルスは立ち下がりのときに発信されるので、その設定です。(参考)
NTPの設定
関連ツールのインストール
シリアル経由でGPSのNMEAフォーマットもPPSの信号も取り出せるようになったので、このままNTPのソースに使ってもいいのですが、NTP以外でも使えるようにしたり(直接シリアルから読んでしまうとNTPがシリアルを専有してしまい、時刻以外の位置情報などが全く取れなくなってしまう。)、NMEAのフォーマットを読みやすい形に変換してもらうために、gpsd,gpsd-clientsのインストールを行います。
gpsdはシリアルに吐き出されるGPSからの信号をtcpの2947番ポートを通して使えるようにしてくれ、また、NTPにおいては127.127.28.uのシェアードメモリ経由で時刻の情報を提供してもらえるようになります。
gps-clientsはgpsdで配信してくれる情報をモニターしてくれるgpsmonコマンドなどが提供されます。
gpsdは/etc/default/gpsdに設定ファイルがあり、今回はGPSデータのソースとしてDEVICES="/dev/serial0 /dev/pps0"
の設定を追加しました。また、GPSD_OPTIONS="-n"
としてシェアードメモリを介してNTPを同期させるための設定を入れておきます(参考)。
gpsdはインストールしただけでは自動起動してくれないようなので、sudo systemctl enable gpsd
などで自動起動の設定をしておきます。ちなみにこれを忘れるとntpq -p
などで見てもいつまで経ってもGPSがdelay,offset,jitterが0.000のままで同期しません。gpsmonなどで確認するとgpsdが動いてしまうので、問題ないように見えてしまう。しばらくこれで悩みました。
remote refid st t when poll reach delay offset jitter
==============================================================================
SHM(2) .GPS. 0 l - 16 0 0.000 0.000 0.000
xPPS(0) .PPS. 0 l 14 16 377 0.000 -2.582 0.459
ntp.nict.jp .POOL. 16 p - 64 0 0.000 0.000 0.002
-ntp-a2.nict.go. .NICT. 1 u 9 64 3 9.904 0.021 1.391
シェアードメモリの様子はipcs -m
コマンドで見ることができます。
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x4e545030 0 root 600 80 1
0x4e545031 32769 root 600 80 1
0x4e545032 65538 root 666 80 1
0x4e545033 98307 root 666 80 1
0x4e545034 131076 root 666 80 1
0x4e545035 163845 root 666 80 1
0x4e545036 196614 root 666 80 1
0x4e545037 229383 root 666 80 1
0x4e545030から始まるメモリーがgpsdのシェアードメモリで、最後の一桁がユニット番号(u)で、ここでは7まで見えています。0,1はownerがrootでpermsが600なので、root権限がないと読めません。今回はユニット2が使えそうなのでこれを使うことになります。
NTPの設定とインストール
パッケージのNTPをインストールします。今回のバージョンはntpd 4.2.8p12@1.3728-o (1)
です。
設定は/etc/ntp.confですが、追加で記述したのは以下になります。
server 127.127.28.2 minpoll 4 maxpoll 4 prefer
fudge 127.127.28.2 time1 0.000 refid GPS
server 127.127.22.0 minpoll 4 maxpoll 4
fudge 127.127.22.0 refid PPS
1行目と2行目がGPSの設定です。gpsdのシェアードメモリから読む設定で、ユニット番号は2です。このとき参照するアドレスが127.127.28.2になります。ntpのドキュメントが参考になります。gpsdは127.127.46.0でも時刻情報を出しているようですが、こちらは試していません。
minpollとmaxpollの値はgpsdの推奨のままです。この値だと2^4で16秒間隔で読みに行きます。
preferは必須のようです。
fudgeのtime1は手動でoffsetを設定するときの設定ですが、今回は0です。
3行目と4行目がPPSの設定です。/dev/pps0で読めるデータですが、ntpでは127.127.22.0がそれに当たるようです。gpsdでもserverのモードを指定すれば読めるようですが、今回は直接127.127.22.0で読みました。
あと、他のNTPの設定でもやるアクセスコントロールやインターネットのサーバ指定などはお好きにどうぞです。
結果確認
起動させたあとしばらくするとGPSが衛星を補足して、reachが377になる頃(3分ほど)経つと、GPSで時刻同期が実現します。ntpq -p
コマンド確認します。
remote refid st t when poll reach delay offset jitter
==============================================================================
*SHM(2) .GPS. 0 l - 16 377 0.000 0.004 0.002
oPPS(0) .PPS. 0 l 13 16 377 0.000 0.002 0.002
ntp.nict.jp .POOL. 16 p - 64 0 0.000 0.000 0.002
+ntp-a2.nict.go. .NICT. 1 u 28 64 377 6.401 0.987 2.171
+ntp-a3.nict.go. .NICT. 1 u 27 64 377 8.572 1.191 1.557
-ntp-b2.nict.go. .NICT. 1 u 29 64 377 10.725 2.783 1.308
-ntp-b3.nict.go. .NICT. 1 u 18 64 377 10.519 2.685 1.352
-ntp-a2.nict.go. .NICT. 1 u 22 64 377 8.623 2.046 1.910
-ntp-a3.nict.go. .NICT. 1 u 18 64 377 11.298 2.499 2.275
1行目のSHMがGPSの状態です。先頭に*がついているので、同期サーバになっています。2行目のPPSの先頭にoがついているので、参照されていることがわかります。
また、ntpq -c rvコマンドでも状態がわかります。
associd=0 status=0118 leap_none, sync_pps, 1 event, no_sys_peer,
version="ntpd 4.2.8p12@1.3728-o (1)", processor="armv6l",
system="Linux/4.19.57+", leap=00, stratum=1, precision=-19,
rootdelay=0.000, rootdisp=1.180, refid=PPS,
reftime=e0dfee76.435da06f Mon, Jul ** 2019 17:12:38.263,
clock=e0dfee82.d4c99980 Mon, Jul ** 2019 17:12:50.831, peer=5249, tc=4,
mintc=3, offset=0.002763, frequency=-7.570, sys_jitter=0.001907,
clk_jitter=0.002, clk_wander=0.001, tai=37, leapsec=201701010000,
expire=201912280000
1行目でsync_ppsとなっているので、PPSで同期していることがわかります。インターネット経由のNTPだとここがsync_ntpとなります。
offsetもjitterもmsec表示なので一桁usec(micro second)の精度が出えているようです。インターネット経由はNICTのサーバを使用していますが、だいたい3桁のオーダで正確なようです。
真ん中のギザギザしているのがインターネットのNTP(ntp.mict.jp) を参照してきたときの、DelayとOffsetとJitterのグラフ。おおよそ数msecの値です。前後は、0のように見えますが、GPSを参照していたときのグラフで、数μsecです。ほとんど0に見えます。