google-site-verification: google3bd66dd162ef54c7.html
FC2ブログ

GPSとArduinoでRTCの誤差(確度)を精密測定

◆まえがき
久しぶりの更新です。GPSモジュールの1PPS信号を使ってRTCの精度をいろいろ調べているのですが、こういう時間差の測定はユニバーサルカウンタを使うと簡単に出来ます。でも、ちゃんとしたユニバーサルカウンタは中古でも結構な値段がするし、あまりしょっちゅう使う測定器でも無いので、持っている人は少なそうです。

◆Arduinoで時間差測定
そんなことから、おなじみの Arduino で時間差の測定をやってみました。出来るだけ時間分解能を上げたいのですが、それを回路でやるのは大変です。ということで、ソフトによる割り込みだけでやってみることにしました。Arduino の時間管理の粒度は4μsなので測定結果は4μsステップの値しか得られません。でもソフトによる後処理で、出来るだけ分解能を上げる方向でやってみることにしました。

なお、ATmega328P に内蔵されているカウンタを使えば、もっと分解能を上げることが出来るのかも知れません。どなたかご存じないでしょうか。

・回路図
回路図
回路はたったこれだけです。右側から RTC と GPS の1秒パルス (1PPS) を入力し、時間差と時間差の変化率を左側のディスプレイに表示するようになっています。

・実際の基板
RTC時計と時間差測定モジュール
左側の Arduino UNO は、前に作った RTC(DS3231) を使ったデジタル時計。右側がこの記事で紹介する時間差測定回路です。

・GPSは窓際に
GPSは窓際に
窓際にGPSモジュール(u-blox M8N搭載モジュール)を置いて、ケーブルで1PPS信号を引っ張ってきました。なお、一旦 GPS をロックすると窓際からかなり離しても大丈夫です。

◆プログラム
/*
GPSとRTCの位相差変化の測定プログラム
位相差をms単位、小数点以下3桁表示。変化率をppmで表示
2019/9/19 ラジオペンチ
*/
#include <Adafruit_SSD1306.h> // OLED表示ライブラリ

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)

Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

volatile boolean state = false;
volatile unsigned long Tgps, Tclock, oldTgps, oldTclock; // 時刻の記録変数
char buff[10]; // 文字列操作バッファ
unsigned long diff;
float diffMs;
float data[60]; // 過去データー保存バッファ(近似直線計算に使用)
float aveSlope;

void setup() {
Serial.begin(115200);
pinMode(13, OUTPUT);
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C); // アドレス 0x3C (0x78) [最初の記事のコード0c3Dになっていたので修正]
oled.setTextColor(WHITE); // 白文字で描く
oled.setTextSize(2); // 2倍角文字で表示
oled.clearDisplay();
oled.print(F("Start")); // 開始メッセージ
oled.display();

for (int n = 0; n <= 59; n++) { // 記録領域クリア
data[n] = 0.0;
}

attachInterrupt(0, gpsIRQ, RISING); // GPS割込み(立ち上がり), Pin2
attachInterrupt(1, clockIRQ, FALLING); // 時計割込み(立下り), Pin3

while (state == false) { // 時計からの割り込みが入るまで待つ
}
state = false;
}

void loop() {
while (state == false) { // 時計からの割り込みを待つ
}
state = false;
digitalWrite(13, HIGH);
diff = Tclock - Tgps; // GPSに対する時計の時間差を計算。70分に一回破綻(オーバーフロー)
diffMs = diff / 1000.0;
aveSlope = calcSlope(diffMs);

oled.clearDisplay();

dtostrf(diffMs, 7, 3, buff); // 全体7桁、小数点以下3桁の文字列に変換
Serial.print(buff); // 時間差を表示
oled.setCursor(0, 0); // 時間差をOLEDに
oled.print(buff);
oled.print(F("ms"));

dtostrf(aveSlope, 6, 3, buff); // 全体7桁、小数点以下3桁の文字列に変換
Serial.print(", ");
Serial.println(buff);
oled.setCursor(0, 20); // 時間差をOLEDに
oled.print(buff);
oled.print(F("ppm"));

oled.display();
digitalWrite(13, LOW); // ループの処理時間=52ms
}

