Computer & RF Technology

GPSのPPS信号を基準にOCXOの安定度を観察してみる

以前さわりだけご紹介したGPS基板ですが、GPSを周波数源として活用したいというのがそもそもの目的でした。GPSモジュールはPPS信号=1秒毎のパルスだけではなく設定で任意の周波数を出力可能なのですが、実はその信号は周波数源としては安定度/信号純度が後に述べる理由から全く不十分なのです。

そこで、GPSを基準に使って、安定で純度の高い信号が得られるOCXOを制御するGPSDOを作ろうとしています。が、その前段階としてGPSのPPS信号を基準にしてOCXOをカウントしてみました。実はこの実験はすでに一年前にやっていてその後放置していたのですが、ようやく再開することにしました。

GPSは手軽に得られる時刻源です。GPSはその原理上、正確な時刻と周期が得られます。GPSの電波が受信できる環境であれば、GPSモジュールからはPPS信号(Pulse Per Second)として正確な1秒毎のパルスが得られます。

一方、安定な発振器としてOCXO (Oven Controlled Xtal Oscillator)があります。比較的大型の部品で、ケース内にヒーターが組み込まれていて、内部の発振器が最適な温度で動作できるよう精密に温度制御されています。ヒータのため比較的消費電流が大きく、また温度が安定するまで一定の時間を要します。さらに精密な制御をするために、オーブンが2重になったDOCXO(Double Oven Controlled XO)というものも使われています。

さて、周波数源=時刻源としては正確さと安定性の両方が求められます。正確さとは絶対値の誤差が少ないこと。安定性とは周波数が揺れ動かないことです。OCXOは周波数の揺れはとても少ないのですが、周波数の絶対値の正確さは十分ではありません。ただし、揺れが少ないとは言っても、長い期間でみるとわずかにドリフトがあります。一方、GPSから得られる時刻は、衛星の観測状況の変化などにより短期的にはわずかに揺れ動くのですが、長期的には原子時計レベルの安定性を持っていると言われています。

さらにGPSモジュールから得られる信号の純度には限界があります。それはGPSモジュールそのものが内部に持つクロックに起因しています。GPSモジュールの出力はそのクロックに同期していて、そのクロック以上の時間精度は出ないからです。ubloxのモジュールの場合、その内部は48MHzのクロックで動作していて、出力されるPPS信号のクロックエッジは、本来のUTCの秒のエッジからは20nsのオーダーで差異が生じています。このためPPS信号にはわずかなジッターが生じており、周波数源としてみると少なくない位相雑音があるのです。(実はGPSモジュールからは(一部の品種に限って)、これを補正するための情報が出力されています。PPS信号が本来のUTC秒からどの程度ずれがあるかが、NMEA形式で出力されるデータから毎秒取得することができます。)

やりたいことは、短期的には安定だけれども、長期的にはイマイチな水晶発振器=OCXOを、短期的にはダメだけど、長期的には安定で正確な時刻源であるGPSを使って調律(Discipline)することです。互いにいいとこ取りをして、短期的にも長期的にも安定な発振器にしようということです。このような発振器をGPSDO(GPS Disciplined Oscillator)といいます。(Z3801などが有名ですね)

そのための実験として、GPSのPPS信号を使って、OCXOの10MHzをカウントしてみて、どのくらいの安定度があるのか観察してみました。

使ったGPSモジュールはubloxのLEA-6Tです。Tという型番はTime Precisionタイプのモジュールであることを示しています。通常のモジュールに比べて、時刻に関する機能が追加されています。

実験用に作ったGPS基板には、MCUとしてPIC18F14K50を載せています。PICのUSBインターフェースを使ってCDCデバイスを構成し、データをPCに取り込めるようにします。またI2C LCDを載せてGPSの動作状況を表示するようにします。OCXOからの信号をペリフェラルを使ってカウントし、PPS信号をトリガーにカウント値をサンプリングします。これらをPICのファームウェアで実装します。

いろいろミスがありますが、回路図です。リンク

この基板にはGPSモジュール並びにPIC、二つのUSBコネクタがあります。どちらもCDCデバイスでPCからはシリアルインターフェースとして使うことができます。GPS側はNMEA 0183フォーマットでデータが出力されており、ubloxのツールu-centerを使うことで動作状況のモニタと各種設定を行うことができます。一方PIC側は、時刻と内部でカウントした値を独自に書き出しています。いずれもPCからはテキストで読み出すことができます。

GPSモジュールのIOは3.3VなのでPICもそれに合わせています。シリアルとPPS信号のTIMEPULSEをPICに接続しています。また、OCXOからは正弦波が出力されていますので、バッファを通してPICに入力します。OCXOには周波数制御端子があり、試してみたところ10Hzほどの調整幅があるようです。5Vの小型のタイプを使用しましたので、電源はUSBでOKです。

さて、実際に動作させるとこんな感じです。屋外に設置したパッチアンテナをアンテナ端子に接続、そして電源兼用でUSBをPCに接続します。しばらくすると測位が行われてLCDにステータスが表示されます。LCDにはアイコンがいくつか用意されているので、GPSの受信状況(測位の有無、DGPSと3D測位)やUSBの接続状態などを表示させています。その他時刻と受信衛星数、HDOPは水平方向の位置精度(m)、そしてOCXOの周波数が10MHzからのずれ(Hz)を表示しています。制御電圧は0V固定で調整していないフリーランの状態で、周波数下限の状態では10MHzから4Hz弱下回っているようです。

