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

Arduino のタイマー1 インプットキャプチャでパルス幅を精密測定

◆まえがき
そろそろルビジウムオシレーター(以下ルビジウムと略します)の校正をやりたい時期です。前回(2年半前)校正を行った時は、ユニバーサルカウンタを使いその値を手書きのメモに残すという形で行いましたが、今回はもう少し自動化したいと考えました。

ルビジウムの誤差を確認するにはGPSの1PPSパルスとの位相差を測定するのが一番簡単だと思います。その方法としてはインターフェイスに出力が出せるカウンターを使うのが一番確実だと思いますが、そういう測定器は持ってません。

ということでArduino UNOで何とかすることにしました。

◆Arduinoでパルス幅測定
Arduinoで精密なタイミングの測定を行う場合、タイマー1のインプットキャプチャ機能を使うのが良いそうで、居酒屋ガレージ日記さんがプログラムを公開されています。また、今回のような、2つのパルスエッジからパルス間隔ゲート信号を作る、時の回路も教えて頂いています。

ということで、そのままコピペして作れば良いのですが、それではつまらないので自分なりの解釈で回路とプログラムを考えてみました。

◆回路図
タイマー1インプットキャプチャーを使ったパルス幅測定回路
GPSとルビジウムから出ている1PPSの信号(正パルス)をU3, U4のRSフリップフロップで1本の信号にまとめ、UNOのD8(ICP1)に入力し、その時間差をパルス幅として測定しています。

1PPSの信号はパルス幅が100ms程度あり、そのまま入力するとRS-FFの禁止状態(両方Low)になることがあるので、入り口のCRで微分しています。U1,U2はシュミットトリガ入力にしておいた方が良かったかも。

◆プログラム
// ATmega328Pを使ってパルス幅を測定 20220514_PulseWidthMeasure
// D8に信号を入力 Timer1 インプットキャプチャで測定。オーバーフローのミスカウントをソフトで対策
// 2022/05/15 ラジオペンチ http://radiopench.blog96.fc2.com/

volatile int       irqFlag  = 0;           // タイマー1インプットキャプチャ割り込みフラグ
volatile uint16_t  ovfCount = 0;           //         オーバーフローカウンタ
volatile boolean execRising = true;        // 波形測定方向フラグ
volatile uint16_t t1, t2, t3;
volatile uint16_t t2MSB, t3MSB;

void setup() {
  Serial.begin(115200);

//  タイマー1をインプットキャプチャに設定
// (参照:http://igarage.cocolog-nifty.com/blog/2020/08/post-ba853b.html)
    TIMSK1 = 0b00100001;
    //           |  ||+---  TOIE1 オーバーフロー割込on
    //           |  |+----  OCIE1A
    //           |  +-----  OCIE1B
    //           +--------  ICIE1 キャプチャー割込on
    TCCR1A = 0b00000000;
    //         ||||  ++---  WGM 標準動作
    //         ||++-------  COM1B
    //         ++---------  COM1A
    TCCR1B = 0b00000001;
    //         || ||+++---  CS 16MHz入力(下位3ビットでプリスケーラー設定)    
    //         || ++------  WGM
    //         |+---------  ICES1 ICP1入力↓エッジ(プログラム内で動的に変更)    
    //         |----------  ICNC1 ノイズキャンセルなし
  startUp();
}

void loop() {
  uint32_t  tw, tp;           // クロック数で表したパルス幅、周期
  uint32_t last_tw, last_tp;
  float     t_tw, t_tp;       // パルス幅、周期
  while (irqFlag == 0) {      // IRQフラグが立つまで待つ
  }
  irqFlag = 0;

  tw = ((uint32_t)t2MSB << 16) + (uint32_t)t2 - (uint32_t)t1; // パルス幅の計算
  tp = ((uint32_t)t3MSB << 16) + (uint32_t)t3 - (uint32_t)t1; // パルス周期の計算

  if((tw - last_tw) > 30000) tw -= 65536;  // TC1オーバーフローミスカウント対策の暫定対策
  if((last_tw - tw) > 30000) tw += 65536;
  if((tp - last_tp) > 30000) tp -= 65536;
  if((last_tp - tp) > 30000) tp += 65536;
  
  t_tw = tw * 62.5E-9;
  t_tp = tp * 62.5E-9;
  Serial.print(t_tw, 9); Serial.print(", "); Serial.print(t_tp, 9);
  Serial.println();
  t1 = t3;              // 次回の測定のためにパルスの先頭位置を記録
  last_tw = tw;
  last_tp = tp;
}

