昨日ラズパイPico(RP2040)の周波数カウンタというものを使ってみました。同様な機能はSAMD51にも備わっています。こちらSAMD51での呼び方は Frequency Meter(FREQM)です。これを使い、外部端子から入力した信号の周波数を計測してみました。設定と適用の限界を見極めておれば、便利な機能であります。
※「IoT何をいまさら」投稿順Indexはこちら
実験のターゲットデバイスは SeeedStudio社のWio Terminal でVS Code + PlatformIOを開発環境とし、Arduinoプラットフォーム利用です。しかし、今回も実験対象の周辺回路を操作する部分ではArduinoの「高水準」な関数のお世話にはならず、直接SAMD51のハードウエア・レジスタを直接操作して動作を確認していきたいと思います。
頼りとするのは Microchip ATSAM51P19A からダウンロード可能なSAMD51のデータシートであります。
前回、SAMD51のタイマを外部クロックで動作させました。今回は、その時に設定した外部クロック入力を流用させていただきます。端子的には、Wio Terminal背面のRaspberry Pi互換端子のGPIO5番です。
SAMD51のFREQM
ラズパイPicoのFrequency Counter同様、SAMD51のFrequency Meterも、「いまどんな周波数で動作しているのかしら?」と確かめたくなったときに、内部のシステムクロックでも、外部信号でもその周波数を測定できる専用カウンタです。原理は簡単で、周波数が既知の参照クロック(REFクロック)の設定したサイクル数期間中、被測定信号のクロック数を数えるハードウエアです。被測定信号のクロック数を、設定の参照クロック数で割って、参照クロックの周波数を乗ずればお答えが出る、という塩梅。
今回は参照クロックとして外部32kHzのオシレータ(XOSC32)の32.768kHzクロックを使います(前々回は常に動作している内蔵32kHzクロックを測定してみましたが、外部のクリスタルが動作しているなら、その方が精度がよいです。)
実験としては以下のような設定です。
-
- XOSC32の32.768kHzクロックをGCLKのクロックジェネレータ7番に供給し、背面GPIO6から出力させておく(REFクロックの実測確認用)
- クロックジェネレータ7番の出力をFREQMのREFクロックに指定
- 測定期間はREFクロック128発分(256分の1秒期間)
- 背面GPIO5へ波形発生器を接続し、各種周波数の方形波(デューティ50%)を入力する。
- GPIO5番はクロックジェネレータ6番に接続、分周なし、6番の出力をFREQMの被測定クロックに指定
確認用にGPIO5番と6番をオシロでみているところがこちら。黄色が被測定クロック、青がREFクロック。
FREQMの初期化
以下の初期化コードで行っているのは以下のような操作です。
-
- REFクロックと非測定クロックのソースを設定
- FREQMブロックへのAPBバスクロックを許可(デフォルトでは不許可なので)
- 参照クロックの測定期間として128クロック設定
- 割り込みは使用しないので念のため禁止
- FREQMの動作を許可
- 許可後「クロック同期」の期間を確保
void enableFREQM() { int timeoutCount = 0; Gclk* gclk = (Gclk*)GCLK; gclk->PCHCTRL[5].reg = 0x46; //GCLK_FREQM_MSR <= Generator 6(PB12 input) gclk->PCHCTRL[6].reg = 0x47; //GCLK_FREQM_REF <= Generator 7(XOSC32K) Mclk* mclk = (Mclk*)MCLK; mclk->APBAMASK.bit.FREQM_ = 1; // enable APB CLOCK Freqm* freqm = (Freqm*)FREQM; freqm->CFGA.bit.REFNUM = 128; // measurement time = 1/256 second freqm->INTENCLR.bit.DONE = 1; // disable FREQM interrupt freqm->CTRLA.bit.ENABLE = 1; // enable FREQM while (freqm->SYNCBUSY.bit.ENABLE == 1) { if (timeoutCount++ > 100) break; } //Sync Busy
測定対象によってクロックソースを変更したり、測定期間を変えたりすれば所望の測定ができそうです。今回は中途半端にREFクロック128発としましたが、最大255まで設定可能です。長くする方が測定時間はかかるけれども精度は良くなるはず。
計測スタートと値の読み取り
計測スタートは開始ビットを立てるだけです。これによりREFクロックの所定期間の測定が始まります。
カウントの終了後、カウンタ値を読みます。今回、終了確認には割り込みをつかっていないので、ポーリングする必要があります。FREQMのカウント中でBUSYとなるフラグがあるのでそれが下がるまで待ちます。以下のコードでは念のためタイムアウトも監視していますが、キメウチの値なので測定期間を変えたならば要変更です。被測定クロックのカウントは24ビットのカウンタなのでそうそうあふれるとも思われませんが、一応カウンタのオーバーフローも確認しています。
void startFREQM() { Freqm* freqm = (Freqm*)FREQM; freqm->CTRLB.bit.START = 1; // start FREQM } int readFREQM() { int timeoutCount = 0; Freqm* freqm = (Freqm*)FREQM; while (freqm->STATUS.bit.BUSY == 1) { if (timeoutCount++ > 1000000) return -1; } if (freqm->STATUS.bit.OVF == 1) { freqm->STATUS.bit.OVF = 1; return -1; } return (int)freqm->VALUE.bit.VALUE; }
計測実験の結果
実験結果を標準出力に取り出し、結果を確認します。測定結果のカウント値に、設定を反映した定数を乗ずれば周波数に変換できます。ここでは、分かり易いように以下の2つの定数から求めています。
#define FREQMREFNUM (128) #define FREQMREFFREQ (32768)
SAMD51P19Aは、Arm Cortex M4F搭載なので、浮動小数をサポートしています。最初周波数変換は素直にfloatで計算したのですが、printf関数でfloat型の書式指定を使用したらうまく変換してくれませんでした。Wio TerminalのArduino環境の制約かもしれないです。深く調べず、整数型で計算してお茶を濁しています。
startFREQM(); measuredV = readFREQM(); int freq = measuredV*FREQMREFFREQ/FREQMREFNUM; Serial.printf("FREQM=%d (%d.%-3d[kHz])\r\n", measuredV, freq/1000, freq%1000);
50kHz外部クロック時の実測結果は以下です。まずまずな感じ。非同期のクロック同士なので、カウントの最後の1くらいは測定の度に変動する可能性があります。
FREQM=194 (49.664[kHz])
100kHzに周波数アップ。カウンタがあふれなければ、周波数が高い方が精度[%]はよくなる筈。
FREQM=389 (99.584[kHz])
1MHz。いいんでないかい。
FREQM=3905 (999.680[kHz])
10MHz。まだまだイケそうですが、とりあえずここまで。
FREQM=39061 (9999.616[kHz])
逆にクロック速度が遅いと、測定精度はボロボロになります。ここでは測定期間が32.768kHz128発で256分の1秒固定です。1kHzといったクロックであると、その期間中に3発カウントするのがやっとです。1kHzのときの結果はこちら。
FREQM=3 (0.768[kHz])
同じような原理、そして参照クロックはどちらも32kHzクロックなのですが、昨日のラズパイPicoの周波数カウンタ読み取り関数は、外部クロック1kHzいれると0[kHz]を返してき、2kHzだと2[kHz]と判定しました。こちらとは挙動が違いますが、いずれもREFクロックに対して遅すぎるクロックはうまく測定できません。まあ、測定時の制限事項として忘れなければよいかと(そういって忘れるのですが、自分は。)