A/Dコンバータでも作ろうかと秋月でセンサーを覗いているとGPS受信機キット 1PPS出力付き 「みちびき」対応 [AE-GYSFDMAXB]なるGPSが2,200円、こっちに目を奪われました。
- 出力データ形式
- NMEA0183V3.01準拠
- 電源電圧
- DC5V(3.8V~12V)
- 入出力信号レベル
- C-MOSロジック(3.3V)
- UART通信速度
- 9600bps(デフォルト)、4800~115200bps
- 1PPS出力
- 精度±10ns C-MOSロジック(3.3V)レベル,パルス幅:100mS(アクティブLow)
いー感じです。
GPS PPSで検索してみるとGPSをRaspberry PiにつなげてRaspbian上でntpサーバを動かすのが世界的に大流行していた模様です。IoT時代っぽくてよいですね。
インターネットといえば固定電話でダイアルアップという時代にGarmin II PlusというハンディGPSにACアダプタやcarrozzeriaのGPSアンテナをつなげるコネクタを適当に作ったものを思い出して引っ張り出してきて、歴代のハンディGPSと並べてみました。
インターネットへの常時接続が当たり前となった時にはGPSをntpサーバの時刻ソースにするという必要がなくなっていたので記憶はすっかり飛んでいます。
信号線をつなぐだけなので電子工作とはいえそうに無いですが、方針を決めます。
- 定期メンテが必要な機械は増やさない。セキュリティパッチの提供期限を気にする機械を増やさない。
- 作る箱はNMEAとPPSを外だしするところまでで、ntpサーバやOSは追加しない。
- そのまま既存のサーバにつながるようにする。
- USBかD-sub9ピン(232C)
- 設定は可能な限りデフォルトを使う。
- 設定ファイルだけですまない改変は後で面倒だしなんだったのかわからなくなるので実験以外ではしない。GPSにコマンド送るような機種依存設定など、設定ファイルにも必要以上の追加をしない。
- がんばらない。
- 興味のないところは手を抜きまくります。
テスト用PCにはFreeBSD 10.3-RELEASEを使うことにして、そのntpdは4.2.8p8です。情報を集めておきます。
- Generic NMEA GPS Receiver:
- time1(secs): PPSのずれの調整
- time2(secs): NMEA sentenceとのずれの調整
- flag1: 1でPPSを使う
- flag2: 1でPPSのclear(true -> false)のエッジを使う
- flag3: 1でkernelのPPS diciplineを使う
- PPS Clock Discipline:
- PPSAPI使用 (<sys/timepps.h>)
- (ntpd.confの)pps設定コマンドはもう無いよ。
- PPSは普通シリアルのDCD(D-sub9ピン1番,D-sub25ピン8番)で、あとパラレルポートドライバでACK(D-sub25ピン10番)をサポートしているものもあるよ。
- jitterを減らすためにメディアンフィルタを使ってて、一般的に minpoll 4に設定すればkernel PPS diciplineよりも良いよ。
- preferなピアが「survivors」にいる、か、「survivors」がいなくてtos minsane 0 のときに動作するよ。
- time1(secs): PPSのずれの調整
- flag2: 1でPPSのclear(true -> false)のエッジを使う
- flag3: 1でkernelのPPS diciplineを使う
- UART(シリアル)ドライバ:
- UART_PPS_ON_CTSカーネルオプションを付けてなければUART_PPS_DCDがデフォルト。
- パラレルポートドライバ:
- 入力は10番ピン、14番ピンにエコーを出力。(アンフェノールのメスコネクタをはんだ付けしてレトロ感を出す?いや出さない)
- PPS関連の情報はPPS_SYNCオプション付きkernelでね
- USB serial PPS support
- PPS Synchronization ドライバの更新に追いついていないけれども、参考になります。
大分古い情報としては
のようなものがありました。
というわけでさらっと見たところではUSBシリアルでPPSを扱えるのはFreeBSD 11以降の模様なのでPPSはレベルコンバータを足してD-sub9ピンのシリアルで出すか、GPSのPPS出力の3.3VをそのままD-sub25ピンのパラレル5Vに出すことにします。kernel PPS diciplineのためのカーネルのリビルドは実験のためにパーツ注文して待っている間に一度だけやり、後でGENERICに戻してしまうという方向で行きます。minpoll 4の22番ドライバはkernel PPSより良いというマニュアルの言葉を信じて。そしてntpdは時間と共にゆっくり落ち着いていく性質があるので、違いがわからなくならないように実験としては最初に一番精確になる構成を作り、だんだんとゆるい構成にしていくことにします。
必要そうなものをGPSといっしょに注文です。RS-232C用のレベルコンバータには3V・3.3V・5V系-RS232レベル変換基板というコネクタに挟めて便利そうなものがありますが、PPSを使うには死んでいる1chを引き出さないといけないので老眼にやさしいDIPのADM3202ANというのを使うことにします。232CからADM3202ANへの入力はプルダウン抵抗内蔵だそうで、使わない入力はほっといて良いので何気に便利です。電源はGPSキットとADM3201ANだけで、USBから取るので超余裕です。このGPSはキット内で3端子レギュレータにて3.3Vを作っています。ADM3202ANも3.3Vでも5Vでも動作するので便利です。D-sub9ピンメスとシェルも用意し、適当に予備を買っても5,000円しないですね。
古い古いPentium 4のノートPC
にFreeBSD 10.3-RELEASE 32ビットがインストールされてます。
待ってる間にkernelをリビルドしておきます。なおリビルドはkernel PPS disciplineを使う時(マニュアルによれば精度悪くなるはず)だけ必要な作業です。ntpdのPPS Clock Disciplineを使うだけの場合はカーネルをリビルドする必要はありません。
時刻のずれなんかどうでも良くて、時間刻みだけ精確ならば良いという方はPPSの刻みを信じるだけなので、インターネット接続不要、time1 time2の較正も不要で、 127.127.1.1 127.127.20.x 127.127.22.x の設定だけして終了です。ntp.confは次のようなものになります。
driver 20のPPSサポートを利用すればdriver 22すら不要かもしれません。電波時計との差を目視と直感で測り、time1やtime2としてみるのも楽しいのではないでしょうか。
今回はインターネットから隔離されてGPS単独となっても、良い時計であるようにPPS(driver 22)のオフセット(time1)を較正するので、較正の間だけは多くの公開サーバに助けてもらいます。なお一般の方がNICTのntpサーバにmaxpoll 6などを指定している例を見かけましたが、1時間平均20回を超えるアクセスには事前承認が必要です。今回、公開サーバはすべてにmaxpoll 10(デフォルト)以上 minpollに8以上を指定して気長に較正していきます。NICTのサーバは使いません。
ntp.confは次のようなものになります。
脱線追記しますがpoolはserverを処理した後にmaxclockまで追加していくので、2018/3/16現在3つのA RRで運用されているntp.jst.mfeed.ad.jpと、1つのA RRをもつntp[123].jst.mfeed.ad.jpを例として、tos,serverおよびpoolが以下だけのようになっていた場合、
例1(server, A RR x 3)
例2(server, A RR x 1)
例3(pool, A RR x 3)
例4(pool, A RR x 1)
ntpq -pしたときに 例1,例2,例4では4台、例3ではmfeed2台だけで計5台がソースとして表示されるようになります。またserverでは.INIT.と初期状態であることが明示されますが、poolではSoliciting pool server …とログされるだけでntpqの表示上ではわからないため、エラーに気づきにくくなります。いずれ改善されていくものと思いますが、この辺の特性を理解しないでやみくもにserverをpoolに変えれば良いという話ではないのでご注意ください。形式的に変えると相手方のトラフィックを慮って少ない台数の設定にされていたものが急に増えてしまうかもしれません。老舗の福岡大学のNTPサーバが停止するということで、個々は極小でも相手方にはすごい量届いていて帯域圧迫する可能性があるということを思い知らされます。問題児だけのせいにして安心しないで気にとめておきたいものです。
なお最近(でもないけど)のntpqはPTR RRを引いてからAまたはAAAA RRに戻らない場合(FCrDNSではない場合)、PTR RRはIPアドレスの横に表示されるように改善されています。そのため逆引きだけではなくて表引きのネームサーバが死んでいたり設定が狂っているような場合も極端に表示が遅くなります。ntpq -pnではすぐ表示されるけれどもという場合はDNSからみです。
アドレスやホスト名がとぎれてしまう場合は ntpq -pnw のようにwオプションが実装されています。
他にもうるう秒の指定や、PLLの周期(driftファイル)、ログ等の出力をさせる設定をします。ログの設定例です。
そしてGPSのドライバですが、Generic NMEA GPS Receiverによればu=0とすることに決めると/dev/gps0が使われるようです。PPS Clock Disciplineも同様にu=0と自分で決めれば/dev/pps0が使われるようです。/etc/devfs.confに以下を追加しておきます。あとで気付くことになりますが、パラレルのPPSは自力で/dev/ppsをmknod()し、 /dev/pps0 はデフォルトでの起動時に最初にパラレルに取られるため、両方のPPSを試したいときはシリアルはu=1として /dev/gps1 /dev/pps1 のようにそろえたほうがわかりやすいのかもしれません。
で/dev/gps0と/dev/pps0のsymlinkができることを確認します。
本題のNMEAドライバとPPSドライバの設定です。NMEAドライバ(driver 20)は秒単位での時間だけ合っていれば細かい精度はどうでもよいので、ドライバに採用されるNMEA sentenceのデータタイプがころころ変わることもないでしょうから9600bpsの指定だけします。ntp.confに以下を追加します。
GPSはつながっていませんが、いったん起動してdriftファイルを作らせてしまいます。
翌日にはパーツが届いたのでさっさと信号線つないでしまいます。ADM3202ANのデータシート通りにコンデンサ配置して、GPSからのTXとPPSを入力にするだけです。一方PC -> GPSの向きではUSBを電源とするのでADM3202AN側は5Vですが、GPSのRXは74HC04につながっていることが説明書からわかるので、10kΩでADM3202ANとつないで寄生ダイオードへの電流を制限するだけで済ませるか、9600bpsとのろいし適当に信号を分圧するか、パーツ数増えるけど3.3Vを作ってADM3202ANのVccにする、あたりでしょうか。今は使わないのでがんばりません、つなぎません。
最後に電源を繋いで40秒ほど、ハンディGPSは衛星がつかめなくて動作しない室内でいきなり秋月GPSのLEDが点滅し始めます。
各点の波形です。


