Raspberry Pi Picoの周波数カウンタには A frequency counter library for the RP2040 が有るので期待していましたが、試しに使ってみると、やはりインタラプト処理によるソフトウェアでタイミングを取っているので、たまに変な値が出てしまいます。たまにしか起こらないので気にしなければ良いのですがやっぱり気になります。 下図では44.3MHzを測定してSerialに出力したものをArduino IDEのシリアルプロッタで表示したものですが、65KHz程上下に変動しています。これは16ビットカウンタのオーバーフロー処理をミスった結果です。
ネットでRaspberry Pi Picoの周波数カウンタについて調べていると Accurate frequency measurement on the Pi Pico RP2040 using C という記事でDMAを使ってキャプチャーもどきの機能を実現できることが分かりました。その記事の方法ではゲート時間を250msまでしか長く出来ないので、他の方法を使うことにします。
先ず、16ビットカウンタで60MHz程度までカウントするためにはどうすれば良いかという問題があります。単純に考えればオーバーフローをソフトウェアインタラプトでカウントして合成すれば良いのですが、キャプチャーしたカウントとオーバーフローをカウントした値のタイミングのずれを完全に解消するのは困難で、前述の様な変動が出ます。そこで、16ビットカウンタが一巡する前にキャプチャーしてしまい、前回のキャプチャーとの差分を積算すればタイミングのずれを気にする必要が無くなります。 1msec間隔でキャプチャーする場合、16ビットカウンタが一巡するのは65.535MHzなので65MHz程度まではカウント出来ることになります。500usec間隔なら131MHz、250usec間隔なら262MHzまで原理的にはカウント可能です。
カウントのキャプチャーを実現するためには先の等間隔のタイミングを作るスライス (図中Slice0) のWRAPイベントでDMAを起動してカウント値 (図中Slice3) を特定の変数 cap1 に転送します。転送はソフトウェアの流れに較べて瞬時に終了するので、WRAPのインタラプト処理で次のWRAPイベントが起こる前までにカウント値の差分を積算すれば良いことになります。
測定周波数の上限ですが一般的にはクロック周波数133MHzの1/3の44.3MHzまでが安全ですが、65MHzまで何となく測れます。250MHzにオーバークロックすれば83.3MHzまでが安全で上限は125MHzです。300MHzにオーバークロックすれば100MHzまでが安全で上限は150MHzです。オーバークロックですがここまでダイレクトにカウントできるのはRasberry Pi Picoならではでしょう。
ゲート時間はFreqCount.begin(msec)のmsecで設定できます。msecは1msから65535msまで設定できます。 ゲート時間は動作中でもFreqCount.gatetime(msec)のmsecで変更できます。
開発環境は
Arduino IDE 1.8.19 + Raspberry Pi Pico/RP2040/RP2350 by Earle F. Philhower, III version 4.6.0です。
ボードは "Raspberry Pi Pico" または "Waveshare RP2040 Zero" を指定します。
ZIPファイルを展開したフォルダを library フォルダに置けばArduino IDEの「ファイル=>スケッチ例=>」からサンプルプログラムを選択することが出来ます。
FreqCountRPP.zip (2025.08.03 更新)
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、ゲート時間を発生するスライスに対応するピン番号はGP0になります。
TCXOから基準周波数を入力します。TCXOの周波数は簡易式の周波数カウンタで測定して自動的に設定します。周波数は10MHzから60MHzまでなら使えるはずですが、100kHz単位に四捨五入しているので10kHz以下の端数があるものは使えません。TCXOを接続しない場合は自動的にCPUクロックに切り替えます。入力アンプには74HCU04のアンバッファインバータを使用しています。100MHz近くまで使えると思います。下手にFETやトランジスタを使うより簡単です。
写真は300MHzにオーバークロックして100MHzのテスト信号を発生して計測したものです。 100.003557MHzとなりCPUクロックがTCXOの基準から35.57ppm高いということが分かります。
Raspberry Pi Picoのシステムクロックは水晶発振器が使われていて50ppm以内の精度に収まるようです。
計算で周波数を補正するには、例えば正確な16MHzを測定して16.000123MHzになったら、
freq = (double)freq * 16.0 / 16.000123;
と補正してしまえばとりあえず補正出来ます。それでも温度変動には無力で、水晶発振回路の性能次第になります。
周波数偏差をソースコードで修正するのは面倒なので、例えば基準周波数の16MHz, 12MHz, 10MHz, 8MHz, 1MHzと100ppm以内の誤差ならばCalibrationボタンを押して自動的に補正して補正値を保存するようにすることも考えられます。これは STM32F103C8T6で192MHz周波数カウンタ を参照してください。
WRAPイベントを発生しているスライスにシステムクロックではなくchannel BからTCXOやGPS受信モジュールからのPPS信号を与えればより精度を上げることが出来ます。
周波数入力ピンを変更するには以下の表を参照して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 |