Arduino UNO R4 Minimaで周期を測定するレシプロカル周波数カウンタを作りました。
ダイレクトカウント方式の周波数カウンタとして Arduino UNO R4で周波数カウンタ では居酒屋ガレージ店主さんのブログ Arduino UNO R4互換:RA4M1コアボードで周波数カウンタ をヒントにしてライブラリ風にアレンジしてArduinoの周波数計測ライブラリ FreqCount と同じ使い方ができるようにしました。
ここでは入力パルスの周期をカウントして周波数を求めるレシプロカル方式の周波数カウンタを試してみました。基本的には入力をカウントして約1秒間隔のタイミングを作り、システムクロックのカウントをキャプチャーすることで周期を求めることが出来ます。1秒の計測時間に対する分解能はシステムクロック分の1すなわち1/48,000,000Hz = 2.1e-8秒になるので、全計測範囲で0.02ppmの分解能が期待できます。 今回はArduinoの周期計測ライブラリ FreqMeasure と同じような使い方が出来るようにしました。
具体的には16ビットカウンタGPT4を入力信号の分周器にして32ビットカウンタGPT1のキャプチャー信号を作って、キャプチャーで発生したインタラプトでGPT1のカウント値を読み取り、前回のキャプチャーでの読み取り値との差を求めます。16ビットカウンタで分周しているので、65536Hz以上の入力周波数でも約1秒を作れるようにソフトウェアでの分周も併用しています。ちょっと不思議なことにカウンタの周期を1 (設定値=0) にしても使えているのは良いのですが理由が分かりません。
キャプチャーによるインタラプトを登録する方法が分からなかったので、その元になるGPT4のオーバーフローでインタラプト処理をしてGPT1のカウント値を読み取っています。FspTimerでGPT4による1秒周期のインタラプトを設定しています。1秒は仮の設定で、後からレジスタ直接アクセスで設定値を変更しています。
D1ピンが周波数入力で、入力にアンプを付けることを想定してプルアップは外してあります。
分周数を決めるには入力周波数がどの位であるかを調べなければならないので、GPT4だけを使った簡易式の周波数カウンタで測定します。まず、1msのゲート時間で24MHzから1kHzの範囲で求め、500kHz以下だったら100msのゲート時間で655.350kHzから60kHzの範囲で、60kHz以下だったら1000msのゲート時間で65.535kHzから0Hzの範囲で再計測します。その結果を使って分周数を決めています。
測定周波数の上限ですが一般的にはクロック周波数48MHzの1/3の16MHzまでが安全ですが、20MHzまで何とか測れます。ただしデューティー比が50%に近い必要があります。R4自身で発生したCPUクロックに同期した信号なら限界の24MHzでもきっちり測れています。 下限はタイムアウトを5秒に設定しているので0.2000000Hz程度になります。このように低い周波数でも有効数字7桁程度が確保できるのがレシプロカル方式の特徴です。ただし、計測時間を短くすると分解能は悪くなります。
測定時間はPeriodCount.begin(msec)のmsecで設定できます。msecは1msから65535msまで設定できます。しかし、あくまでも目標の時間であって測定時間に比べて低い入力周波数では1周期の測定時間になります。24MHzの周波数に対しても178秒までは32ビットカウンタが折り返さないので、計測値の積算はしていません。
測定時間はPeriodCount.gatetime(msec)のmsecで変更できますが、PeriodCount.adjust(freq)呼出し後に反映されます。
開発環境は
Arduino IDE 1.8.19 + Arduino UNO R4 Boards by Arduino version 1.4.1です。
ボードはArduino UNO R4 Minimaを指定します。
Arduino IDEのシリアルモニターに結果を表示します。 PwmOutクラスを使ってD10ピンに9876.543210Hzを出してテストできるようにしました。 ピンD10とD1を接続してシリアルモニタに1秒毎に 9876.543210 と表示されれば動作OKです。 ちなみに、この状態ではGPT3が使われています。
(2025.06.12 update) 中華互換機の中には16MHzの水晶が実装されている物があるので、それを有効にするコードも入れてあります。#define EXT_XTAL 16 のコメントを外せば水晶を使うようになります。12MHzの水晶の場合は #define EXT_XTAL 12 のコメントを外せば良いようにしました。 Arduino UNO R4 互換機のオンボード16MHz XTALをシステムクロックにする を参照してください。
period_count.zip (2025.06.12 update)
Arduino UNO R4のシステムクロックは内蔵CR発振器が使われていて、一般的な水晶発振器に比較して精度も悪く、不安定です。ただしPCとUSB接続している場合は水晶並みの精度が出ているという話があります。しかしジッターは多いようです。精度にはあまり期待しない方が良いと思います。
純正のUNO R4 Minima基板には水晶実装用のパッドがあるのでそこに水晶発振子を載せることもできますが、そのままでは使われないので別途ソフトで設定を変更する必要があります。また、中華互換機の中には16MHzの水晶が実装されているものがあります。こちらも別途ソフトで設定を変更する必要がありますが、16MHzの水晶では仕様範囲外になってしまいます。この辺の話はReferencesを参照してください。いずれにしても一般的な水晶の精度は20ppmから50ppm程度ですので、付加するコンデンサを調整しなければ精度を上げることができません。TCXOに置き換えることが出来れば1ppm以下に出来ますが、UNO R4でそこまでする必要はないと思います。
ということで、計算で補正してしまった方が楽です。例えば正確な16MHzを測定して16.000123MHzになったら、
freq = (double)freq * 16.0 / 16.000123;
と補正してしまえばとりあえず補正出来ます。それでも温度変動には無力で、UNO R4の水晶発振回路の性能次第になります。
周波数偏差をソースコードで修正するのは面倒なので、例えば基準周波数の16MHz, 12MHz, 10MHz, 8MHz, 1MHzと100ppm以内の誤差ならばCalibrationボタンを押して自動的に補正して補正値を保存するようにすることも考えられます。これは STM32F103C8T6で192MHz周波数カウンタ を参照してください。
analogWriteやPwmOutクラスでPWMパルスを出力する場合、ピン番号によって使われるGPTが以下のように変わります。したがって、GPT1に対応するD2, D3, D11, D12へPWMを出力するかGPT4を別の目的に使うプログラムと競合します。
Pwmピン | D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9 | D10 | D11 | D12 | D13 | A4 | A5 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
使用GPT | GPT4 | GPT4 | GPT1 | GPT1 | GPT2 | GPT2 | GPT0 | GPT0 | GPT7 | GPT7 | GPT3 | GPT1 | GPT1 | GPT3 | GPT5 | GPT5 |