2021年12月19日日曜日

Arduinoで周波数を計測する便利ライブラリHzMeter_asukiaaaを作った


背景

以前Arduinoでパルスを読み取る方法を記事にしました。
しかしながら、1.2Hzなどの低い周波数を読もうとすると、基本1Hzで時々0Hzになるぶれ方をします。
パルスの間隔や直前の測定情報を利用すればよりふさわしい測定値を算出できたのですがコピペ推奨のコードとしては量が多くなったので、ライブラリとしてまとめました。
同様の課題を抱えている方の助けになれば良いと思い、ライブラリの使い方を共有します。

使ったもの

  • Arduinoにプログラムを書き込めるPC
    Arduino IDEかVSCode+PlatformIOで書き込めます
  • ProMicro
    Leonardo互換の開発ボードです
  • ブレッドボード
    ProMicroの配線を使いやすいように引き出します。
  • Analog Discovery2
    周波数の生成に利用しました。
    これの使い方はこの記事では解説しませんが、利用例を見たい方はanalog_discoveryのタグをご覧ください。
  • ジャンパワイヤ
    Analog Discovery2とProMicroを接続します。

ライブラリHzMeter_asukiaaaの設定方法

ライブラリは下記のリポジトリで管理しています。

https://github.com/asukiaaa/arduino-HzMeter

各環境での設定方法を解説します。

PlatformIOの場合

作成したプロジェクトのplatformio.iniのlib_depsとしてHzMeter_asukiaaaを記述してください。

Arduino IDEの場合

ライブラリマネージャからインストールしてください。
ライブラリマネージャはスケッチ -> ライブラリをインクルード -> ライブラリを管理で開けます。
右上の検索フィルタにHzMeterと入力すると探しやすいです。


プログラムの書き方

サンプルプログラム全体を示して要所を解説します。
この記事で解説するプログラムが動かない場合は、下記のリンクから最新のサンプルプログラムを確認してください。

https://github.com/asukiaaa/arduino-HzMeter/blob/main/examples/monitorHz/monitorHz.ino

#include <HzMeter_asukiaaa.hpp>

#define PIN_INTERRUPT_HZ_METER 3
#if digitalPinToInterrupt(PIN_INTERRUPT_HZ_METER) < 0
#error needed to assign interrupt pin for PIN_INTERRUPT_HZ_METER
#endif

HzMeter_asukiaaa::Core hzMeter;

// #define HISTORY_LENGTH 5
// HzMeter_asukiaaa::Core hzMeter(HISTORY_LENGTH);

void setup() {
Serial.begin(115200);
hzMeter.begin();
pinMode(PIN_INTERRUPT_HZ_METER, INPUT_PULLUP);
attachInterrupt(
digitalPinToInterrupt(PIN_INTERRUPT_HZ_METER),
[]() { hzMeter.countUp(); }, RISING);
}

void loop() {
hzMeter.onInterval();
auto countInfo = hzMeter.getInfoBundled();
Serial.print(countInfo.calcHzByFirstAndLast());
Serial.print("Hz from ");
Serial.print(countInfo.measuredFrom);
Serial.print(" to ");
Serial.println(countInfo.measuredTill);
delay(1000);
}


割り込み処理が出来ないピンだとdigitalPinToInterruptで-1が返されてコンパイルエラーになるよう記述しています。
どのピンを割当可能化はボードの設定を参照してください。
Arduino公式のボードはattachInterruptの解説ページに割当可能なピンが示されており、今回利用するLeonardo(互換のProMicro)は0,1,2,3,7を割り込みとして使えるため、サンプルプログラムでは3番ピンを利用しています。
#define PIN_INTERRUPT_HZ_METER 3
#if digitalPinToInterrupt(PIN_INTERRUPT_HZ_METER) < 0
#error needed to assign interrupt pin for PIN_INTERRUPT_HZ_METER
#endif

周波数計測クラスHzMeter_asukiaaaのインスタンスを生成します。
特に記述しなければHzMeter_asukiaaaは過去2回の計測情報も参照して周波数を算出します。
長さを変えたい場合はインスタンス生成時の引数として利用したい過去の履歴の長さを渡してください。
HzMeter_asukiaaa::Core hzMeter;

// #define HISTORY_LENGTH 5
// HzMeter_asukiaaa::Core hzMeter(HISTORY_LENGTH);


setup内で周波数測定インスタンスのbeginを実行します。
その後、測定対象のピンの電圧が変化したらcountUpを実行するようにattachInterruptに関数を登録します。
(attachInterrupにクラスメンバ関数をラムダ式を介さず渡す方法をご存知でしたら、共有していただけるとライブラリを洗練できて嬉しいです。)
void setup() {
hzMeter.begin();
pinMode(PIN_INTERRUPT_HZ_METER, INPUT_PULLUP);
attachInterrupt(
digitalPinToInterrupt(PIN_INTERRUPT_HZ_METER),
[]() { hzMeter.countUp(); }, RISING);
}


onIntervalの呼び出しによって、計測インスタンスの情報を引き上げて計測結果を保持します。
  hzMeter.onInterval();


計測結果はgetInfoBundledで取得できるので、それを介して周波数や計測終了時間を確認できます。
  auto countInfo = hzMeter.getInfoBundled();
Serial.print(countInfo.calcHzByFirstAndLast());
Serial.print("Hz from ");
Serial.print(countInfo.measuredFrom);
Serial.print(" to ");
Serial.println(countInfo.measuredTill);


動作確認

Analog Discovery2で周波数を発生させて、ProMicroに書き込んだサンプルプログラムで解釈します。

1.2Hzを生成。


1.2Hz読取り良し。
1.20Hz from 78063 to 80065
1.20Hz from 79064 to 81065

0.5Hzを生成。


読めなくなりました。
1.20Hz from 111091 to 113093
0.00Hz from 112093 to 114095
0.00Hz from 113093 to 115095
0.00Hz from 114095 to 116096
これは測定期間が2秒に対して電圧の立ち上がりが1度しかないため、測定不能になっているためです。

周波数が低くて測定できないときは、intervalの呼び出し間隔を長くするか、履歴の長さを増やすと計測可能になります。
// HzMeter_asukiaaa::Core hzMeter;

#define HISTORY_LENGTH 5
HzMeter_asukiaaa::Core hzMeter(HISTORY_LENGTH);

上記の利用履歴の長さ5で書き込んだプログラムだと、下記のように0.5Hzを計測できました。
0.50Hz from 116098 to 121103
0.50Hz from 117099 to 122103
0.50Hz from 118099 to 123105
0.50Hz from 119101 to 124105

利用する履歴の長さやonIntervalを呼び出す間隔を伸ばすと低い周波数を測定可能になりますが、その分応答速度は落ちるので、応答を担保しつつ長い周波数を読みたい場合は、応答性の高いインスタンスと低いインスタンスを両方つくってふさわしい方の周波数を利用するなど工夫が必要です。

終わり

HzMeter_asukiaaaを利用して数Hzの周波数を計測できました。

0 件のコメント :