Arduinoでローパスフィルタ(センサのノイズを無くそう)

記事内に広告が含まれています。

Arduinoでセンサの値を読み取るとき、ノイズが多くて困る場合があります。そんなときにつかえるプログラムでのノイズ対策をまとめます。なんか、たくさんあって面倒だという人は、とりあえずRCフィルタやっとけば大丈夫でしょう。

ノイズ

移動平均フィルタ

出力値 = (センサの値 + 前回のセンサ値 + 前々回のセンサ値 + ...) / 標本数

算術平均を取ることでノイズを除去します。直感的でわかりやすい方法です。

#define n 10                             //標本数を決める
int f[n]={0};
float ave = 0;                           //平均値を入れる変数

void setup(){
    Serial.begin(9600);                  //9600bpsでシリアルポートを開く
}

void loop(){
    for(int i=n-1;i>0;i--) f[i] = f[i-1];//過去10回分の値を記録
    f[0] = analogRead(0);

    ave = 0;
    for(i=0;i<n;i++) ave += f[i];        //総和を求める
    ave = (float)ave/n;                  //標本数で割って平均を求める

    Serial.print(f[0]);                  //フィルタ前の値
    Serial.print(",");
    Serial.print(ave);                   //フィルタ後の値
    Serial.print("\n");
}

1行目で平均をとる個数を決めます。f[0]からf[9]には新しい順にanalogReadの値を入れていきます。最後にf[0]からf[9]の総和をnで割れば平均値が求められます。

n=10の移動平均フィルタ

図はn=10のときのグラフです。青色がフィルタ前、オレンジがフィルタ後です。

n=50の移動平均フィルタ

n=50のときのグラフです。nを大きくするとフィルタが強くなる分、反応が遅れていることがわかります。

RCフィルタ

出力値 = a * 前回の出力値 + (1-a) * センサの値

抵抗とコンデンサを使ったRCフィルタをプログラム的に実装します。コードが短いのでおすすめです。「センサの値」と「前回の出力値」の内分点を出力値とします。

#define a 0.8               //0<a<1の範囲 大きいほど効果大
int val = 0;                //センサ値
float rc = 0;

void setup(){
 Serial.begin(9600);        //9600bpsでシリアルポートを開く
 } 
void loop() {
  val = analogRead(0);      //センサ値を更新
  
  rc = a * rc + (1-a) * (float)val;

  Serial.print(val);        //フィルタ前の値
  Serial.print(","); 
  Serial.print(rc);         //フィルタ後の値
  Serial.print("\n");
}

aは(0<a<1)の範囲で1に近づくほどフィルタが強力になります。値を大きくしすぎると反応が遅くなるので良いくらいの値を見つけましょう。

RCフィルタ

a=0.8のときのグラフです。

aの値の決め方

カットしたい周波数(カットオフ周波数)が分かっている場合はaの値を計算で求めることが出来ます。fc [Hz] 以上の周波数をカットしたい場合は、a = 1/(2π・fc・dt + 1) とします。このとき dt [s] はサンプリング周期です。(dt秒ごとにloopをまわす)

メディアンフィルタ

メディアン(中央値)を代表値とすることでノイズを除去します。

int f[3]={0};
int med = 0;

void setup(){
    Serial.begin(9600);     // 9600bpsでシリアルポートを開く
}

void loop(){
    f[2] = f[1];
    f[1] = f[0];
    f[0] = analogRead(0);   //過去3つ分のデータを格納
    
    int mx = 0;
    int mi = 0;

    if(f[1]>f[0])mx = 1;
    else mi = 1;
    if(f[2]>f[mx])mx = 2;   //f(mx)が最大値になる
    else mi = 2;            //f(mi)が最小値になる

    if(mx==mi)med = f[1];
    else med = f[(3^mx)^mi];//中央値

    Serial.print(f[0]);     //フィルタ前の値
    Serial.print(",");
    Serial.print(med);      //フィルタ後の値
    Serial.print("\n");
}

3つの値の最大値、最小値を求め、それ以外を中央値としてmedに代入しています。

メディアンフィルタ

3回のうちの中央値なので、あまり変わっていないが標本数を増やすともう少し滑らかになると思います。

まとめ

移動平均フィルタ,RCフィルタ,メディアンフィルタの3つのフィルタを紹介しました。コードが短くすむので僕は普段、RCフィルタを使っています。メディアンフィルタは標本数を増やすと強力だと思います。スケッチがめんどくさそうですけど。

その他デジタルフィルタについて↓

コメント