float calcSlope(float d) { // データーの傾きを計算して戻り値として返す
float sumXY = 0.0;
float sumX = 0.0;
float sumY = 0.0;
float sumX2 = 0.0;
float a;
static int dN = 0;

for (int n = 58; n >= 0; n--) { // 一つ後ろにずらして先頭を空ける
data[n + 1] = data[n];
}
data[0] = d; // 先頭には最新データーを保存

// 最小二乗法で近似直線の傾きを求める
for (int n = 0; n <= 59; n++) {
sumXY += n * (data[n] - data[0]); // ∑xy (オーバーフロー防止のためyの値は先頭データーからの相対値)
sumX += n; // ∑x
sumY += data[n] - data[0]; // ∑y (オーバーフロー防止のためyの値は先頭データーからの相対値)
sumX2 += n * n; // ∑x^2
}
a = (60.0 * sumXY - sumX * sumY) / (60.0 * sumX2 - pow(sumX, 2)); // 最小二乗法で傾きを求める

a = -1000.0 * a; // データの保存順が逆なので符号反転, ms→ppm のために1000倍

if (dN <= 59) { // データーが全部揃っていなかったら
dN += 1; // カウンタをインクリメント
a = 9.999; // aの値を未計算表示用の値に差し替え
}
return a;
}

void gpsIRQ() { // GPSの割込み処理
oldTgps = Tgps;
Tgps = micros();
}

void clockIRQ() { // 時計からの割込み処理
oldTclock = Tclock;
Tclock = micros();
state = true;
}
Arrduinoで標準関数として用意されている 割込み(attachInterrupt関数)を使って時間差を求めているだけのプログラムです。

一番の工夫は、calcSlope 関数の中でやっている、最小二乗法による近似直線の傾きの計算です。データーは4μs刻みの値しか得られませんが、ある程度データーをまとめて分析すると、細かい傾向が見えてきます。今回のプログラムでは60個のデーター、つまり1分間のデーターを分析することで、0.02μs/毎秒 くらいの変化率を検出することが出来ました。

・結果の表示
表示
この時の時間差は334.116msで、変化率は-0.033ppm だったことを示しています。なお、±0.02ppmくらいは表示される値がばらつきます。でもこれは時計の誤差に換算すると、月差0.05秒程度にすぎないので、あまり気にしないでも良いと思います。あと、同じ情報をシリアルに流しているのでPCで記録することが出来ます。

なお、もっと小さな誤差を知りたい場合は、時間差の変化を長時間観察すれば可能になります。

◆まとめ
簡単な回路ですが、これを使うと GPS と RTC の時間差の変化が詳しく判ります。つまり、RTC の精密な調整が可能になります。

今回、表示には手持ちが沢山ある、0.96インチの128x64画素のOLEDを使いました。ただ、このデバイスは画像バッファとしてメモリーを1kバイトも消費するので、肝心な測定値を保存する配列のサイズをあまり大きく出来ないという、嬉しくない状態になっています。ちなみに、60回分のデーター、つまり240バイトしかバッファに使うことが出来なくて、何だか本末転倒状態です(RAMは2kバイトあります)。キャラクタ表示のOLEDで16文字4行くらいの物が(安く)あれば良いです。

あと、記事中に書きましたが、ATmega328に内蔵のハードウエアカウンタをうまく使うことで測定の分解能をもっと上げる手は無いものでしょうか。ただ、仮に0.1μの分解能が必要だとして、最長の測定時間は1秒必要なので、24ビットカウンタが必要になる訳で、これはちょっと難しいかも知れません。
関連記事

コメントの投稿

管理者にだけ表示を許可する

タイマー1のインプットキャプチャ機能で

タイマー1(16bit)のインプットキャプチャ機能を使いましょう。
しかし、1入力(ICP1:PB0)しかないので、2つのエッジ入力から位相差を得るハードが必要です。
 ※概略図、書いてみます。 ちょいお待ちを
RTC、GPSの信号エッジで2つのフリップフロップをたたき、ゲートで位相差信号を得ます。

これの↑↓でインプットキャプチャして、タイマー1のカウント値
からパルス差を計算します。

タイマー1のイニシャル。
クロックは16MHz。
アウトプットコンペア(リセット機能)は使わずフリーランで。