とっととつないで実行です。
この時点でのkldstatはパラレルのPPSを使用していないのでこんな感じです。
clockstatsを見ると、ハンディGPSではせいぜい3,4個の衛星がつかめたら良いような室内の場所で、なんと
8個の衛星つかんでいてちょっとびっくりです。感度が良いのか、ハンディGPSはあえて無視するような微弱電波でも使っているというだけの話なのかこれだけではちょっとわかりません。あとでスマホとハンディGPSを並べて確認してみます。この中にみちびきがいるのかどうかもお楽しみです。
そのまま3日ほど放置したところ、以下のようになりました。みごとに動作しているようです。




PPS(driver 22)にmsec単位でのoffsetの変動がありますが、PPS(driver 22)をnoselectにしているので他のNTPサーバがソースとなり、そのうち除外されていないサーバの値から算出されているためです。較正するため、MuninのデータをCSVにします。
何でも良いですがちょうど使っていたのでOctaveで平均を計算します。
そのまま符号を反転させて、ntpqが出力するデータはmsec、ntp.confでの time1 time2 の指定はsecなことに注意しながらPPS(driver 22)のtime1とNMEAセンテンスのオフセットtime2を指定します。noselectも外してしばらく動作させます。


PPSのNTP stateはずっと7でpps.peerとして動作しているのでjitterが急激に小さくなっています。引き続き較正します。とここで気付きますが、NMEAドライバ(driver 20)についてはPPSのずれとNMEAセンテンスのずれが混ざってしまったことになります。またNMEAドライバのPPSサポートの立ち位置がよくわからないので、以降PPSはPPS Clock Discipline(driver 22)だけを利用することにします。ここではNMEAドライバ(driver 20)のPPSサポートを取りやめ、PPSドライバ(driver 22)のtime1だけ較正します。
そしてここで別途注文していたD-sub25ピンオスとシェルが届いたため、パラレルのACKに3.3VのPPSをそのまま繋ぎ、パラレルのPPSドライバをkldloadします。
まずパラレルのPPSはロード時に/dev/ppsをmknodするので、/dev/pps0をパラレルのPPSに譲るため、/etc/devfs.confを書き換えます。
パラレルのPPSを読み込みます。
再起動時にも読み込まれるようにします。
ntp.confをシリアルPPS & パラレルPPS体制にします。PPSの波形は先の通りパラレルI/Fにとっては反転しているので立下りを検知させるため、ntp.confは次のようになります。
ところがflag2 1でエラーが出ます。
パラレルのPPSは立下りの検出は現状ではできないようです。もちろんインバータ通すとかがんばらずに、パルス幅がきっちりであることを信じてパルスの終わりである立ち上がりで検出します。



