見出し画像

C++のヘッダファイルをわざわざ自分で作って公開してみる -アベレージングフィルタ

こんにちは。
今回は、温度センサのアベレージングフィルタを、ヘッダファイルとして実装してみたいと思います。

この記事は、下記の記事の続きです。

また、今回作成したヘッダファイルを以下のリンクで公開してみました。よかったら使ってやってください。


やりたいこと

アベレージングとは?

ノイズ等によりブレが含まれるデータに対し、過去数回分のデータの平均値を計算します。そうすることで、ブレを除去する手法です。

ヘッダファイルとは?

プログラミング言語における、処理に必要な関数や変数を定義するファイルのことです。
というと難しく聞こえますが、要は何回も使う処理は使いやすいように別ファイルに書いておきましょうね~みたいな感じです。
Arduinoだとライブラリもヘッダファイルですね。
ライブラリを使う際、

#include <~.h>

のように使うライブラリを記載すると思います。
この、「.h」の拡張子のファイルがヘッダファイルです。
今回は、今まで利用するだけだったヘッダファイルを、自分でもつくってみようという試みです。

なぜこれくらいの処理でヘッダファイルをつくるのか?

・アベレージング処理を、温度と湿度に対して計算する⇒繰り返し使うのでヘッダファイルにしておいたほうが楽そう
さらに、今後気圧やCO2濃度も測定する予定です。つまり、より繰り返し回数が増える見込みなので、今のうちに準備しておきたく。

・自分の勉強のため
ヘッダファイルを書く経験をしてみたかったからです。
普段使っているライブラリの理解にも役立ちそうなので。

・「ヘッダファイルを書く」この響きがかっこいいから
自己満要素

作り方

作り方は、下記のQiitaの記事を参考にさせていただきました。

ざっくりまとめると、下の図のような感じだと思います。

ポイントは、
・インクルードガードをしておく
二度漬け禁止みたいなものです。
間違えて2回同じヘッダファイルをインクルードした時に、重複する名前の宣言が生まれてしまいます。そうするとエラーが発生します。これを防止するため、インクルードガードが必要になります。

・具体的な処理の内容は、ヘッダファイルと別で実装する
ヘッダファイル内に書くこともできます。が、保守性の観点から、別ファイルに実装したほうが良いそうです。
ただし、実行速度など、別の観点で見たときはヘッダファイルに実装したほうがベターな場合もあるようです。
私はこのあたりの良し悪しがわかりません。なので、今回は愚直にこのルールを守ります。

実装

以上の作り方を踏まえて、実際に作ってみました。

・ヘッダファイル(.h)

#ifndef AVERAGINGFILTER_H
#define AVERAGINGFILTER_H


class AveragingFilter{
  private:
    int sampleNum;
    int ctAvg;
    float *data;
    bool flag_rotation;

  public:
    void init(int fixedSampleNum);
    void storeMeasuredData(float input);
    float outputAveragingData();

};

#endif

・実装ファイル(.cpp)

#include "AveragingFilter.h"

void AveragingFilter::init(int fixedSampleNum){
  sampleNum = fixedSampleNum;
  ctAvg = 0;
  flag_rotation = false;
  data = new float[sampleNum];
  for (int ctInit=0; ctInit<sampleNum; ctInit++){
    data[ctInit] = 0;
  }
}

void AveragingFilter::storeMeasuredData(float input){
  if (ctAvg == sampleNum){
    ctAvg = 0;
    flag_rotation = true;
  }
  data[ctAvg] = input;
  ctAvg++;
}

float AveragingFilter::outputAveragingData(){
  float output = 0;
  for(int ctSum=0; ctSum<sampleNum; ctSum++){
    output += data[ctSum];
  }
  if (!flag_rotation) {
    output = output / ctAvg;
  } else {
    output = output / sampleNum;
  }
  return output;
}

使い方としては以下の通りです。

①AveragingFilterクラスのインスタンスを生成します。
②setup関数内でinit関数を呼び出し、引数でアベレージングのサンプリング回数を指定します。
③storeMeasuredData関数でサンプリングしたデータを保存します。入力データは引数で指定します。
④outputAveragingData関数でアベレージング処理したデータを呼び出せます。出力データは返り値で得られます。

動作の概要としては、
①init関数で指定したサンプリング回数分、データを格納する配列を生成します。
②storeMeasuredData関数で、入力データを、①で生成した配列に順番に格納していきます。全ての配列が埋まったら、最初の配列に戻って上書きしていきます。
③outputAveragingData関数が呼び出されたときに、①で生成した配列に格納されているデータの平均値を計算します。

動作確認

動作確認として、次のコードを実行してみます。

#include "AveragingFilter.h"

#define SAMPLE_NUM 3     //アベレージングのサンプリング数

//アベレージングのインスタンス生成
AveragingFilter avf;

//1ずつインクリメントする変数
int i;

void setup() {
  Serial.begin(115200);

  //アベレージングフィルタ初期化
  avf.init(SAMPLE_NUM);

}

void loop() {
  i++;
  avf.storeMeasuredData(i);
  Serial.print("Input:");
  Serial.print(i);
  Serial.print(" / Avg-out:");
  Serial.println(avf.outputAveragingData());

  delay(500);
}

内容としては、1周期ごとにインクリメントしていく変数を準備し、サンプリング回数3のアベレージングをかけてみます。
こうすると、連続する整数3つの平均値を計算するので、出力は3つの整数の真ん中の数、つまり、今の値の1つ前の値が必ず出力されるはずです。
実行した結果、次のスクショの通りになりました。

想定通り、1つ前の数字が表示されています。
(1,2の場合は全ての配列が埋まっていないので、それぞれ1, 1.5(1と2の平均)になっています。)
以上より、アベレージングフィルタは正しく動作していることを確認できました。

まとめ

今回は、アベレージングフィルタのヘッダファイルを作成しました。
次回こそは、Wi-Fi温度ロガーの製作に入りたいと思います。


最後に

良かったら、つかってやってください!!


この記事が気に入ったらサポートをしてみませんか?