void startUp() {
  Serial.println();
  Serial.println("Pulse measurement start");
  Serial.println("width, period");
  for (int i = 0; i < 3; i++) {      // 変数が落ち着くまで空廻し
    while (irqFlag == 0) {           // IRQフラグが立つまで待つ
    }
    irqFlag = 0;
    t1 = t3;
  }
}

ISR(TIMER1_OVF_vect) {      // Timer1オーバーフロー割り込み処理
  ovfCount ++;
}

ISR(TIMER1_CAPT_vect) {      // Timer1 インプットキャプチャ割り込み処理
  uint16_t t;
//  sei();                     // 途中でovfCountの値が動くと困るので割り込み禁止で処理(これは逆効果)
  t = ICR1;
  if (execRising == true) {  // 立ち上がりの処理だったら、
    t3 = t;                  // パルス周期測定のための立ち上がり時刻として記録
    t3MSB = ovfCount;
    ovfCount = 0;            // オーバーフローカウンタをクリア
    TCCR1B &= 0b10111111;    // 次回はネガエッジでキャプチャ(ICES1=0)
    execRising = false;      // 次回は立下りの処理
    irqFlag = 1;             // フラグセットしてメインに通知
  } else {                   // 立ち下がりの処理だったら
    t2 = t;                  // 立下り時刻として記録
    t2MSB = ovfCount;
    TCCR1B |= 0b01000000;    // 次回はポジエッジでキャプチャ(ICES1=1)
    execRising = true;       // 次回は立ち上がりの処理
  }
//  cli();
}
上の方でリンクを貼った居酒屋ガレージ日記さんの記事中にリンクがある、duty_checker1.c のコードを参考にさせていただきました。動作説明も詳しく書かれているのでそちらを見て頂くのが宜しいかと思います。

パルス1発毎に、そのパルス幅と周期をシリアルに出力するようになっています。カウンターのクロックは16MHzなので分解能は62.5nsになっています。

16MHzのクロックなので16ビットカウンターは4.096msでオーバーフローし、測定周期は1秒なのでその間に244回オーバーフローが発生することになります。

41,42行は割り込み禁止状態にして実行した方が良さそうですが、実行されるタイミングは固定されているのでこのままでも大丈夫か?

パルス幅の測定は16ビットカウンタのオーバーフロー回数とカウンタの値を合成して計算する必要があります。そのあたりの処理を居酒屋ガレージ日記さんは巧妙に処理されているのですが、一部で理解出来なかったところがあったので、私のプログラムは対症療法的な処置になっているところがあります(44-47行目)。毎回大きな変動がある場合はこういうズルしちゃいけませんが、とりあえず臭い物には蓋をしておいて話を先に進めることにしました。

ちなみに、もしカウンターの値が0xFFFFから0x0000に変化した瞬間にインプットキャプチャの信号が入った場合、割り込みの優先度からインプットキャプチャ割り込み処理 (TIMER1_CAPT_vect) が先に実行されます。その時点ではオーバーフロー割り込み(TIMER1_OVF_vect) はまだ実行されていないので、そのあたりの辻褄合わせをしておく必要があるということだと思います。

◆測定結果
1秒毎にパルス幅と周期の値をシリアルに流しており、パルス幅はGPSに対するルビジウムオシレーターの位相差を表わします。つまりパルス幅の変化でグラフを描けば位相差の変動、つまりルビジウムの誤差が見えてくるはずです。

しかし、実際にやって見るとArduinoのクロックの変動の影響の方が大きくてルビジウムの誤差がはっきりと見えて来ませんでした。なお、位相誤差は累積するので何日も測定を続ければ見えてくるはずです。

幸いパルスの周期の値(tw)も測定しているので、パルス幅(Tp)をパルスの周期(tw)で割ることでGPSの精度でパルス幅の値を得ることが出来ます。

そういう計算を行って作成したのが次のグラフです。

・ルビジウムの位相変化
測定結果
右肩上がりになっているのでパルス幅は広がっています。