PIC側のUSBをCDC接続するとこんな出力が得られます。

20141011T075609 9999996  
20141011T075625 9999995  
20141011T075626 9999997  
20141011T075627 9999996  
20141011T075628 9999996  
20141011T075629 9999996  
20141011T075630 9999997  
20141011T075631 9999996  

毎秒1行出力され、最初がUTC時刻、後ろがOCXOのカウント値です。カウントはデッドタイム無しに全てをカウントしています。数値はタイミングによって1カウント前後しているのがわかります。

これをファイルに落としてグラフ化します。ファイルに落とすのはCDCのデバイスノードをcatで開いてリダイレクト、もしくはteeコマンドが使えます(OSX/Linuxの場合)。

$ cat /dev/cu.usbmodem26431 | tee gps-ocxo.log 

動作が安定してからデータを取ります。ある程度の期間観測することが必要ですから、一日から一週間程度取ってみました。

このデータを例によってIPython notebookでグラフ化してみます。IPython notebookを開き、matplotlibとnumpyを使うための準備として下記をセルに入力しておきます。

%matplotlib inline  
import pylab as pl  
import numpy as np

ファイルからデータを読み込み、2カラム目だけを取り出しておきます。

data = np.loadtxt('gps-oxco.log', dtype={'names':('time','count'), 'formats':('S15', 'i')})  
d = data\['count'\]

まずはそのままプロットしてみます。

pl.plot(d)

縦軸の表示がちょっとヘンですが、数値はカウント値で9999995~9999998の整数値です。真の値はこの間のどこかにありますが、整数値に振り分けられているわけです。横軸の単位は秒で86400秒=一日分です。

これではなんだかわかりませんので、移動平均を取ってみます。区間は600秒=10分です。

def movingaverage(values, window):
   weights = np.repeat(1.0/window, window)
   return np.convolve(values, weights, 'valid')
  
v1 = movingaverage(d, 600)
pl.plot(v1)

トレンドが見えるようになりましたが、これでもまだステップ状なので、さらにもう一度移動平均を重ねて掛けてみます。同じく区間は600秒です。

v2 = movingaverage(v1, 600)
pl.plot(v2)

だいぶ滑らかになりました。大きく揺れ動いているように見えますが、縦軸1目盛りは0.001Hz=1mHzですから、全体ではおよそ10mHz弱の変動があることがわかります。10MHzに対する10mHzですから10^-9すなわち1ppb(part per billion:10億分の1)のオーダーです。フリーランのOCXOとGPSのゆれを合わせた安定度がこの程度ということです。ということで安定度のオーダーはわかりました。

移動平均を掛けるとは、すなわちLPFを通したことになります。移動平均の期間が600秒ですからノッチが1/600Hzに来ます。カットオフ周波数はそれよりも下に来ることになります。例のインターフェース特集記事でも少し詳しく解説しましたが、移動平均はCICと同値の処理でその周波数応答はSINC特性です。移動平均を2重にかけたので2段のCICと同じになるわけです。一応念のためですが、ノッチの位置は変化せず、減衰量が倍になるわけです。

変動のグラフは取れましたが、これを解析してみたいのです。具体的には時刻源の評価によく使われているアラン分散として示してみたいのです。

少し探してみると、ちゃんとアラン分散を計算するためのAllanToolsというライブラリがPythonにありました。AllanToolsは下記のようにコマンドラインからpipでインストールできます。

$ sudo pip install AllanTools

インストール後、さきほど読み込んだデータで計算させてみるために、IPython notebookのセルに下記のように入力します。1Hzと10MHzの比較なので10MHzで除しています。

import allantools
\# tau values from 1 to 10^5
t = np.logspace(0, 5, 50)
f=1e7
(t2, ad, ade, adn) = allantools.mdev(d/f, 1, t)
pl.loglog(t2, ad, '.-')

それっぽいグラフが得られます。このグラフは修正アラン分散(Modified Allan Deviation)です。他にもいくつか種類があり、それぞれ特徴があるようです。

1秒ごとに1カウントの誤差があるので、左側τ=10^0=1sに向けて数値が大きくなっています。また長期的にも揺れがあるためか、右側も上昇しています。グラフの傾きから安定性を損なう原因を推測できるのだそうです。果たしてこれが正しい結果なのか謎ですが、これを理解するために、もう少し研究してみたいと思います。

引き続き、DACを駆動して実際に周波数を合わせる調律動作をさせてみたいと考えています。

実は反省点があり、MCUにPIC18F14K50を使ったのは大失敗でした。というのもNMEAの読み込みと解析はテキスト処理が必要なのですが、このサイズのPICではRAMが全く足りません。全部で768バイトしかなく、USBのためのバッファを確保してしまうと、ごく僅かしか残りません。PICの癖である256バイト境界の制限(バッファが跨げない)もあり、現状でもとても苦しい状況です。周波数調整のための実装はできたとしても見通しが悪くなってしまいそうです。PICのコンパイラも使いにくいですし、ここは能力に余裕のあるCortex-M0くらいを奢っておくべきでした。

というわけで、次回に続きます。

リファレンス

Load more