前回同様にMuninのデータからパラレルのPPSの平均を計算してtime1を更新します。
2日ほど放置で以下のようになりました。



ここで間違えてntpdを再起動してしまったので、今回はそれが含まれないように1時間前までの47時間分のデータを取得します。
Octaveをスプレッドシート扱いばかりでは申し訳ないので、MathWorks®の通りにfftしてみます。平均については前回間違えてmsec単位とsec単位をそのまま足しこんだ分もこれで修正されるはずです。


だからなんだというグラフゲットとなりました。もうしません。ntp.confは次のようになって仕上がりです。
シリアルPPSとパラレルPPSのtime1の差
は、秋月GPSの説明書のパルス幅100msとほぼ同じとなっています。立ち上がり時間、パルス幅の誤差、パラレルとシリアルのハードウェアの遅延の差、パラレルPPSドライバとシリアルPPSドライバの遅延の差、が足しこまれると上の値に近くなるのだと予想されます。
さて、すでにoffsetはμsec単位でのjitterになり、平均にはnsecなどという文字も見えてきました。自己満足の世界もほどほどにということで較正は終了、利用させていただいた公開サーバに感謝しつつntpサーバを自LAN内用のサーバだけを参照するようにして運用開始です。
そして kernel PPS discipline やら、ネットから隔離してみるやら、GPSを外してみるやらをぼちぼちやっていくのです。
ちょっと enable calibrate だけ試してみましたが、較正した後では結局 time1 time2 は20 driver, 22 driver x 2 すべて初期値(有効数字3桁にて)のまま変わらないようなので、最初の較正のときには便利なのか別の機械につなげたときにでも試してみる項目としておきます。
なお較正中pps.peerになるのはシリアルのPPS(127.127.22.1)だけで、パラレルのPPS(127.127.22.0)が選ばれることは無く常にfalsetickでした。恐らく温度などで変化するであろうパルス幅のせいなのか、他の理由なのか、誰かインバータ入れてどうなるか確認してみてください。
2017/3/6 やもり様よりコメントいただき、取り急ぎたどってみた参考情報を以下に追加します。
- filter vs. ithread
- UART(シリアル): filter登録に失敗したらithread登録
- ppbus(パラレル): ithread登録
- GPIO: filter(gpiopps_ifltr)とithread(gpiopps_ithrd)を同時登録
- UART(シリアル): pps_captureと同時にpps_event(filter)
- ppbus(パラレル): pps_captureと同時にpps_event(ihandler)
- GPIO: filterでpps_captureし、ihandlerでpps_event
- UARTのbus_setup_intrは指定が無いのでx86 ACPI機ならばnexus_setup_intr(下記参照)
- ppbusのbus_setup_intrはppbus_setup_intr
- GPIOのbus_setup_intrはbus_generic_setup_intr(x86 ACPI機ならばBUS_SETUP_INTRが呼ばれ、そこからnexus_setup_intrが呼ばれる)
x86のACPI搭載機の場合(他のアーキテクチャでも似たように探せると思います)。
- x86のデフォルトのbus_setup_intrはnexus_setup_intr
- x86のnexus_setup_intrはintr_add_handlerが呼ばれ、さらにintr_event_add_handlerが呼ばれる
- INTR_FILTER
その他
コメント確認ありがとうございました。
私が運用していた、秋月のモジュールは9月にちょっといじったところ、不安定になってしまい、まったくntpdで同期しなくなってしまったので、止めてしまいました。UBLOX NEO-6Mなモジュールが手に入ったので現在はこれをつかっています。このモジュールはすこぶる安定していて、スパイクがでても1ms以下でjitter/offsetとも平均2usくらいで運用できています。
やもり様、コメントありがとうございます。
太陽フレアにやられた…なんてことはないですね。こちらの秋月GPSは場所の関係で2ヶ月ほど前に止めてしまいましたが、ログ上は快調に動いていたようです。他に何をやりたかったのか忘れかけてますが、また再度場所ができたらぼちぼち続きをやろうと思います。しかしNMEAやらPPS出力が数千円で手に入って精度も良いということで、ありがたいことです。こんだけ安いといろいろ集めてGPIOかシリアルの複数ポートで、PPSソースに選ばれている時間でレースとかしたら楽しそうですね。情報どうもありがとうございました。
こちらの記事大変参考になりました。ありがとうございました。
uartとppbusのppsサポートなのですが、ドライバーコードを見るとuartは割り込みhandlerで処理していて、ppbusはfilterの方で処理しています。また12-CURRENTで入ったgpioppsは両方に分けて入っていたりします。gpioppsのコードにはコメントが入っているのですが、理解できていません。。。
やもり様、するどいコメントありがとうございます。
参考のために、該当部分の情報を本文の末尾に追加しました。
GPIOドライバでは、いつ起こったかの記録(pps_capture)を軽快にfilterでやってしかも他の事はしないで戻り、一方captureしたことの登録(pps_event)はgpiopps_ioctl用にちゃんと排他制御しつつihandlerで行う(あるいは排他制御が必須なのでihandlerに書くしかなかった)という合理的な実装に見えます。精度的には GPIO ≒ UART(シリアル) > ppbus(パラレル) となってもおかしくなさそうです。
歴史を追うと分かるのだと思いますが、UARTのPPSはfilterが実装されていないアーキテクチャがあった時代のなごり、ppbusのPPSはfilter対応をしていないまま、ということなのかもしれません。