Raspberry Pi Picoの周波数カウンタには単純なゲート方式の Raspberry Pi Picoで周波数カウンタ を作りましたが、ゲート時間が1秒だと1Hz単位でしか周波数が分かりません。そこで、入力パルスの周期をカウントして周波数を求めるレシプロカル方式の周波数カウンタを試してみました。
最初に断っておきますが、この方式では約2kHz以下では16ビットカウンタのオーバーフロー処理対策が不十分なので時々変な値が出てしまいます。ゲート方式のカウンタの時に既存ライブラリにケチを付けたのがブーメランで帰って来ました。それでも2kHz以上では原理通りに機能するし、2kHz以下でもほとんどの場合は正しい値が出ます。
ここでも Accurate frequency measurement on the Pi Pico RP2040 using C という記事からヒントを得たDMAを使ったキャプチャーもどきの機能を利用します。
入力周波数の複数周期を基本的には133MHzのシステムクロックをSlice0の16ビットカウンタで数えます。このカウンタが1周するのは 133MHz/65536=2029Hz すなわち 0.49msec 掛かるので、それよりも短い間隔でキャプチャーして積算すればより長い周期を計測出来ます。 このキャプチャーするタイミングを入力周波数の何波分にするかをSlice3の16ビットカウンタのWRAP値Nで調整します。そしてその間隔を何回繰り返せば約1秒になるかをMとし、ソフトウェアでM回目の積算値をM*N波分の周期として周波数に換算します。
入力周波数が2029Hz以下では1波でも0.49msを超えてしまうのでSlice0のオーバーフローをソフトウェアインタラプトでカウントして合成したものの差分を取って1波の周期とします。キャプチャーしたカウントとオーバーフローをカウントした値のタイミングのずれを完全に解消するのは困難で、前述の様な変動が出ます。
カウントのキャプチャーを実現するためには入力周波数をN分周するスライス (図中Slice3) のWRAPイベントでDMAを起動してカウント値 (図中Slice0) を特定の変数 cap1 に転送します。転送はソフトウェアの流れに較べて瞬時に終了するので、WRAPのインタラプト処理で次のWRAPイベントが起こる前までにカウント値の差分を積算すれば良いことになります。
システムクロックの精度は通常50ppm以下ですが、Slice0のChannel Bからより精度の良いTCXOやGPSモジュールからの10MHzを入力することによって改善することが出来ます。TCXO は10MHzから50MHzまでの1MHz単位を想定しています。
測定周波数の上限ですが一般的にはクロック周波数133MHzの1/3の44.3MHzまでが安全ですが、65MHzまで何となく測れます。250MHzにオーバークロックすれば83.3MHzまでが安全で上限は125MHzです。300MHzにオーバークロックすれば100MHzまでが安全で上限は150MHzです。オーバークロックですがここまでダイレクトにカウントできるのはRasberry Pi Picoならではでしょう。
ゲート時間はPeriodCount.begin(msec)のmsecで設定できます。msecは1msから65535msまで設定できます。 ゲート時間は動作中でもPeriodCount.gatetime(msec)のmsecで変更できます。
開発環境は
Arduino IDE 1.8.19 + Raspberry Pi Pico/RP2040/RP2350 by Earle F. Philhower, III version 4.6.1です。
ボードは "Raspberry Pi Pico" または "Waveshare RP2040 Zero" を指定します。
ZIPファイルを展開したフォルダを library フォルダに置けばArduino IDEの「ファイル=>スケッチ例=>PeriodCountRPP」からサンプルプログラムを選択することが出来ます。
FreqMeasureRPP101.zip (update 2025.08.20)
回路図は「Raspberry Pi Picoで周波数カウンタ」と同じです。
Arduino IDEのシリアルモニターに結果を表示します。 PWMを使って14番ピンに44.3MHzを出してテストできるようにしました。 周波数入力ピンはGP7、基準周波数をカウントするスライスに対応するピン番号はGP0になります。
MAX7219を使用した8桁7-segment LEDに結果を表示します。 PWMを使って14番ピンに44.3MHzを出してテストできるようにしました。 周波数入力ピンはGP7、基準周波数をカウントするスライスに対応するピン番号はGP0になります。
SSD1306を使用した128x32のOLEDに結果を表示します。 PWMを使って14番ピンに44.3MHzを出してテストできるようにしました。 周波数入力ピンはGP7、基準周波数をカウントするスライスに対応するピン番号はGP0になります。
SSD1306を使用した128x32のOLEDに結果を表示します。大きい数字フォントで表示します。
PWMを使って14番ピンに44.3MHzを出してテストできるようにしました。
周波数入力ピンはGP7、基準周波数を入力するピン番号はGP1になります。
TCXOから基準周波数を入力します。TCXOの周波数は簡易式の周波数カウンタで測定して自動的に設定します。周波数は10MHzから50MHzまでなら使えるはずですが、1MHz単位に四捨五入しているので100kHz以下の端数があるものは使えません。TCXOを接続しない場合は自動的にCPUクロックに切り替えます。入力アンプには74HCU04のアンバッファインバータを使用しています。100MHz近くまで使えると思います。下手にFETやトランジスタを使うより簡単です。
写真は25MHzで0.5ppmのTCXOを使用してシステムクロックから1kHzのテスト信号を発生して計測したものです。 1000.0365HzとなりCPUクロックがTCXOの基準から36.5ppm高いということが分かります。
Raspberry Pi Picoのシステムクロックは水晶発振器が使われていて50ppm以内の精度に収まるようです。
計算で周波数を補正するには、例えば正確な16MHzを測定して16.000123MHzになったら、
freq = freq * 16.0 / 16.000123;
と補正してしまえばとりあえず補正出来ます。それでも温度変動には無力で、水晶発振回路の性能次第になります。
周波数偏差をソースコードで修正するのは面倒なので、例えば基準周波数の16MHz, 12MHz, 10MHz, 8MHz, 1MHzと100ppm以内の誤差ならばCalibrationボタンを押して自動的に補正して補正値を保存するようにすることも考えられます。これは STM32F103C8T6で192MHz周波数カウンタ を参照してください。
システムクロックをカウントしているスライスにchannel BからTCXOやGPS受信モジュールからの10MHz信号を与えればより精度を上げることが出来ます。測定時間1秒の時の基準周波数と分解能の対応は下表の通り。
周波数 | 分解能 | 有効桁数 |
---|---|---|
10MHz | 0.1ppm | 7桁 |
20MHz | 0.05ppm | 7桁以上 |
25MHz | 0.04ppm | 7桁以上 |
30MHz | 0.033ppm | 7桁以上 |
40MHz | 0.025ppm | 7桁以上 |
50MHz | 0.02ppm | 7桁以上 |
周波数入力ピンを変更するには以下の表を参照してGPIOとSliceとchannelを決定することになります。周波数入力ピンと基準周波数入力ピンはchannel Bにする必要があります。
GPIO | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
PWM channel | 0A | 0B | 1A | 1B | 2A | 2B | 3A | 3B | 4A | 4B | 5A | 5B | 6A | 6B | 7A | 7B |
GPIO | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ||
PWM channel | 0A | 0B | 1A | 1B | 2A | 2B | 3A | 3B | 4A | 4B | 5A | 5B | 6A | 6B |