Arduinoでセンサの値やアナログ電圧を読み取るとき、ノイズにより値がガタついてしまうことがあります。今回はこのようなノイズをソフト(プログラム)で低減させる方法をご紹介します。
ノイズフィルタの種類
フィルタにはどのような信号をフィルタリングするかによって以下のような分け方があります。
- ローパスフィルタ
信号の低周波成分のみを通過させ、高周波成分を低減します。低いものだけを通すのでロー(low)パス(pass)フィルタです。センサ値はゆっくり変化しますが、ノイズは高周波であることが多いのでこのフィルタをよく使います。 - ハイパスフィルタ
信号の高周波成分のみを通過させ、低周波成分を低減します。オーディオ信号や交流電流等、変化する信号を取り出したいときに使います。 - バンドパスフィルタ
高周波と低周波の両方を低減し、真ん中の周波数帯だけ通したいときに使います。
本記事ではセンサ値のノイズを除去したいという目的なので、ローパスフィルタについてご紹介します。
FIRフィルタとIIRフィルタ
デジタルフィルタは実装の仕方にも種類があり、FIRフィルタとIIRフィルタに分けられます。FIRフィルタもIIRフィルタも係数を適切に選ぶことでローパス/ハイパス/バンドパスのいずれにも使えます。
FIRフィルタとIIRフィルタの違いについて調べたところ、大体次のような特徴があるようです。
FIRフィルタ
- 波形が変形しない(群遅延が一定である)
- 必ず安定である(出力が発散しない)
- 通過域と減衰域の境目が緩やかである
- 高性能にしたければ演算量が多くなる
- カットオフ周波数が低い場合、性能が悪くなる
IIRフィルタ
- 少ない演算量でも通過域と減衰域がはっきり分かれる
- 通過域の信号をほとんど変化させない(通過域のゲインが0)
- サンプリング周波数に対してカットオフ周波数が低い場合もフィルタリングが可能
- 不安定なことがある(出力が振動/発散する)
- 波形が変化する(群遅延が一定でない)
波形が変化するとは、信号の周波数によって遅延時間が変化するということで、それがあると例えばセンサ値がゆっくり変化したときは遅延が少ないが、早く変化したときは遅延が大きいということが起こりえます。
通過域と減衰域の境目がはっきりしていないと、例えば30Hz以上の信号を遮断するローパスフィルタを設計しても、40Hzの信号を50%だけ通過させてしまうといったことが起こります。
FIRフィルタを使うかIIRフィルタを使うかは、メリット・デメリットをよく考えて使う必要があるそうです。(参考1)
よくあるローパスフィルタの実装(RCフィルタ)
Arduino関連の記事でセンサ値のノイズを除去する方法として紹介されているのがRCフィルタです(参考2、参考3)。IIRフィルタに分類されるフィルタです。実装が簡単ですが性能はそこそこで、波形の変形も起こりますし通過域と減衰域の境目も緩やかです。
RCフィルタを実装して、加速度センサの出力をフィルタリングしてみました。ノイズは低減できましたが、もとの信号の波形が一部変わってしまっています。
#include <ADXL345.h> double x_rc = 0; ADXL345 adxl; // 加速度センサ:ADXL345 void setup() { Serial.begin(9600); adxl.powerOn(); // 加速度センサ起動 } void loop() { int x, y, z; adxl.readXYZ(&x, &y, &z); // 加速度センサのxyz軸の値を読み取り、変数に格納する x_rc = x_rc * 0.85 + x * 0.15; // RCフィルタ // 結果を出力 Serial.print(x); // フィルタ前 Serial.print(" , "); Serial.print(x_lpf); // フィルタ後 Serial.println(""); delay(10); }