オーバーフロー割り込みを有効に。
カウンタは16bitなので、オーバーフロー検出でソフト的な上位桁カウンタをインクリメント。
24bit(タイマー1のハードウェアカウンタ16bitとソフトで8bit)あれば16MHzクロックで1秒を計時できるかと。

↑エッジ検出(ICES1=1)にして、インプットキャプチャ割り込みを有効に。
まず↑エッジ検出でICR1とオーバーフローカウンタの値を保存。
割り込みの抜けしなにエッジ検出を↓に。(ICES1=0)

次回の↓エッジ検出割り込みで、新たに読んだICR1の値からさっきの値を引けば、パルス幅が出てくるので保存。
ICES=1にして次の↑エッジを待つ。

↑(H)↓(L)↑(H)↓とサイクリックに読むと、(H)+(L)で1秒周期の確認ができます。

注意:入力パルス(RTC、GPS)のエッジが同時に入ると、ちゃんと読めない。

外付け回路が必要ですが、これで16MHz分解能で計測できるかと。

ゲート信号の回路案

http://igarage.cocolog-nifty.com/blog/2019/09/post-f63346.html
こんなので。

割り込みで操作する2バイト以上のデータをメイン側で読み書きする時は、「割込禁止状態」にしておかなくてはなりません。
面倒でも、読み書きの前後にcli()、sei()が必要です。
AVRマイコンのメモリーアクセスはバイト単位ですんで、2バイト以上のデータを読み書きしてる途中に割り込みが入ると、間違った値になるかもしれません。

re:タイマー1のインプットキャプチャ機能で

居酒屋ガレージ店主(JH3DBO)さん、詳しい解説ありがとうございます。

なるほど、インプットキャプチャ機能を使うんですね。タイマーあたりの使いこなしが十分理解できていないので良い勉強になりそうです。今度やってみたいと思います。

あとアトミックアクセスですね。これ忘れてて、たまに変なデーターが出て来て慌てるんですよね、

ロジックICは7400と7404くらいしか持ってないので、組み合わせてD-FF作ろうかと。その前にまずは、パルス幅の測定が正常に出来るようにソフト作るのが先ですね。

No title

とても興味深い内容なので、早速、RTCの誤差測定をさせてもらっています。

その中で、スケッチの25行目にあるOLEDのアドレスが0x3Cのところ0x3Dとなっているのに気づきました。(当初、何も表示されないので・・・)

ただ、私が持っているDS3231では、表示される値が目まぐるしく変化するのですが、これって、偽物なのでしょうか?
(ICの刻印等はラジオペンチさんの画像とは異なっていますが、温度の表示間隔は64秒ごとなのですが・・・)

まっくんさん、おはようございます

同じことを試して頂いてありがとうございます。

OLEDのI2Cアドレスは使ったモジュールのアドレスに合わせているのでこういう違いが出ちゃいます。でも、ブログに書くならデフォルトの値で公開したほうが良かったです。

お尋ねの表示値がパラパラと変わる件ですが、おっしゃるようにRTCが偽物なのかも知れません。
正常な物をきっちり合わせた状態では、位相差の表示は、たまに4μステップで変わるくらいの状態になります。

あと、GPSの1PPSがちゃんとしている(ジッタが少ない)かも心配です。でもこれを確かめるのは測定器が必要になって面倒です。

PS:DS3231関係の記事が増えたので、新たにカテゴリを作ってそちらに分類しました。そんなことで、記事の見え方(表示順など)が変わっています。

GPSモジュール間では?

現在は「GPS vs RTC」ですが「GPS#1 vs GPS#2」と、GPSモジュールを二つ使うとどうなるか・・・
  同型番、異型番、異メーカーだと?
気になります。

re:GPSモジュール間では?

そうですね、どんなデーターになるんでしょうね。

ちなみに、GPS-ルビジウムならこんな感じでした。
http://radiopench.blog96.fc2.com/blog-entry-941.html
手書きで記録した原始的な測定ですが、振れ幅は200nsくらいでしょうか。

時間分解能10nsで毎秒の測定結果が記録出来ると、見えなかった世界が見えてくるのかも知れません。

追記

上のリンク先のグラフは10秒間のアベレージなので、生のジッタより小さく観察されていると思います。
カレンダー
10 | 2019/11 | 12
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード