ArduinoでFFTを使う

マイクから拾った音の音階を調べるプログラムのテストです。Arduinoだけを使ってFFTする方法を調べてみたのですが、情報が古く、日本語では欲しい情報になかなかたどり着けなかったので上手く行ったやり方を紹介します。

FFTとは

一定期間の音信号を解析して、どの周波数成分がどの程度含まれているか求める手法です。信号の数を2のべき乗にすることによって高速に計算することが出来ます。

あんま上手く説明できません。とりあえず、鳴っている音の周波数が何なのかわかる手法です。

使うもの

  • ESP8266(手持ちのArduino nanoでもやってみましたが、メモリが足りずスケッチが書き込めませんでした。他のArduinoは要検証です。)。以下の2個セットのやつが値段的にも最安でWiFiも使えてメモリも多いのでよく買ってます。
  • マイクモジュール

やり方

ここのやり方に沿って進めていくだけですが、かいつまんで解説していきます。
https://www.norwegiancreations.com/2017/08/what-is-fft-and-how-can-you-implement-it-on-an-arduino/

ライブラリをインストール

Arduino IDEを開き、スケッチ→ライブラリをインクルード→ライブラリを管理…の順にクリックします。

スケッチ→ライブラリをインクルード→ライブラリを管理… の順にクリック

検索バーに「fft」を入力し、「arduinoFFT」をインストールします。

arduinoFFTをインストール

これだけで完了です。

プログラム書き込み

下にサンプルプログラムを置いておきます。上記サイトに掲載されているプログラムほぼそのままです。一部ESP8266向けに変更しています。オリジナルよりもサンプル数とサンプリング周期を上げています。

FFTの特徴として、サンプリング周波数の半分の周波数までしか解析することが出来ません。サンプリング周波数が8000Hzなら、4000Hz以上の音は検出できません。

また、サンプル数を多くすると、結果の分解能が上がる代わりに測定にかかる時間が増えます。サンプル数は2のn乗にしなければならないと決まっていますので、欲しい分解能に応じて決める必要があります。

分解能はサンプリング周波数[Hz]÷サンプル数になります。つまり下のサンプルプログラムでは8000÷256=32[Hz]刻みの結果しか得られないことになります。判別する周波数の精度を上げたい場合にはサンプル数(=サンプリング時間)をあげる必要があります。

下記のプログラムでは最も成分の多い周波数(ピーク周波数)を出力するようにしています。

#include "arduinoFFT.h"
 
#define SAMPLES 256             //Must be a power of 2
#define SAMPLING_FREQUENCY 8000 //Hz, must be less than 10000 due to ADC
 
arduinoFFT FFT = arduinoFFT();
 
unsigned int sampling_period_us;
unsigned long microseconds;
 
double vReal[SAMPLES];
double vImag[SAMPLES];
 
void setup() {
    Serial.begin(115200);
 
    sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
}
 
void loop() {
   
    /*SAMPLING*/
    for(int i=0; i<SAMPLES; i++)
    {
        microseconds = micros();    //Overflows after around 70 minutes!
     
        vReal[i] = analogRead(A0);  //ESP8266の場合は「A0」。普通のArduinoは「0」。
        vImag[i] = 0;
     
        while(micros() < (microseconds + sampling_period_us)){
        }
    }
 
    /*FFT*/
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
    double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
 
    /*PRINT RESULTS*/
    Serial.println(peak);     //Print out what frequency is the most dominant.
 
    for(int i=2; i<(SAMPLES/2); i++)
    {
        /*View all these three lines in serial terminal to see which frequencies has which amplitudes*/
         
        //Serial.print((i * 1.0 * SAMPLING_FREQUENCY) / SAMPLES, 1);
        //Serial.print(" ");
        //Serial.println(vReal[i], 1);    //View only this line in serial plotter to visualize the bins
    }
 
    delay(1);  //Repeat the process every second OR:
    //while(1);       //ESPだと無限ループがエラーになるので注意
    
}

Arduinoとマイクを接続

わざわざ書くまでもありませんが、Arduinoとマイクを接続します。3.3VとGNDとA0をマイクモジュールにつなぐだけです。

マイクモジュールを使えば簡単!

テスト

プログラムを書き込んだらシリアルプロッタを開いて口笛を吹いてみます。

口笛が下手

口笛の音階があっているかはさておき、音の高さを判別することができました!

追記:電源を安定化すれば精度が上がる

上記の方法で問題なくFFTはできるのですが、たまに誤差が多い結果が出ることもありました。安いマイクなのでしょうがないのかな?と思っていましたが、ふと思いついて電源を見直したところ精度がめっちゃ向上したので報告します。

どうやらArduinoの3.3Vピンから電源を取るとノイズが多いようで、マイクの出力がノイズを含んでいました。

マイクの電源を別に用意したところ、ノイズがなくなりFFTの出力もキレイになりました。

マイク用電源を別に追加

電池を別に用意するのが面倒な場合は、電源安定回路やレギュレータを使うのもありです。esp8266のVinピンから取っても大丈夫でした。(VinはUSBの5Vと繋がっている)

今回は以上です。

「ArduinoでFFTを使う」への2件のフィードバック

  1. 素晴らしいですね!
    シリアルプロッタというのを使うとそのままグラフに表されるんですか?
    だとしたら本当にスゴイです!

    1. ありがとうございます
      もちろんシリアルプロッタだけでグラフを表示できます!
      まあシリアルプロッタも万能ではなくて、軸ラベルの表示や表示範囲の変更が出来なかったりします。結果を簡単に確認したい時によく使います。

コメントを残す

メールアドレスが公開されることはありません。