フィルタ後の波形は振幅がもとの信号よりも小さくなってしまいました。また分かりづらいですが遅延時間が所々変わっています。
(準備)ライブラリのインストール
本記事では以下のライブラリを使用します。Arduinoのライブラリ管理ツールから検索しても出てこないので、git cloneするか、zipファイルをダウンロードしてArduinoのlibrariesフォルダに展開します。
// git cloneする場合 c/Users/ppdr/Documents/Arduino/libraries$ git clone https://github.com/tttapa/Filters.git
FIRフィルタでより高性能なノイズ除去
FIRフィルタを設計してセンサ値をフィルタリングするとどうなるか見てみます。FIRフィルタの設計は本来難しい理論があるらしいのですが、ここは周波数等の特性を入れるだけでFIR/IIRフィルタの設計を行ってくれる以下のサイトを利用しました。
石川高専 山田洋士 研究室ホームページ Digital Filter Design Services
#include <ADXL345.h> #include <FIRFilter.h> // 設計サイトで求めたフィルタの係数 // フィルタ長=31、正規化遮断周波数=0.1 const double a[] = { 6.237074932031003e-19, 1.203468256554930e-03, 2.789059944207731e-03, 4.234500608097008e-03, 3.949464324766553e-03, -2.416866536162014e-18, -8.270810074278890e-03, -1.861479317048980e-02, -2.543297162983507e-02, -2.127139910497268e-02, 6.003184622079840e-18, 3.965539443147850e-02, 9.204504649186659e-02, 1.453456837490316e-01, 1.852171297065770e-01, 2.000000000000000e-01, 1.852171297065770e-01, 1.453456837490316e-01, 9.204504649186659e-02, 3.965539443147850e-02, 6.003184622079840e-18, -2.127139910497268e-02, -2.543297162983507e-02, -1.861479317048980e-02, -8.270810074278890e-03, -2.416866536162014e-18, 3.949464324766553e-03, 4.234500608097008e-03, 2.789059944207731e-03, 1.203468256554930e-03, 6.237074932031003e-19}; ADXL345 adxl; // 加速度センサ:ADXL345 FIRFilter fir(a); // FIRフィルタ void setup() { Serial.begin(9600); adxl.powerOn(); // 加速度センサ起動 } void loop() { int x, y, z; adxl.readXYZ(&x, &y, &z); // 加速度センサのxyz軸の値を読み取り、変数に格納する // 結果を出力 Serial.print(x); // フィルタ前 Serial.print(","); Serial.print(fir.filter(x)); // フィルタ後 Serial.println(""); delay(10); }

RCフィルタに比べて波形の変形が少なくなりました。しかし、元波形よりも振幅が小さくなってしまい、遅延が出てしまいました。これはカットオフ周波数が低い場合、性能が低くなるというFIRフィルタの特徴になります。ちなみにこの遅延時間は設計段階で知ることができ、今回は15サンプル遅延でサンプリング間隔は10msなので150msの遅れです。
IIRフィルタで遅延が少ないフィルタを実現する
FIRフィルタでは波形は変形しませんでしたが、遅延が大きくなってしまいました。また元波形よりも振幅が小さくなってしまいました。
この点を改善するため、低いカットオフ周波数に対しても遅延が少ないIIRフィルタを設計します。またIIRフィルタの特性の一つであるバタワース特性では通過域のゲインがほぼ0なので、波形の振幅を変えてしまうことも防げます。
それでは実装と結果です。
#include <ADXL345.h> #include <IIRFilter.h> // 設計サイトで求めたフィルタの係数 // バタワース特性、サンプリング周波数=100、パスバンド周波数=5、ストップバンド周波数=30 // 注意:設計サイトはb[1]以降の値なので、b[0]=1を追加しています const double a[] = {2.87258084601274277e-01, 5.74516169202548554e-01, 2.87258084601274277e-01}; const double b[] = {1, -1.27955098077267904e+00, 4.77550934586849885e-01}; const double k = 1.72318869709970568e-01; ADXL345 adxl; // 加速度センサ:ADXL345 IIRFilter iir(a, b); // IIRフィルタ void setup() { Serial.begin(9600); adxl.powerOn(); // 加速度センサ起動 } void loop() { int x, y, z; adxl.readXYZ(&x, &y, &z); // 加速度センサのxyz軸の値を読み取り、変数に格納する // 結果を出力 Serial.print(x); // フィルタ前 Serial.print(","); Serial.print(iir.filter(x * k)); // フィルタ後 Serial.println(""); delay(10); }

IIRフィルタでは遅延がかなり少なくなりました。また、振幅も元波形とほとんど同じです。
今回のようにセンサ出力が10Hz以下くらいであれば、IIRフィルタが遅延/波形の変形の両方で有利なようです。ただ、FIR/IIRのどちらが良いかは一概には言えないので、色々なパラメータで試してみる必要がありそうだと感じました。
ここまで読んでいただきありがとうございました。今回は以上です。