近似直線の傾きは2E-5μsなので、1秒毎に 2E-11秒ルビジウムの位相が遅れていることになります。つまり、10MHzのクロックの出力は 0.2mHz低かったというのがとりあえずの結論になります。

グラフのばらつきが大きいのがちょっと不満です。原因として考えられるのは、
1) 測定クロック周期が62.5nsなのでその分は量子化誤差が出る。
2) GPSの1PPSのジッタの公称値は30nsなのでその分はばらつきが出る。
3) 波形のトリガ精度が悪い(ノイズの影響などがある)
4) 1秒間の中でのArduinoのクロック周波数の変動(電源電圧変化他)
5) ルビジウムの変動?、GPSの変動?

◆まとめ
プログラムの一部で手抜きのところがありますが、これでルビジウムの誤差の変化を何とか自動測定出来るようになりました。

このままもう少し測定して様子を見たいと思います。

◆追記
この記事を書く前に行った予備調査の状況です
1)インプットキャプチャの動作確認
2)ばらつきがひどいので温度変化防止のために箱に入れたけどダメ
3)ボード(Arduino NANO)のクロック精度が悪かったことが判明
4)UNOの互換品でうまく測定出来るようになった。入力はパルジェネで作った幅100msのパルス

関連記事

コメントの投稿

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

UNOの発振子

Arduino-UNO R3のATmega328P、クロックは「セラミック発振子」です。
ムラタの「CSTCE16M0V53-R0」。
このあたりも影響するのではと。

※UNOの回路図に記された型番を信じてですが、このタイプはもう廃番だと。
同形状の別型番のに置き換わっているようです。

https://www.murata.com/ja-jp/products/productdetail?partno=CSTCE16M0V53-R0

・2019年12月20日 Arduino UNOで周波数カウンタ
では、UNOの発振子をXTALに交換しました。

http://igarage.cocolog-nifty.com/blog/2019/12/post-8ded09.html

re:UNOの発振子

すみません、本文にはUNOと書いてますが、実際に使ったのはUNOの互換品でXtalオシレーター品です。(両側に負荷容量コンデンサが入っていてXtalにパラに1MΩが入っていることを確認しています)

この記事を書く前に、タイマー1インプットキャプチャ機能の動作確認を兼ねて精度確認をやった結果があるので後で追記しておきます。

re:UNOの発振子

なるほど。 互換品で「水晶」を使ったのがあるんですな。

インプットキャプチャ機能によるパルス幅測定、問題は「短パルス」。
ず~っとLが続いて一瞬だけH。
あるいはHが続いて一瞬だけL。
これを正確にキャッチするのがむつかしい。
STM32やRXマイコンのように高機能なタイマーが欲しくなります。

re2:UNOの発振子

この記事のプログラムでやっているミスカウント対策は不十分なようで、たまに変な値が記録されていました。
その値は32ビットカウンタがオーバーフローする268秒付近の値なので怪しそうなところは想像が付くのですが、データー取りを優先してそのまま走らせています。

そんなことで、居酒屋ガレージ店主さんが問題にされている短パルスの測定以前の問題が残っちゃってます。ルビジウムの校正が終わったら手を入れてみようかと思ってます。

Arduino MEGAだと

インプットキャプチャできる16bitタイマーを複数積んでいる「MEGA」だと、2つのタイマーを同じクロック源で同時に走らせ(これがやってみないとわからんけど。カウントがずれてもokか?)、2つのパルスの↑・↑エッジでキャプチャさせて、その差から位相ずれを見るということができそうです。
パルスはそれぞれを直接入れればよく、エッジの時間差を作るゲート回路は不要。
割り込み内での↑↓エッジの切り替えも不要になり、回路も処理もシンプルになるかなぁっと。

re:Arduino MEGAだと

なるほどMega2560ですね、そういう仕様なら思いっ切り狭いパルスが来ても大丈夫そうですね。

AVR64DB32マイコン

スイッチサイエンス「AVR64DB32マイコン基板」
https://www.switch-science.com/catalog/8143/

このあたりがArduino環境で動くようになればあれこれ便利に
なるのじゃないかと。

re:AVR64DB32マイコン

そうですね。
ただ、10年以上経ったのにATmega328Pの後継がこれかよ、という気もしますが、私が世間知らずなだけ?
カレンダー
02 | 2024/03 | 04
- - - - - 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
31 - - - - - -
プロフィール

ラジオペンチ

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

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