はじめに
前回はMSGEQ7のデータを取得しました。図1の青線がMSGEQ7から取得した1kHzの無音時波形なのですが、無音時でも波形にノイズが多く乗っています。
この状態のデータでは使えませんので、Arduinoのスケッチにノイズを除去するためのフィルターを実装します。図1の赤線と青線はそれぞれ異なるノイズフィルターを適用した後の波形です。ノイズによる変化がかなり穏やかになっています。
図1 無音時のMSGEQ7 1kHz出力(青:生値 赤:移動平均 緑:指数平滑)
今回はこのノイズフィルターについて解説します。
目次
1. ノイズフィルタの種類
Arduinoで実装しやすいノイズフィルターには移動平均と指数平滑(RCフィルタ)があります。図1の赤線が移動平均、緑線が指数平滑を適用した波形です。それぞれのフィルターを大雑把に説明すると、移動平均は一定数のデータの平均値、指数平滑は新しいデータに重みをおいて平滑化した値です。
データ数をi、入力をx[i]、出力をy[i]として式で表すと以下のようになります。aは指数平滑の係数です。大きくするほど変化がなだらかになります。自分が試した範囲では0.9~0.99くらいが良かったです。
移動平均
$\displaystyle {y[i] = \frac{1}{n} \sum_{j=0}^{n-1}x[i-j] }$
指数平滑
$\displaystyle {y[i] = a \times x[i] + (1-a) \times y[i-1]}$
正直に言うとこれらの式でノイズが除去出来る理由をちゃんと説明出来るほど、理解出来ていません。これらのフィルタについては下記のWebサイトを参考にいたしましたので、詳しく知りたい方はそちらをご覧ください。
"センサの入力などに使うディジタルフィルタ" なんでも独り言
"THREE METHODS TO FILTER NOISY ARDUINO MEASUREMENTS" MegunoLink
この2つのフィルタをArduinoに実装します。
2. ノイズフィルタの実装
MSGEQ7から1[kHz]の値を取得するスケッチに移動平均と指数平滑のフィルタを実装しました。移動平均に関する箇所は41行目〜51行目、指数平滑に関する箇所は51行目です。
/*
* グラフィックイコライザー用IC MSGEQ7の値を取得し、ノイズをフィルタにて除去する
* 2019.8.30
*/
//MSGEQ7関連
#define MSGEQ7_RESET 8
#define MSGEQ7_STROBE 7
#define MSGEQ7_OUT A0
#define FREQ_NUM 7
int valAmplitude[FREQ_NUM]={0};
//移動平均関連
#define BLOCK_LENGTH 64 //移動平均長
int tempValAmp[BLOCK_LENGTH] = {0};
long sumAmp = 0;
double aveAmp;
byte cnt = 0;
//指数平滑関連
#define EXPO_FIL_COEF 0.98
double expoFilOut =0.0;
void ReadMSGEQ7(); //MSGEQ7の読み取り
void setup() {
Serial.begin(115200);
//MSGEQ7の初期設定
pinMode(MSGEQ7_RESET, OUTPUT);
pinMode(MSGEQ7_STROBE, OUTPUT);
digitalWrite(MSGEQ7_RESET, HIGH);
digitalWrite(MSGEQ7_STROBE, HIGH);
delayMicroseconds(1); //Reset Pulse Width(tr)
}
void loop() {
ReadMSGEQ7();
//移動合計の更新・レジスタへの格納
sumAmp = sumAmp + valAmplitude[3] - tempValAmp[cnt];
tempValAmp[cnt] = valAmplitude[3];
//配列の最後ならば、先頭に戻る。そうでなければ次のレジスタへ
if( cnt == BLOCK_LENGTH -1 ){
cnt = 0;
} else {
cnt++;
}
//移動合計を移動平均へ
aveAmp = (double)(sumAmp/BLOCK_LENGTH); // -H31.4.18 ExpoFil
//指数平滑
expoFilOut = expoFilOut*EXPO_FIL_COEF + (1.0-EXPO_FIL_COEF)*valAmplitude[3];
//波形表示
Serial.print(valAmplitude[2]);
Serial.print(", ");
Serial.print(aveAmp);
Serial.print(", ");
Serial.print(expoFilOut);
Serial.print(", ");
Serial.print(1024);
Serial.print("\n");
}
//MSGEQ7の読み取り
void ReadMSGEQ7(){
digitalWrite(MSGEQ7_RESET, LOW);
delayMicroseconds(100); //Reset to Strobe Delay(trs)
for(byte freq=0; freq<FREQ_NUM; freq++){
digitalWrite(MSGEQ7_STROBE, LOW);
delayMicroseconds(50); //Output Setting Time(to)
valAmplitude[freq] = analogRead(MSGEQ7_OUT);
delayMicroseconds(20); //指定や名称無し。これとtoとtsの合計がStrobe to Strobe Delay(tss)
digitalWrite(MSGEQ7_STROBE, HIGH);
delayMicroseconds(30); //Strobe Pulse Width(ts)
}
digitalWrite(MSGEQ7_RESET, HIGH);
delayMicroseconds(1); //Reset Pulse Width(tr)
}
ソースコードの該当する箇所を見れば分かるように、移動平均はシフトレジスタを実装しないといけませんのでソースコードが多くなります。一方、指数平滑の方は1行で記述することが出来ます。
またシフトレジスタは値を保存しないといけないので、使用するメモリ量が多いです。まぁ指数平滑も小数の乗算がありますので、計算量は多いのですが……。
3. ノイズフィルタの結果
それぞれのフィルタの波形を比較し、どちらのフィルタを使うか決めます。
図2は音の強弱がなだらかに変化しているときの波形です。このような波形だとどちらのフィルタもよくノイズを除去出来ています。
図2 強弱変化遅め時のMSGEQ7 1kHz出力(青:生値 赤:移動平均 緑:指数平滑)
一方、図3は音の強弱が急に変化しているときの波形です。この場合移動平均のピークは一定時間続きますが、指数平滑のピークは一瞬です。
実際のそれぞれのフィルタでLEDを光らせた場合、ピークが一瞬となる指数平滑の方が自然に光りました。
おわりに
MSGEQ7の出力値のノイズを除去するために、移動平均や指数平滑の説明や比較を行いました。比較結果をまとめると以下の通りです。
- 指数平滑は記述するコードや使用するメモリが少ない
- 指数平滑はLEDの光り具合が自然
プログラム的にもLEDの光り具合的にも指数平滑の方がメリットが多いので、指数平滑を使用することにします。