FreeBSDの時間

# VDSOのこと

armのlibcにVDSO like gettimeofdayのコミットがあって、古いarmでは使えなかったので、ちょっと調べてみました。

VDSOって言うのはCPUの機能をつかってgettimeofdayの処理を軽量にするテクニックのようです。

[gettimeofday(2) は VDSO によりユーザー空間で実行される](http://d.hatena.ne.jp/yohei-a/20140913/1410628229)

https://www.kib.kiev.ua/kib/kievbsd-sharedpage.pdf

# 時間の仕組み

FreeBSDのカーネルで主要な時間系の仕組みは二つあります。タイマーから見ると

* et_register()で登録される仕組み
* tc_init()で登録される仕組み

前者は[eventtimers](http://www.freebsd.org/cgi/man.cgi?query=eventtimers&apropos=0&sektion=0&manpath=FreeBSD+11-current&arch=default&format=html)とよばれる仕組みでハードウエア割り込みを使って管理する仕組みで、後者は[timecounters](http://www.freebsd.org/cgi/man.cgi?query=timecounters&apropos=0&sektion=4&manpath=FreeBSD+11-current&format=html)と呼ばれタイマー値を返す関数登録してカーネル本体から呼び出す（ポーリング）して使っています。

eventtimersでet_flagsがET_FLAGS_PERIODIC | ET_FLAGS_ONESHOTの場合は下記のような処理になります。

![freebsd_eventtimers.png](https://qiita-image-store.s3.amazonaws.com/0/104066/5954f8c4-2b59-d8da-29af-60aad141562e.png)

両方の仕組みともシステムが持ついくつかのタイマーからベストのものを使うようになっています。

eventtimersはFreeBSD 9の途中くらいにそれまでの仕組み(hardclock)と入れ替えたようです。

![FreeBSD-Time.png](https://qiita-image-store.s3.amazonaws.com/0/104066/5b13dd69-6e24-7bdc-6fc7-424019a4eb16.png)

eventtimerとtimecounterは別々のタイマーで実装する事も出来るし、mipsの用に一つのカウンターで実装する事も出来るようだ。

2016/05現在、armはconf/files.armにkern_clocksource.cがなくそれぞれのsocのfilesにある。これはcavium/cns11xxなどeventtimerに対応できていないプラットフォームがあるためで、これらのためのカーネルオプションNO_EVENTTIMERSもある。

### SOCのTimer

MIPS4KアーキテクチャではTimerも含めたアーキテクチャになっている。なのでSOC毎のTimerコードは必要がなく、すべて同じsys/mips/mips内のコードで実行される。

ARMについてはCortex-A9(armv7)になるまでSOC毎に独自のTimerを利用していた実装にする必要があった。FreeBSDではCortex-A9以降ではmpcore_timerかgeneric_timerを使うようになったが、これ以前のアーキテクチャのサポートの場合は独自に実装する必要がある。arm64はgeneric_timerに統一されたようだ。

私がportsしたRT1310(armv5t)はtc/etとも自作したのだが、ntpdateで一日一回強制的に時間を合わせているが、日に決まって12分45秒遅れる。なにかの定数を間違えている可能性が高い。

一日のずれを確認してdtsのclockを調整してみました。

一日の平均のずれが+759.2309037秒

dtsのクロックは75M(75000000)としていた。

計算するとクロックを74346686が適当そう。dtsを修正してビルドして確認したところ日々のずれが7秒くらいになりました。

Microserver

```
kern.timecounter.fast_gettime: 1
kern.timecounter.tick: 1
kern.timecounter.choice: TSC-low(800) ACPI-fast(900) HPET(950) i8254(0) dummy(-1000000)
kern.timecounter.hardware: HPET
kern.timecounter.stepwarnings: 0
kern.timecounter.tc.i8254.mask: 65535
kern.timecounter.tc.i8254.counter: 14435
kern.timecounter.tc.i8254.frequency: 1193182
kern.timecounter.tc.i8254.quality: 0
kern.timecounter.tc.HPET.mask: 4294967295
kern.timecounter.tc.HPET.counter: 3898805239
kern.timecounter.tc.HPET.frequency: 14318180
kern.timecounter.tc.HPET.quality: 950
kern.timecounter.tc.ACPI-fast.mask: 4294967295
kern.timecounter.tc.ACPI-fast.counter: 782034235
kern.timecounter.tc.ACPI-fast.frequency: 3579545
kern.timecounter.tc.ACPI-fast.quality: 900
kern.timecounter.tc.TSC-low.mask: 4294967295
kern.timecounter.tc.TSC-low.counter: 458035797
kern.timecounter.tc.TSC-low.frequency: 1098193546
kern.timecounter.tc.TSC-low.quality: 800
kern.timecounter.tsc_shift: 1
kern.timecounter.smp_tsc_adjust: 0
kern.timecounter.smp_tsc: 1
kern.timecounter.invariant_tsc: 1
```

元祖amd64なマシン

```
kern.timecounter.tsc_shift: 1
kern.timecounter.smp_tsc_adjust: 0
kern.timecounter.smp_tsc: 0
kern.timecounter.invariant_tsc: 0
kern.timecounter.fast_gettime: 1
kern.timecounter.tick: 1
kern.timecounter.choice: TSC(800) ACPI-fast(900) i8254(0) dummy(-1000000)
kern.timecounter.hardware: ACPI-fast
kern.timecounter.alloweddeviation: 5
kern.timecounter.stepwarnings: 0
kern.timecounter.tc.TSC.quality: 800
kern.timecounter.tc.TSC.frequency: 800000000
kern.timecounter.tc.TSC.counter: 991710125
kern.timecounter.tc.TSC.mask: 4294967295
kern.timecounter.tc.ACPI-fast.quality: 900
kern.timecounter.tc.ACPI-fast.frequency: 3579545
kern.timecounter.tc.ACPI-fast.counter: 14183103
kern.timecounter.tc.ACPI-fast.mask: 16777215
kern.timecounter.tc.i8254.quality: 0
kern.timecounter.tc.i8254.frequency: 1193182
kern.timecounter.tc.i8254.counter: 3219
kern.timecounter.tc.i8254.mask: 65535
```

MIPSなマシン

```
kern.timecounter.tc.MIPS32.quality: 800
kern.timecounter.tc.MIPS32.frequency: 192000000
kern.timecounter.tc.MIPS32.counter: 964069785
kern.timecounter.tc.MIPS32.mask: 4294967295
kern.timecounter.stepwarnings: 0
kern.timecounter.alloweddeviation: 5
kern.timecounter.hardware: MIPS32
kern.timecounter.choice: MIPS32(800) dummy(-1000000)
kern.timecounter.tick: 1
kern.timecounter.fast_gettime: 1
```

# ppsの話

注意：秋月のモジュールは結局の所うまく使えてなくて後から手に入れたubloxのモジュールはちゃんと使えています。

30年近く前にLUNAをやっていた頃、同僚にGPSで3つの衛星で位置が特定できて4つの衛星で時間が取れるという話を聞いて、ずっと長い事試してみたかったのですが、GPSモジュールが高価でなかなか手を出せなかったのですが、PPS出力がある[GPSモジュール](http://akizukidenshi.com/catalog/g/gK-09991/)が秋月電子でこれまでで一番安い価格(2200円)で発売されたので入手して試してみる事にしました。

通常GPSモジュールはPPSという秒の開始を伝える信号とNMEAセンテンス(以下NMEA)という時間情報を伝える信号を持っています。NMEAはシリアルでの通信になります。

PPSのタイプは3種類あります。

![PPS.png](https://qiita-image-store.s3.amazonaws.com/0/104066/a5ed72a9-a7be-c3ff-c116-09c3199e8d94.png)

正エッジ、負エッジ、両エッジ、になります。パラメータはuart ppsのものです。

このモジュールのppsは10nSの精度があるそうです。0.00000001秒ってことですね。

このモジュールのNMEAとPPSは信号はこのようになっています。黄色い方がPPSでピンクがNMEAです。NMEAはデフォルトの9600bpsです。

![PPS_TX1.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/b9093a9d-7f61-88a7-436e-47e83de45158.jpeg)

FreeBSDでPPS対応のカーネルはPPS_SYNCオプションを設定してカーネルをビルドする必要があります。カーネルがPPS_SYNC入りかどうかは以下のように確認できます。

```
# sysctl -a | grep pps_sync
kern.features.pps_sync: 1
```

PPS_SYNCなカーネルでは以下のようなsysctlが確認できます。

```
# sysctl kern.ntp_pll
kern.ntp_pll.pps_shiftmax: 8
kern.ntp_pll.pps_shift: 8
kern.ntp_pll.time_monitor: 25170801
kern.ntp_pll.pps_freq: 322981943312384
kern.ntp_pll.time_freq: 322981943312384
```

|name|help|
|:--|:--|
|pps_shiftmax|Max interval duration (sec) (shift)|
|pps_shift|Interval duration (sec) (shift)|
|time_monitor|Last time offset scaled (ns)|
|pps_freq|Scaled frequency offset (ns/sec)|
|time_freq|Frequency offset (ns/sec)|

FreeBSDカーネルでppsを処理するためのカーネルの関数はkern/kern_tc.cにあり、ヘッダーはsys/timepps.hにあります。関数は以下が用意されています。

```
void pps_capture(struct pps_state *pps);
void pps_event(struct pps_state *pps, int event);
void pps_init(struct pps_state *pps);
void pps_init_abi(struct pps_state *pps);
int pps_ioctl(unsigned long cmd, caddr_t data, struct pps_state *pps);
```

pps対応のドライバは、pps_initしてデバイスからのpps信号の割り込みでpps_capture, pps_eventを呼ぶ事でppsな時間同期ができるようです。またntpdからの制御のためにioctlを実装してpps_ioctlを呼ぶようにしておく必要もあるようです。

UNIXシステムでのPPSの扱いについては[RFC2783](https://tools.ietf.org/html/rfc2783)に書かれています。

12-CURRENT(2017/2現在)でppsをサポートするドライバコードは以下にあります。

ppbus/pps.c
sio/sio.c
gpio/gpiopps.c
uart/uart_core.c
usb/serial/usb_serial.c

2017/3現在のCURRENTのソースを見てちょっと疑問があります。FreeBSDでは割り込み処理は、割り込みから直接呼ばれるhandlerとその後に処理されるfilterに分かれています。uartはppsの処理をhandlerで実装してあって、ppbusではfilterに実装してあります。そしてgpioでは半分に分けてそれぞれに実装しています。どれが正しいのでしょうか。。。gpioのコードには分けた理由が書いてあるようなのですが、理解できていません。

![GPIOFig1.png](https://qiita-image-store.s3.amazonaws.com/0/104066/f5983a78-d2c0-da15-9554-ff74d08da164.png)


gpio/gpiopps.cは12-CURRENTに始めて入ったコードでarmのimxで開発されたようです。これをsys/mipsで唯一fdt対応している、mediatekのRT3050で試してみました。このFDTの設定はdtsのgpioの中に以下を追加します。

```
        pps {
                compatible = "pps-gpio";
                gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
                status = "okay";
                assert-falling-edge;
        };
```

このGPSモジュールではPPSはHIからLOに変わったところが秒の始まりなのでassert-falling-edgeを設定してあります。実はこれだけではダメで以下を実行する必要がありました。gpioはsoc毎に仕様が違うのでやっかいです。

```
gpioctl -c 12 IN II
```

そもそもassert-falling-edgeで立ち下がりを指定しているのになぜに、Inverseしなければならないのかも謎です。

半年くらいして、上の確認のために、いじっていたところ不安定になり、no_sys_peerで止まってしまうようになったので、このモジュールでの運用あきらめました。多分いじっていたのとは関係ないとおもうのですが。

```
Dec 16 09:08:50  ntpd[86]: 0.0.0.0 0413 03 spike_detect -0.506881 s
Dec 16 09:09:06  ntpd[86]: 0.0.0.0 0418 08 no_sys_peer
```

peerstatsを見ると530.117秒(09:08:50)に突然リファレンスクロックとの差が大きくなっています。

```
58103 434.087 127.127.20.0 971a -0.003300836 0.000000000 0.000302690 0.008438224
58103 450.117 127.127.20.0 971a -0.002393262 0.000000000 0.000285463 0.007036131
58103 466.117 127.127.20.0 971a -0.001999952 0.000000000 0.000273452 0.005038724
58103 482.116 127.127.20.0 971a -0.001421208 0.000000000 0.000267421 0.004017279
58103 498.116 127.127.20.0 971a -0.001095211 0.000000000 0.000264485 0.003144880
58103 514.117 127.127.20.0 971a -0.000883474 0.000000000 0.000263075 0.002417834
58103 530.117 127.127.20.0 971a -0.506881326 0.000000000 0.005006457 0.504721484
58103 546.117 127.127.20.0 971a -0.491587103 0.000000000 0.008255521 0.453446884
58103 562.116 127.127.20.0 911a -0.490674095 0.000000000 0.012975091 0.413423826
58103 578.116 127.127.20.0 911a -0.483621870 0.000000000 0.011206563 0.364691586
58103 594.116 127.127.20.0 911a -0.486061261 0.000000000 0.008223063 0.317570414
58103 610.117 127.127.20.0 911a -0.000215462 0.000000000 0.004242512 0.415491993
```

NMEAはRMCだけしか出力しないようにしているのですが、NMEAがおかしなタイミングで出力されているように見えます。

そもそもNMEAではPPSが有効な場合はNMEAの出力タイミングはチェックしなくても良いように思うのですが。20番ドライバにはObscures location in timecode(flag4)というオプションがあるのですが1を設定しても止まります。

最初試したときにうまくいかなかったり、動いて時期にもntpqでとっていたログにスパイクがあったり、手元のモジュールだけかもしれませんが、このGPSモジュールには何か問題があるような気がしてなりません。

![スクリーンショット（2017-09-25 11.15.16）.png](https://qiita-image-store.s3.amazonaws.com/0/104066/7a06718d-36f4-c409-cf17-22b827b7612d.png)


ppsを受けているpinの状態をgpioctlコマンドで確認すると100msのパルスの場合、10回に1回は状態が変わっているのが確認できます。

FDTなRT3050のgpioなコードはsys/mips/mediatek/mtk_gpio_v1.cになるのですが、gpiopps.cのようなコードに対応していなかったので、パッチを作ってみました。[レビュー](https://reviews.freebsd.org/D9784) -> 2017/05/06 headにマージされました。MT7系のmtk_gpio_v2.cは対応してません。

これでgpioppsを追加してカーネルをビルドすると組み込まれます。/dev/gpiopps0になるので/dev/gpspps0にシンボリックを張っておきます。

```
# dmesg | grep pps
gpiopps0: <GPIO PPS> on ofwbus0
gpiopps0: PPS input on gpio0 pin 12
random: harvesting attach, 8 bytes (4 bits) from gpiopps0
```

NMEAを拾うためのシリアルの接続したポートにgettyが立ち上がらないように/etc/ttysを該当ポートをコメントアウトしておきます。RT3050は二つuartがあるので、コンソールを接続していない方にdtsで設定しておくと確実です。

一般的にはntpdは上位のサーバにネットワーク越しに問い合わせを行い、時間の補正を行いますが、最上位すなわちStratum 1なサーバはntpd内にある[クロックドライバ](https://www.eecis.udel.edu/~mills/ntp/html/refclock.html)を利用してGPSなどから時間情報を取得します。

ppsで使われるクロックドライバは[Generic NMEA GPS Receiver](http://doc.ntp.org/4.2.8p8/drivers/driver20.html)(20番)と[PPS Clock Discipline](http://doc.ntp.org/4.2.8p8/drivers/driver22.html)(22番)というドライバになります。20番はNMEAとppsを扱うドライバで、22番はppsだけのドライバになります。

20番のドライバでは/dev/gps0,/dev/gpspps0のデバイスを使います。前者がNMEA用で、後者がpps用です。22番のドライバで/dev/pps0を使います。FreeBSDの対応ドライバによりデバイス名は異なるので、必要な場合はシンボリックリンクを作っておきます。

当初20番と22番のドライバを両方使っていたところ、falsetickになってうまく行かなかったのですが20番だけで設定したところうまくいくようになりました。クロックドライバのパラメータをいろいろ試したら安定したようです。

```ntp.conf
server 127.127.20.0 mode 16 minpoll 4 prefer
fudge 127.127.20.0 flag1 1 flag3 1
```
```
# ntpq  -p -crv -ckern -csysinfo
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
oGPS_NMEA(0)     .GPS.            0 l   10   16  377    0.000    0.001   0.015
associd=0 status=0415 leap_none, sync_uhf_radio, 1 event, clock_sync,
version="ntpd 4.2.8p9-a (1)", processor="mips",
system="FreeBSD/12.0-CURRENT", leap=00, stratum=1, precision=-16,
rootdelay=0.000, rootdisp=1.150, refid=GPS,
reftime=dc5baeb3.63fc3367  Sat, Feb 25 2017 16:20:19.390,
clock=dc5baebe.1385c3a2  Sat, Feb 25 2017 16:20:30.076, peer=33585, tc=4,
mintc=3, offset=0.001041, frequency=75.652, sys_jitter=0.015259,
clk_jitter=0.003, clk_wander=0.009
associd=0 status=0415 leap_none, sync_uhf_radio, 1 event, clock_sync,
pll offset:            0.00236
pll frequency:         75.6515
maximum error:         0.0065
estimated error:       6e-06
kernel status:         pll ppsfreq ppstime ppssignal nano
pll time constant:     4
precision:             1e-06
frequency tolerance:   495.911
pps frequency:         75.6515
pps stability:         0.0350952
pps jitter:            0.005
calibration interval   256
calibration cycles:    50
jitter exceeded:       8
stability exceeded:    3
calibration errors:    7
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:     -16
root delay:         0.000
root dispersion:    1.150
reference ID:       GPS
reference time:     dc5baeb3.63fc3367  Sat, Feb 25 2017 16:20:19.390
system jitter:      0.015259
clock jitter:       0.003
clock wander:       0.009
broadcast delay:    -50.000
symm. auth. delay:  0.000
```

[ここ](https://www.eecis.udel.edu/~mills/ntp/html/decode.html#peer)を見ると先頭のoはPPSなpeerを示しているようです。これについて記述がない資料も多く、後から追加されたものなのかもしれません。

動くようにはなったがsyslogを見ると時々エラーを起こしている。1階に置いてあるのでGPSがつかめない事があるみたい。

```
Feb 26 09:36:32  ntpd[96]: kernel reports TIME_ERROR: 0x2307: PPS Time Sync wanted but PPS Jitter exceeded
```

これが起きるとntptimeのjitter exceededがカウントアップする。ちなみにppsが無いとOKではなくERRORとなる。

```
# ntptime
ntp_gettime() returns code 0 (OK)
  time dc5ce5b7.852d1288  Sun, Feb 26 2017 14:27:19.520, (.520219628),
  maximum error 4500 us, estimated error 5 us, TAI offset 0
ntp_adjtime() returns code 0 (OK)
  modes 0x0 (),
  offset 4.651 us, frequency 75.644 ppm, interval 256 s,
  maximum error 4500 us, estimated error 5 us,
  status 0x2107 (PLL,PPSFREQ,PPSTIME,PPSSIGNAL,NANO),
  time constant 4, precision 0.001 us, tolerance 496 ppm,
  pps frequency 75.644 ppm, stability 0.010 ppm, jitter 5.261 us,
  intervals 349, jitter exceeded 205, stability exceeded 3, errors 65.
```

NMEAの方もエラー起こす事があって、安定運用難易度高いかも。。。

起動時にrcからntpdを起動すると、ntpdの規定時間内にGPSモジュールが準備できなくて、ntpdが落ちてしまうようです。電源断せずrebootだと問題ありません。バックアップ電池付けてないので、付けると良いのかもしれません。

```
Feb 26 16:16:03  ntpd[795]: GPS_NMEA(0) 802b 8b clock_event clk_bad_format
```

PPSは処理系の遅延などで実際のよりも遅れます。下記の資料の方はこれを校正して、補正されているようです。

正しく動いているかどうかは、以下のように正確なサーバとの時間差を確認する事ができます。

```
# ntpdate -q ntp.nict.jp 
```

offsetが数msであれば正しく時間を刻んでいるとおもわれます。100msとかになっている場合は、ppsの割り込みが逆のエッジになっている可能性があります。

実はRT3050で試す前にAR2315で試そうとしたところppsとかGPSとか以前にntpdを上げるとOSが固まったのでRT3050で試しました。AR2315のOSのポーティングに問題があるかビッグエンディアンなどの環境の問題があるのかもしれません。

今回使ったGPSモジュールは信号線が3.3VでRT3050などに直接つなげるので都合が良かったのですが電源が5Vで直接つなげなかったのでレギュレータを外して3.3Vダイレクトにしました。またPPSを受けている時のLEDが明るすぎたのと、結構消費電力多いようなので、R2の抵抗外して点灯しないようにしました。窓際においてチカチカしていると防犯用品とも活用できそうですが。

シリアルのTXは接続せず、RXだけ接続しています。NMEAが送り続けられている状態でリブートしても問題なくu-bootに引っかかったりせず起動できるようです。これはたまたまだと思うので、ロジックを入れるなりして本来は制御すべきと思います。シリアルの使い方は[SOCのシリアルポートを使う](http://qiita.com/yamori813/items/54d6b1a8b7d0e3be6c07)に書いてみました。

衛星は刻々と移動しますので、今日安定して受信できても明日受信できない事があるかもしれません。GPSを使ったntpdは設定も面倒ですが、運用もかなり面倒です。

室以外にGPSモジュールを設置できれば良いのですが、それが出来ない場合には室内で工夫する必要があるようです。

![GPS-POSITION.png](https://qiita-image-store.s3.amazonaws.com/0/104066/e90dcef1-8ccb-3772-e70b-baa539223150.png)

一見aの方が真上が確保できているので良く入りそうですが、bの方が上空のカバー率は大きくなります。

2017/3の時点でFreeBSD 12-CURRENTに入っているntp 4.2.8p9はMK_OPENSSL=yesでないとビルドが通らなくなっています。以前は無くてもビルドできたようです。この設定でlibmpが必要になりrootfsがちょっと大きくなります。ntp自体も大きいのですが、libmpが入るだけで4MのFlashには入らなくなります。

GPSのアンテナのコネクタはこんな構造になっています。ゆるんでいて接触悪くなり、不安定になった事がありました。

![写真（2018-06-18 09.06） #2.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/55ed3bfe-5541-4ca4-3508-d12e810bfccc.jpeg)
![写真（2018-06-18 09.06）.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/fea3e89c-e780-39ac-b849-4f14f762b675.jpeg)

このコネクタはSMAメス呼ばれるもののようです。オスメスが逆のRP SMAというコネクタもあるので要注意です。WIFI機器はRP SMAがよく使われています。

GPSモジュールを本体の中にいれてWIFIのアンテナを切り落として、GPSのアンテナをだしてみました。

![写真（2024-05-10 11.39）.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/bd001de3-eddb-84ca-181d-4872b63bb52d.jpeg)

![写真（2024-05-10 11.40）.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/1966d45c-170f-7b7c-46b7-a6057ab9818c.jpeg)

GPSアンテナは、使ってなかったFM八木アンテナの台座を使ってベランダの支柱に固定しました。

![IMGP0036.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/d1c532e8-e5c6-fa0b-d3b5-46346f58ab1c.jpeg)

おまけですが、このGPSモジュールのシリアルのボーレートを変更するには以下のようにコマンドを送ります。コマンド送った後は設定したボーレートで通信します。このコマンド体系はGlobalTopという会社の製品と同じようだ。

```
#include <stdio.h>
#include <fcntl.h>
#include <string.h>

main()
{
int sum = 0;
char *str="$PMTK251,57600*";
char *ptr;
char cmd[128];

        ptr = str;
        ++ptr;
        while(*ptr != '*') {
                sum = sum ^ *ptr;
                ++ptr;
        }

        sprintf(cmd, "%s%x¥r¥n", str, sum);

        int fd = open("/dev/cu.usbserial-DA0056NV", O_RDWR);
        write(fd, cmd, strlen(cmd));
        close(fd);
}
```

UBLOX NEO-6Mなモジュールも試してみました。PPSは信号線としてはでていなかったのですが、LEDにPPS信号が出力されていたので、そこから拾いました。こちらは立ち上がりが秒の初めだったので、assert-falling-edge無しで処理しました。

![20170921-181032.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/19602a2e-659a-2da0-69e3-f147e95395a3.jpeg)

UBLOX NEO-6Mを受けているRT3050のモジュールを発泡スチロールの箱に入れたところ劇的に精度が改善してスパイクがなければ平均Jitterが15usec固定のOffsetの絶対値の平均が1から2usecで運用できています。

![filename(11).png](https://qiita-image-store.s3.amazonaws.com/0/104066/64f0a0aa-802b-7236-4ff1-c2aa3f903bf7.png)

GPSのアンテナを金属板の上に置くと良いという情報をネットで見つけて、手元にあったモルトウイスキーの丸い箱のふたをしいてみたところ、少しスパイクが減ったような気がします。

2018/3に秋月のモジュールのアップデートファームが公開されていました。アップデートして確認したところ、少し安定したような気がしますが、やっぱダメみたいです。アンテナが小さいので受信状況が悪くなる事が多いのかもしれません。

2019/4/7に「[GPS週数ロールオーバー](http://qzss.go.jp/column/gps-rollover_190225.html)」がありましたが、問題は起きませんでした。

2024/1/3 昨年末からu-bloxのモジュールからppsが出ない状態になっていたのですが、アンテナを代えたらでるようになったので、アンテナの問題のようでした。

# JJY

後日まとめ：最初に作ったシチズン仕様のAttiny85のデコーダーはJitterとOffsetの絶対値が10msec以下にできたのですが、その後作ったSEIKO仕様のデコーダーはJitterが2msec台でOffsetの絶対値が1msec台で運用できました。SEIKO仕様のデコーダーはArduinoで作ってオープンソースにしましたが、設置場所によって精度は変わってくると思います。JJYの受信は設置場所のインパクトが非常に大きいので骨がおれます。下記はだらだらと書いたものなので適当に読んでください。

ntpdには電波時計の信号(JJY)を使った[クロックドライバー](http://doc.ntp.org/4.2.8p8/drivers/driver40.html)(40番)もあります。もちろん[国内で開発](http://www.bea.hi-ho.ne.jp/abetakao/ja/ntp/)されていて、メンテナンスもされているようです。

去年Aitendoで処分品の電波時計モジュールを39円(税別)で買ってきていたので、これで使えるように試してみました。ホストはGPSと同じようにRT3050/FDT(sys/mips/mediatek)を使いました。おまんじゅうRT3050モジュールはハードオフで108円で入手できるので、お気に入りです。

![JJY-GPS2.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/4022243d-49f5-9659-73b8-f7247fed00b7.jpeg)

黄色い方がJJYの信号で、ピンクがGPSの信号です。


モジュールからは信号しか出ていないのでATTiny85でデコーダーを作ってみました。デコーダーの処理の概要は以下のような感じです。

* マーカが二つ続くところ見つけて0秒を取得
* 60秒のデータで時間情報を取得
* 取得した情報は前の分のデータなので次の分を計算
* 毎時15分,45分の40秒台のコールサインを無視する
* 信号の立ち下がりでビットを判断して、立ち上がりでシリアル送信

![IMGP0149.JPG](https://qiita-image-store.s3.amazonaws.com/0/104066/8754cfe6-0b8f-c9ba-7f91-120e68846501.jpeg)


40番のクロックドライバはいくつかの製品のサポートがありシチズンTIC JJY-200が一番仕様が簡単だったので、これのクローンを書いてみました。

JJY-200の仕様は毎秒以下のような24バイトのデータをシリアル4800ボーで送ります。

```
'OK 17/03/04 5 18:36:51<CR>
```

実際のJJY-200は直にJJYの電波のエッジで出力するのではなくて、内部にクロックを持っていて、これを同期させて、これを元に出力するように作られているようです。

ベースにしたコードは[FPGA FMチューナの選局用](https://github.com/yamori813/fpgafmctl)に作ったもので、タイマー0を回して赤外線の信号の割り込みを拾ってソフトシリアルで送信するようになっていましたが、これをJJYの信号処理に書き換えてみました。

```ntp.conf
server   127.127.40.0   mode 4
fudge    127.127.40.0   time1 0.0
```

```
# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*JJY(0)          .JJY.            0 l   11   64  377    0.000  -69.732  39.316
```

ログ

```
57870 25673.235 127.127.40.0 JJY mode=4 dev=/dev/jjy0
57870 25673.240 127.127.40.0 JJY Refclock: CITIZEN T.I.C CO. LTD. JJY200
57870 25673.241 127.127.40.0 JJY minpoll=6 maxpoll=6
57870 25674.228 127.127.40.0 === polls=1 reach=0000000
57870 25674.984 127.127.40.0 <-- 'OK 17/04/27 3 16:07:55
57870 25674.997 127.127.40.0 === 2017/04/27 16:07:55.000 JST   ( 2017/117 07:07:
55.000 UTC )
57870 25674.997 127.127.40.0 --- status 6 [*] Sys.Peer : offset 16.697 mSec. : j
itter 0.015 mSec.
57870 25738.262 127.127.40.0 === polls=2 reach=0000001
57870 25738.952 127.127.40.0 <-- 'OK 17/04/27 3 16:08:59
57870 25738.954 127.127.40.0 === 2017/04/27 16:08:59.000 JST   ( 2017/117 07:08:
59.000 UTC )
57870 25738.955 127.127.40.0 --- status 6 [*] Sys.Peer : offset 48.269 mSec. : j
itter 31.572 mSec.
57870 25802.233 127.127.40.0 === polls=3 reach=0000011
57870 25802.977 127.127.40.0 <-- 'OK 17/04/27 3 16:10:03
57870 25802.979 127.127.40.0 === 2017/04/27 16:10:03.000 JST   ( 2017/117 07:10:
03.000 UTC )
57870 25802.980 127.127.40.0 --- status 6 [*] Sys.Peer : offset 23.678 mSec. : j
itter 18.075 mSec.
```

こちらの方法もかなり安定するまで手こずりました。まず電波時計はノイズに弱いので、受信モジュールはコンピュータから離して起きます。AMラジオなどでノイズを確認するのも良いです。人が近づいたりしてもノイズになることがあります。電源のACアダプタは、スイッチングではなくトランスのタイプが良いようです。余談ですが、壁掛けの電波時計がMacの近くに置いていたところ壊れた事がありました。おそらくノイズが原因だったのだと思います。

理由は分からないのですが、オシロをつなぐと正常に動いて外すと動かなくなるという状態でした。いろいろ調べたら、プローブのグランドさえつながっていれば正常に動くようです。このためGNDとアースをコンデンサーを挟んでつないだところ同じ効果になり動くようになりました。（設置場所変えたらなくても動くようになりました。電波の受信強度や外部ノイズに依存しているのかもしれません）

![JJY-GND.png](https://qiita-image-store.s3.amazonaws.com/0/104066/ba7ce4a5-6825-d103-e057-f07076a5e646.png)

あとバーアンテナの向きも大切で送信アンテナ方向に直行するように設置する必要があります。

* 設置場所の緯度経度とアンテナの緯度経度で方位角をネットのサービスなどで確認
* 設置場所の当日の太陽の南中時に影を確認して北を割り出す
* 北から方位角の線を引きそれに直行するようにバーアンテナを置く

<a data-flickr-embed="true"  href="https://www.flickr.com/photos/14000636@N03/33255424085/in/dateposted-public/" title="IMGP0164"><img src="https://c1.staticflickr.com/4/3931/33255424085_189ab22d1c_z.jpg" width="640" height="480" alt="IMGP0164"></a>

我が家からおおたかどや山はおおよそ30度の方向にあるので上記のように置いてみました。

2023/11追記: 東京で動いていたセットを酒田にもってきたところ、動きませんでした。いろいろ試したところFONのACアダプタをトランスタイプのものに変えたところ、受信できるようになりました。AC100のノイズに影響されているものではないかと思われます。

暦確認用に作ってあった自分用Androidアプリ([Rabbit.apk](http://yahoo.jp/box/O3vELx))におおたかどや山とはがね山の方角を表示して確認できるようにしてみました。

![device-2017-03-05-030530.png](https://qiita-image-store.s3.amazonaws.com/0/104066/73bf49e2-5d68-e02d-cda4-694d4afeff19.png)



パーアンテナの地上高も影響があるようです。ある程度の高さがあった方が良いようです。

このモジュールの信号にはチャタリングのようなノイズがのることがあるようなので、区間の小さいレベル変化は無視するようにしてあります。

デコーダーのコードは400行弱で、あまりテストしていないのと他で動くか分からないので、今のところ公開していません。AVR以外でも作れると思いますし、他の対応機器のクローンを作ってみても面白いかもしれません。

発信器を制作している方もネットで見かけるので、しっかりしたテストは発信器を作って行った方が良いと思います。

JJY自体はうるう秒に対応していますが40番のクロックドライバのJJY-200モードでは対応していません。

このモジュールの信号をPPSとして使う事もできますが、GPSの方が安定して動いているので、試してません。

ルーターのようなリアルタイムクロックが無いモジュールだと起動時に1970年になってしまうため、そのままntpdを起動するとすぐに落ちます。ntpdateでいったんいったん時間を合わせてからntpdを起動する必要があります。ntpdを起動する前にNMEAやJJYからデータを取得して日付を設定するという方法もあると思います。我が家ではJJYは起動時にGPSをntpdateしてGPSはJJYをntpdateするように設定してあります。片方をメンテナンスで再起動する場合はこれでよいのですが、停電とかあって両方落ちた時は自動復旧できないです。

野ざらしだと埃まみれになるので、塩辛が入っていたケースに入れてみました。ちょっと作り直したらまたグランドが無いと動かなくなりました。ケーブルの長さや特性にも影響されるようです。

<a data-flickr-embed="true"  href="https://www.flickr.com/photos/14000636@N03/34174364832/in/dateposted-public/" title="IMGP0370"><img src="https://c1.staticflickr.com/3/2857/34174364832_7a7fbed2d5_n.jpg" width="320" height="240" alt="IMGP0370"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>

GPSもJJYも周波数は違うが電波を使っているので、電子レンジやパソコンなどのノイズによる影響がある。ノイズには二種類あって発生源から空中を伝わるものと、電灯線を伝わるものである。前者については発生源から距離を離すとかシールドで隔離するとかが考えられる。後者については電灯線の安定化や、バッテリー駆動などが考えられる。

またノイズを絶対値を減らすのではなくアンテナからのゲインを上げて相対的にノイズを減らすという方法もある。JJYの場合は大きめのバーアンテナを使うとか、正確に同調させるなどの方法が考えられる。

と書いていたのですが、電子レンジのノイズなどは絶対的に大きく、これを除去する方法は設置場所の距離をとるしか無いようです。送信所から遠い場合などにはバーアンテナのチューニングも効いてくるようです。

ノイズの発生源の電子レンジなどのアースをちゃんととるのも効果があるようです。

Lattice版FPGA FMチューナでFM放送を聞いているのですが、FPGAは強力なノイズ元になり、JJYの受信を妨害していました。^ ^;

10時台のスパイクは1Fの受信機が2FのFPGA FMチューナをONにした事が原因と思われます。

![filename(17).png](https://qiita-image-store.s3.amazonaws.com/0/104066/7e1ce842-29fd-6a37-c923-4c05e8db1b74.png)


JJYは点検や雷害回避の為に停波することもあります。試験電波の送信もあります。うるう秒もあります。ちゃんと実装するの結構大変です。

JJYの実装でコールサインがある15分と45分の処理が手こずりました。

![ntp-zen.png](https://qiita-image-store.s3.amazonaws.com/0/104066/142a5019-3317-6dbd-0693-cb8c9db23998.png)


2017/05/12に点検で停波が13:15から14:15まであったときのOffsetとJitterのグラフはこんな感じになりました。

![スクリーンショット（2017-05-13 7.15.40）.png](https://qiita-image-store.s3.amazonaws.com/0/104066/7b34e899-fcb6-b281-51fd-50207131fc5a.png)

電波時計で使われているチップ

|メーカ|型番|備考|
|:--|:--|:--|
|C-MAX|CME6005|ドイツの会社で、おそらく上記のモジュールで使われている。すでにディスコンになってる模様。CME8000というチップもあるようだ。|
|Micro Analog Systems|MAS6180C|Aitendoで販売されていた初期のモジュールで使われていたよう。フィンランドの会社のようです。|
|Micro Analog Systems|MAS6181B|新しいチップでAitendoでモジュールが販売されています。|
|セイコーNPC|SM9501A|マルツで取り扱いある|
|沖電気|ML6191|2004年くらいに開発された古いチップ|
|ON Semiconductor|LA1654C|三洋電機の頃にLA1650という製品があり、これの後継チップと思われます。|
|TEMIC|U4226B|秋月で販売されているトライステートのキットで使われています。T4225Bというチップもあった模様。このキットのバーアンテナはスミダコーポレーションのACL80Bのようです。 データーシートに16-Jan-98とあり、おそらく一番古いチップでCME6005とデータシートのイラストが似ているので、なんらか関係があるのかもしれません。|

市販のntpdで使える電波時計機器のいくつかは毎秒のタイミングをそのまま使っている訳ではなくて、定期的に受信して内蔵のクロックを同期させてそれをもとに出力を行うようです。

どうも安定度に問題があるのでいろいろ調べていたら[電波時計モジュールが「使えない」理由](http://seppina.cocolog-nifty.com/blog/2013/10/post-ad2b.html)というページを見つけました。この中にもありますが、直前のビットタイプによって秒の長さが変わってしまうという事のようです。

細かく調べる面倒だったんで、ざっくり今朝の3時間のoffsetを調べてみたら10ms以上が発生しているのがマーカの後すなわち0,10,20,30,40,50だった。とりあえずこれらと1秒は出力しないようにしてみた。(2017/07/17)

上記は不安定になっていた時期に調べた事なので、実装は入っているのですが、全くまとはずれかもしれません。考えてみると直前のビットの幅に影響されるのであれば、直前のビットがすべて同じになる、マーカの直後だけを送るようにするのが良い気がします。(2017/09/26)

CME6005を使った[DCFのモジュール](https://qiita.com/yamori813/items/f61bb2855ccf2c95f03a)を入手したので、バーアンテナと水晶を交換して40KHzを受信してみました。

![20171019-212449.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/21c6577c-c7c3-e747-68bf-99319bfc1b88.jpeg)
![20171019-212606.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/c66cd3ae-2c20-89f4-5392-bc9c5f6fcccb.jpeg)

このモジュールは反転した出力のみが拾えます。結構なまっているのでなんらか工夫をしないと使えそうにありません。oz_

3.3Vのレギュレータを電源にしてバッファとフォトカプラーを入れてArduinoで受けて、1秒の最小と最大のmsecを確認するテストプログラムを書いてみました。最小のmsecと秒数、最長のmsecと秒数を0秒に表示しています。

バーアンテナはいろいろいじっていたら、断線してしてしまったので、大きなフェライトバーに巻き直してみました。

<a data-flickr-embed="true"  href="https://www.flickr.com/photos/14000636@N03/37568958704/in/dateposted-public/" title="IMGP0127"><img src="https://farm5.staticflickr.com/4526/37568958704_078bf14974.jpg" width="500" height="375" alt="IMGP0127"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>

```
M01001001M000001001M001100000M011100010M000010111M101000000M
991,28,1009,9
M01100000M000001001M001100000M011100000M000010111M101000000M
990,8,1009,19
M01100001M000001001M001100000M011100010M000010111M101000000M
991,38,1009,19
M01100010M000001001M001100000M011100010M000010111M101000000M
991,58,1008,39
M01100011M000001001M001100000M011100000M000010111M101000000M
991,38,1010,19
M01100100M000001001M001100000M011100010M000010111M101000000M
990,28,1010,19
M01100101M000001001M001100000M011100000M000010111M101000000M
990,58,1009,9
M01100110M000001001M001100000M011100000M000010111M101000000M
991,28,1009,19
M01100111M000001001M001100000M011100010M000010111M101000000M
991,58,1010,9
M01101000M000001001M001100000M011100010M000010111M101000000M
991,8,1008,19
M01101001M000001001M001100000M011100000M000010111M101000000M
990,38,1009,50
```

これを見るとマーカー前とマーカーに偏っていることが見えます。マーカー部分をうまく処理すると精度を上げられそうです。

いろいろ考えて、ビットパターンによって精度がかわってくるので、毎秒の信号は基準とせずタイマーで処理して1分ごとに同期させる処理で、ntpd 40番ドライバmode 6用のプログラムをArduinoで作ってみました。

https://github.com/yamori813/JJYS

ロジックは以下の通りです。

0. 処理は秒の終りすなわち次の秒の始まりで行います
1. マーカーが二つ続くところを待つ
2. エッジ割り込みで58秒までデータを取得
3. 59秒の開始エッジで次の分の日時を計算して900msのタイマー設定
4. 以後の秒のインクリメントはタイマーに移行
5. 最初のタイマー割り込みで日時をシリアルに出力してタイマーを1000msに再設定
6. タイマー割り込みでシリアルに出力
7. エッジ割り込みでデータ取得
8. 58秒のシリアルに出力してタイマー割り込みでタイマー停止
9. 4に戻る


![JJYS.png](https://qiita-image-store.s3.amazonaws.com/0/104066/e3afe01d-945c-56c3-3bf1-3c4085398a9e.png)


![20171109-093116.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/1e35e331-4f1b-be56-444b-e17759540e4a.jpeg)

![20171109-093247.jpg](https://qiita-image-store.s3.amazonaws.com/0/104066/f5e8838e-0a8d-2606-d069-f0e04b7e5ca8.jpeg)

ビットのエラーチェックは入っていますが、パリティーのチェックは入ってません。またうるう秒の処理も入っていません。

Arduino Pro miniなどはセラミック発振子を使っているので精度がでません。水晶を使ったボードを使う必要があり、8MHzよりも16MHzの方が精度が上がります。

タイマーとJJYの1分のずれを拾って、補正を分散させるというような改良も考えられます。

本来は時刻の文字列の13バイトは秒の始めに出力しているの用なのですが、ntpd 40番ドライバでは特に見てないので、秒の終わりにタイミングの3バイトの直前に出力するようになっています。オリジナルの仕様は送信後5-10msecで秒が始まるようになっていますが、上記の実装はそれよりも長く(30msecくらい)なっているので、time1で適当に調整してください。

何らかの理由で15,45分のコールサインから開始した場合はエラーになり、16,46分から再スキャンになります。（NICTに問い合わせたところ、停波後15,45分からの再開も可能性があるそうです）

上のデータを見てもJJYの秒の開始のエッジは10ms程度の揺れがありあまり信用できない事がわかったことと、1分のmsをArduinoのタイマーで計測したところ5ms程度の誤差であったので、59秒のエッジを基準に以後はArduinoのタイマーで処理して、また59秒でタイマーを再設定するという仕組みにしてみました。59秒前後はビットパターンが毎回同じなので、ビット変化による影響はないものと思われます。またmode 6の仕様が秒頭より前に出力するようになっていたので、エッジだけでは処理できないのでタイマーを使う事を考えました。簡単に確認した範囲ではエッジだけで処理するよりも良い数字が出ています。

![スクリーンショット（2017-11-08 7.21.53）.png](https://qiita-image-store.s3.amazonaws.com/0/104066/72bb0acb-0453-31ec-819a-14ad1f054749.png)


JJYは外部ノイズによる影響が大きくアンテナを移動したところ傾向が変わっているのがはっきり見えます。

![filename(7)2.png](https://qiita-image-store.s3.amazonaws.com/0/104066/4cec3f54-8e04-8cb6-ec96-08087570a519.png)

毎日6時前後にスパイクがあるのですが、自分が眠っている日も有り、おそらく近所のなんらかのノイズが原因ではないかと思われます。

![filename(12).png](https://qiita-image-store.s3.amazonaws.com/0/104066/67dedbbc-a4e8-f4cf-1b53-b81b6495a130.png)


外部ノイズは刻々と変化するので、本体の評価が変更による影響か外部ノイズ要因か見極めるのが結構大変です。

自宅はおおだたどや山から200Kmで木造家屋なので、条件は悪くない方だとおもうのですが、設置場所のインパクトが非常に大きい事がわかりました。自宅内でノイズ元になるものの近くに置かないなどの対策はもちろんですが、近所のノイズも影響する事があるようなので、ケースによってはどうしようもない事もあるかもしれません。

ノイズの原因はパソコンやディスプレーまた電子レンジやIH炊飯器、FPGA FMチューナなどが確認されている。これらはAMラジオを近づけてもノイズが確認できます。ただ実際はこの方法で確認できる範囲よりも、ずっとシビアな気がします。

あと自宅の上空を自衛隊機が飛んでく時にもノイズになっている気がします。

ntpdのログファイルのclockstatsに"# Invalid length"とでて正しくデータ拾えなくなることがあるようです。ntpdを再起動するとなおることがあるので、スクリプトでログファイルをモニターしてこれが出続けたら、ntpdを再起動するのが良いかもしれません。

受信モジュールと、デコーダーは離した方が良いと思われます。受信モジュールとデコーダーの接続はこのような感じにしています。AVRは16MHzで動かさないと精度がでなかったので5V駆動で、右の回路でTXは分圧して3.3Vにしています。

![JJY.png](https://qiita-image-store.s3.amazonaws.com/0/104066/638b1d24-1620-6a78-a378-ce08c24a277f.png)

フォトカプラーには寿命があるようです。これは発光側の寿命になり、流している電流により変化するようです。上の回路の抵抗値は他の回路を参考にしていて、計算はしていませんが、ざっくり10年くらいは大丈夫そうですが。

Aitendoで購入したモジュール（オレンジの線）とDCF77を改造したモジュール（緑の線）のJitterの変化を重ねてみた。

![filename(41).png](https://qiita-image-store.s3.amazonaws.com/0/104066/84066df4-b7cb-62d6-e762-930027f5f722.png)

似てるところもあるが、早朝のご近所さんのIH炊飯器と思われるノイズに対する挙動も違うし、他にも違ったところが見受けられる。モジュールのチップは樹脂に覆われて確認できないが、違う物なのかもしれない。

JJY受信についてまとめ

* 長波は非常にノイズの影響を受けやすい
* 受信モジュールの特性によってノイズの影響の出方はさまざま
* 日中帯よりも深夜帯の方が安定して受信できる
* 深夜帯でもノイズが発生している事もある
* 夏の暑い時期はクーラーなどのノイズの影響がある
* 二つの受信機でノイズの大小で安定度が逆転するケースあった
* 1時間などの周期的なノイズもある
* ノイズ源は電子レンジやIH炊飯器やPCやディスプレーなどがある
* FPGA FMチューナも強烈なノイズ源だった
* 自宅だけではなく隣家のノイズの影響も受ける
* 人がアンテナに近づいたり、触っても影響がある
* すべてのノイズを把握する事は不可能
* 1mくらい設置場所を変えただけでも受信状況は変化する
* バーアンテナを送信所に直行するように置くと良い
* 12時や18時などの食事時間帯は電子レンジなどを利用する事があり厳しい事がある
* AMラジオを使い大きなノイズ源は確認できるが確認できないくらいの小さいノイズも影響する
* 点検などで停波していることも時々ある
* 200km強のおおたかどや山は、はっきり受信できるが、900km弱のはがね山は受信は出来るがノイズでデコードできない
* ピットパターンによって立ち上がりの精度が変わるが、これは波長と送信所からの距離との関係もある
* タイムサーバとは関係ないが針式の電波時計はノイズが多い場所だと異常に電池が消耗したり、壊れる事すらある

木造家屋でもこのような状態なので、鉄筋コンクリートの建物ではより厳しいのかもしれない。最初に手に入れた電波時計が安定していなくて、あまり電波時計を信用していなかったのだが、設置場所さえよければ結構使える事が分かった。GPSを使ったntpサーバではまれにJJYを越えるjitterのスパイクが発生する事があり、GPSが万能というわけでもないようだ。

JJYのリピーターの製品もいろいろあったり、Arduinoで自作されている方なども見かける。受信状態が確認できる機能が提供できれば、いろいろ試して通常の受信方法でもどうにかなるケースもあるのではないだろうか。

# うるう秒

[2017年1月1日いGMT時間0時(JST時間9時)にうるう秒](http://jjy.nict.go.jp/news/leaps2017.html)が挿入されました。

|JST時間|FreeBSDのunixtime|TAI-UTC|GPS-UTC|
|:--|:--|:--|:--|
|2017年1月1日8時59分59秒|1483228799|+36sec|+17sec|
|2017年1月1日8時59分60秒|1483228800|+37sec|+18sec|
|2017年1月1日9時0分0秒|1483228800|+37sec|+18sec|
|2017年1月1日9時0分1秒|1483228801|+37sec|+18sec|

スタンドアローンの場合には実際には2017年1月1日8時59分60秒に2017年1月1日9時0分0秒となり1秒進んだ状態になります。

JJYの送信は以下のようになるようです。

![JJY-LIP.png](https://qiita-image-store.s3.amazonaws.com/0/104066/1fb95de2-f81e-58bd-fcc5-3102c643bee3.png)

# グラフ

ntpのoffsetとjitterのグラフは以下のようにして作っています。

1. cronでshスクリプトを起動してntpqの出力をcsvに落とす
2. ログをmrubyで整形してjsonにする
3. Chart.js+JQueryでグラフ表示

1のshスクリプト

```sh
#!/bin/sh

dumpntpq() {
# delete last piriod for jquery pasererror workaround
VAL=`ntpq -p $1 | awk '/'$2'/{gsub(/¥.$/, "",$9);gsub(/¥.$/, "", $10);print $9"
,"$10}'`
DAY=`date "+%Y/%m/%d"`
TIME=`date "+%H:%M:%S"`
if [ -n "${VAL}" ]; then
  echo ${DAY},${TIME},${VAL} >> $3
fi
}

dumpntpq 10.0.1.20 GPS /tmp/gps2.log
```

2のmrubyスクリプト

```ruby
#!/usr/local/bin/mruby

f = File.open("/tmp/" + ARGV[0], "r")

j = File.open("/tmp/htdocs/json/" + ARGV[1], "w") 

j.printf("{¥"date¥":¥"" + ARGV[2] + "¥",")
j.printf("¥"type¥":¥"" + ARGV[0] + "¥",")

j.printf("¥"data¥":[")

c = 0

f.each_line do |line|

  line.chomp!

  arr = line.split(",")

  if ARGV[2] == arr[0] then
    if c != 0 then
      j.printf(",")
    end
    j.printf("[¥"" + arr[1] + "¥"," + arr[2] + "," + arr[3] + "]")
    c = c + 1
  end
end

j.printf("]}")
```

3のjavascript

```javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="js/Chart.min.js"></script>
<script src="js/jquery.min.js"></script>
<canvas id="myChart" width="200" height="100"></canvas>
<a id='link' download='filename.png'>Save as Image</a>
<script>
var ctx = document.getElementById("myChart");
//ctx.style.backgroundColor = 'rgba(250,250,100,255)';
var clab = [];
var odat = [];
var jdat = [];
$(function() {
  if(!window.CanvasRenderingContext2D){
    return;
  }
  var arg = new Object;
  var pair=location.search.substring(1).split('&');
  for(var i=0;pair[i];i++) {
    var kv = pair[i].split('=');
    arg[kv[0]]=kv[1];
  }
  Chart.pluginService.register({
    beforeDraw: function (chart, easing) {
      if (chart.config.options.chartArea && chart.config.options.chartArea.backg
roundColor) {
        var helpers = Chart.helpers;
        var ctx = chart.chart.ctx;
        var chartArea = chart.chartArea;

        ctx.save();
        ctx.fillStyle = chart.config.options.chartArea.backgroundColor;
        ctx.fillRect(chartArea.left, chartArea.top, chartArea.right - chartArea.
left, chartArea.bottom - chartArea.top);
        ctx.restore();
      }
    }
  });
  var now = new Date();
  var srcfile = "json/" + arg.src + ".json" + "?id=" + now.getTime();
  $.getJSON(srcfile, function(data) {
    var len = data.data.length;
//    alert(len);

    var oav = 0;
    var jav = 0;
    var omax = 0;
    var jmax = 0;
    for(var i = 0; i < len; i++) {
      clab.push(data.data[i][0].substr(0,5));
      odat.push(Math.abs(data.data[i][1]));
      jdat.push(Math.abs(data.data[i][2]));
      oav += Math.abs(data.data[i][1]);
      jav += Math.abs(data.data[i][2]);
      if(Math.abs(data.data[i][1]) > omax) {
        omax = Math.abs(data.data[i][1]);
      }
      if(Math.abs(data.data[i][2]) > jmax) {
        jmax = Math.abs(data.data[i][2]);
      }
    }
    jav /= len;
    jav = Math.round(jav*1000);
    jav /= 1000;
    oav /= len;
    oav = Math.round(oav*1000);
    oav /= 1000;
    var myChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: clab,
            borderColor : "#fff00",
            datasets: [{
                label: 'Jitter',
                data: jdat,
                fill: false,
                lineTension: 0.1,
                backgroundColor: "rgba(214,182,65,0.4)",
                borderColor: "rgba(214,182,65,1)",
                borderCapStyle: 'butt',
                borderDash: [],
                borderDashOffset: 0.0,
                borderJoinStyle: 'miter',
                pointBorderColor: "rgba(214,182,65,1)",
                pointBackgroundColor: "#fff",
                pointBorderWidth: 1,
                pointHoverRadius: 5,
                pointHoverBackgroundColor: "rgba(214,182,65,1)",
                pointHoverBorderColor: "rgba(220,220,220,1)",
                pointHoverBorderWidth: 2,
                pointRadius: 1,
                pointHitRadius: 10,
                spanGaps: false,
            },{
                label: 'Offset',
                data: odat,
                fill: false,
                lineTension: 0.1,
                backgroundColor: "rgba(75,192,192,0.4)",
                borderColor: "rgba(75,192,192,1)",
                borderCapStyle: 'butt',
                borderDash: [],
                borderDashOffset: 0.0,
                borderJoinStyle: 'miter',
                pointBorderColor: "rgba(75,192,192,1)",
                pointBackgroundColor: "#fff",
                pointBorderWidth: 1,
                pointHoverRadius: 5,
                pointHoverBackgroundColor: "rgba(75,192,192,1)",
                pointHoverBorderColor: "rgba(220,220,220,1)",
                pointHoverBorderWidth: 2,
                pointRadius: 1,
                pointHitRadius: 10,
                spanGaps: false,
            }]
        },
        options: {
chartArea: {
        backgroundColor: 'rgba(251, 85, 85, 0.4)'
    },
          bezierCurve : false,
          animation: {
            onComplete: done
          },
          title: {
              display: true,
              fontSize: 24,
              text: data.date +' - ' + decodeURIComponent(arg.title)
          },
          legend: {
            labels: {
              generateLabels: function(chart) {
                labels = Chart.defaults.global.legend.labels.generateLabels(char
t);
                labels[0].text = 'Jitter average: ' + jav + 'ms max: ' + jmax + 
'ms';
                labels[1].text = 'Offset average: ' + oav + 'ms max: ' + omax + 
'ms';
                return labels;
              }
            }
          },
          scales: {
              xAxes: [{
                ticks: {
                  callback: function(value) {return value.match(/((^0[0369])|(^1
[258])|(^21)):0/) ? value : ''},
                  stepSize: 1
                }
              }],
              yAxes: [{
//                  type: 'logarithmic',
                  ticks: {
                    callback: function(value) {return value + " ms"},
                    min: 0
                  }
              }]
          }
        }
    });
  })
  .fail(function(jqXHR, textStatus, errorThrown) {
    console.log("getJSON status: " + textStatus);
    console.log("getJSON text: " + jqXHR.responseText);
  });
});
function done(){
  var url_base64 = document.getElementById('myChart').toDataURL('image/png');
  link.href = url_base64;
}
</script>
</body>
</html>
```

## 参考資料

[NTPメモ-Internet ARCHIVE](http://web.archive.org/web/20110709052520/http://www.rr.iij4u.or.jp/~zubora/ntpcook-20070413-1.pdf) ちょっと古いですが、NTP関連ほとんど網羅されています。すごい資料です。

[ntpd – PPS discipline まずは普通に較正 編](https://kana.me/entry/ntpd-pps-discipline)

[NTP うるう秒(閏秒)](http://www.bsddiary.net/doc/ntpd-leapseconds.html)

[PC-UNIXとGPSによるStratum-1 NTPサーバの構築と評価](http://sojo.yamanashi.ac.jp/bul/final01/ando/ando01.html)

[NTP設定](http://d.hatena.ne.jp/incarose86/20110505/1312522379)　とあるSIerの憂鬱

[日本標準時グループ](http://jjy.jp/index.html)

新城 靖さんの[時刻と時間の管理](http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2006/2007-01-23/)

VA LINUXさんの[カーネルにおけるタイマー事情](http://www.valinux.co.jp/technologylibrary/document/linuxkernel/timer0001/)

[New event timers infrastructure](https://wiki.freebsd.org/201010DevSummit?action=AttachFile&do=get&target=timers.pdf)

# 2020/02/29

閏年で29日があったのだが、そのグラフはこのようになっていた。

![filename-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/c57c7175-cb1c-0561-99ed-0b5e9b9af943.png)

![filename-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/ed8f8284-15a1-e693-9b5d-19165b787005.png)

![filename-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/c272e948-37a8-3c72-40d0-0d01d6afe8af.png)

![filename-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/099e7546-4731-b9d2-d6c3-55f14479d254.png)

JJY2が深夜帯に同期が取れなくなっているが、日頃からJJY2の方が近隣のノイズの影響が大きことや、同じロジックのJJYの方が正常に動作していることから、おそらくバグではなく外部ノイズに影響された可能性が考えられる。

# 12.3Rのntpd

12Rでmipsのサポートが終わりなのだが、最後のリリースになるだろうと思われる12.3Rで焼きなしたところntpdの挙動が変わった。

st1でJJYの信号(st0)を受けられなくなると、そのホスト自体のreachは0になるのだが、st1を参照しているst2からはreachできる状態だった。

ところが12.3Rのntpdはst0がなくなるとstratum=16になり、st2は参照しなくなる。

結構大きな変更な気がするが、大丈夫なのかな。

JJY停止時

```
% ntpq -p 10.0.1.18
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*10.0.1.22       .GPS.            1 u   19  128  377    0.929   -2.156   0.313
+10.0.1.20       .GPS.            1 u   72  128  377    0.956   -2.210   0.373
+10.0.1.19       .JJY.            1 u   19  128  377    0.958   +3.454   2.154
 10.0.1.8        .INIT.          16 u 1044 1024    0    0.000   +0.000   0.000
```

JJY復旧時

```
% ntpq -p 10.0.1.18
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*10.0.1.22       .GPS.            1 u   68  128  377    0.929   -2.156   0.313
+10.0.1.20       .GPS.            1 u  121  128  377    0.956   -2.210   0.373
+10.0.1.19       .JJY.            1 u   68  128  377    0.958   +3.454   2.154
 10.0.1.8        .JJY.            1 u   27 1024    1    1.015   +5.040   0.015
```

システム情報

```
# ntpq -crv 10.0.1.8 
associd=0 status=c0e8 leap_alarm, sync_unspec, 14 events, no_sys_peer,
version="ntpd 4.2.8p15-a (1)", processor="mips",
system="FreeBSD/12.3-RELEASE", leap=11, stratum=16, precision=-16,
rootdelay=0.000, rootdisp=0.000, refid=.,
reftime=e5625cef.02fb6915  Tue, Dec 14 2021  8:59:43.011,
clock=e562c786.8d0c5a11  Tue, Dec 14 2021 16:34:30.550, peer=0, tc=6,
mintc=3, offset=+3.128440, frequency=+73.034, sys_jitter=0.000000,
clk_jitter=3.649, clk_wander=0.012
# ntpq -crv 10.0.1.19
associd=0 status=00c8 leap_none, sync_unspec, 12 events, no_sys_peer,
version="ntpd 4.2.8p11-a (1)", processor="mips",
system="FreeBSD/12.0-CURRENT", leap=00, stratum=1, precision=-16,
rootdelay=0.000, rootdisp=417.748, refid=JJY,
reftime=e5625cd2.0591cb22  Tue, Dec 14 2021  8:59:14.021,
clock=e562c78c.a6943841  Tue, Dec 14 2021 16:34:36.650, peer=0, tc=6,
mintc=3, offset=-6.961388, frequency=+52.459, sys_jitter=0.000000,
clk_jitter=5.881, clk_wander=0.016
```

2022/3に4年に一度くらいの停電があり、復旧が面倒だったので、自動起動するようにしました。

https://qiita.com/yamori813/items/7328d19399cee562f047

たまに固まることがあったのでwatchdogを使うようにしました。