Difference between revisions of "Fast Fourier Transform Library & Arduino"

From ESE205 Wiki
Jump to navigation Jump to search
 
(23 intermediate revisions by 2 users not shown)
Line 17: Line 17:
  
 
==Process==
 
==Process==
 +
===Background===
 
First, let's begin with a discussion of a general Fourier Transform (FT) and then we will address the Fast Fourier Transform (FFT). The Fourier transform is a mathematical function that decomposes a waveform, which is a function of time, into the frequencies that make it up. The result produced by the Fourier transform is a complex valued function of frequency. The absolute value of the Fourier transform represents the frequency value present in the original function and its complex argument represents the phase offset of the basic sinusoidal in that frequency.
 
First, let's begin with a discussion of a general Fourier Transform (FT) and then we will address the Fast Fourier Transform (FFT). The Fourier transform is a mathematical function that decomposes a waveform, which is a function of time, into the frequencies that make it up. The result produced by the Fourier transform is a complex valued function of frequency. The absolute value of the Fourier transform represents the frequency value present in the original function and its complex argument represents the phase offset of the basic sinusoidal in that frequency.
  
Line 50: Line 51:
  
 
end
 
end
 +
===FFT on Arduino===
  
 
+
[https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm Here] you can see some classic implementations in other languages, but as you can see the actual implementation is incredibly difficult. You can also find an example of a custom FFT at the [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm same place]. Typically one would not code an FFT from scratch unless you had a very specific purpose for it. Many libraries that deal with computational problems regarding any sort of waveform will have an FFT coded in the API that has been vetted by multiple software developers.  
Below you can see some classic implementations in other languages, but as you can see the actual implementation is incredibly difficult. You can find an example of a custom FFT at the bottom. Typically one would not code an FFT from scratch unless you had a very specific purpose for it. Many libraries that deal with computational problems regarding any sort of waveform will have an FFT coded in the API that has been vetted by multiple software developers.  
 
  
  
Line 72: Line 73:
 
3. Print the output
 
3. Print the output
  
 +
To download and use Arduino's pre implemented arduino library see instructions [https://www.norwegiancreations.com/2017/08/what-is-fft-and-how-can-you-implement-it-on-an-arduino/ here].
  
===Implementation on the Arduino===
+
====Code Implementations====
  
 
'''Arduino C'''
 
'''Arduino C'''
  
'#'include "arduinoFFT.h"
+
'#'include "arduinoFFT.h"                                 //import the library
 
   
 
   
'#'define SAMPLES 128             //Must be a power of 2
+
'#'define SAMPLES 128                               //Must be a power of 2
  
'#'define SAMPLING_FREQUENCY 1000 //Hz, must be less than 10000 due to ADC
+
'#'define SAMPLING_FREQUENCY 1000               //Hz, must be less than 10000 due to ADC
 
   
 
   
arduinoFFT FFT = arduinoFFT();
+
arduinoFFT FFT = arduinoFFT();                     // create FFT object
 
   
 
   
 
unsigned int sampling_period_us;
 
unsigned int sampling_period_us;
 
unsigned long microseconds;
 
unsigned long microseconds;
 
   
 
   
double vReal[SAMPLES];
+
double vReal[SAMPLES];         //Real part of FFT array
double vImag[SAMPLES];
+
 
 +
double vImag[SAMPLES];         //Imaginary part of FFT aray
 
   
 
   
 
void setup() {
 
void setup() {
Line 111: Line 114:
 
     }
 
     }
 
   
 
   
     /*FFT*/
+
     /*FFT*/                                                               // builtin FFT library and methods
 
     FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
 
     FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
 
     FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
 
     FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
Line 118: Line 121:
 
   
 
   
 
     /*PRINT RESULTS*/
 
     /*PRINT RESULTS*/
     //Serial.println(peak);     //Print out what frequency is the most dominant.
+
     //Serial.println(peak);                                         //Print out what frequency is the most dominant.
 
   
 
   
 
     for(int i=0; i<(SAMPLES/2); i++)
 
     for(int i=0; i<(SAMPLES/2); i++)
Line 133: Line 136:
 
}
 
}
  
 +
*Note other implementations exist and a few can be found [https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm here]
  
'''C++'''
+
====More Arduino Examples====
 
+
More examples on Arduino [https://github.com/kosme/arduinoFFT/tree/master/Examples here].
/* fft.cpp
 
*
 
* This is a KISS implementation of
 
* the Cooley-Tukey recursive FFT algorithm.
 
* This works, and is visibly clear about what is happening where.
 
*
 
* To compile this with the GNU/GCC compiler:
 
* g++ -o fft fft.cpp -lm
 
*
 
* To run the compiled version from a *nix command line:
 
* ./fft
 
*
 
*/
 
#include <complex>
 
#include <cstdio>
 
 
 
#define M_PI 3.14159265358979323846 // Pi constant with double precision
 
 
 
using namespace std;
 
 
 
// separate even/odd elements to lower/upper halves of array respectively.
 
// Due to Butterfly combinations, this turns out to be the simplest way
 
// to get the job done without clobbering the wrong elements.
 
void separate (complex<double>* a, int n) {
 
    complex<double>* b = new complex<double>[n/2];  // get temp heap storage
 
    for(int i=0; i<n/2; i++)    // copy all odd elements to heap storage
 
        b[i] = a[i*2+1];
 
    for(int i=0; i<n/2; i++)    // copy all even elements to lower-half of a[]
 
        a[i] = a[i*2];
 
    for(int i=0; i<n/2; i++)    // copy all odd (from heap) to upper-half of a[]
 
        a[i+n/2] = b[i];
 
    delete[] b;                // delete heap storage
 
}
 
 
 
// N must be a power-of-2, or bad things will happen.
 
// Currently no check for this condition.
 
//
 
// N input samples in X[] are FFT'd and results left in X[].
 
// Because of Nyquist theorem, N samples means
 
// only first N/2 FFT results in X[] are the answer.
 
// (upper half of X[] is a reflection with no new information).
 
void fft2 (complex<double>* X, int N) {
 
    if(N < 2) {
 
        // bottom of recursion.
 
        // Do nothing here, because already X[0] = x[0]
 
    } else {
 
        separate(X,N);      // all evens to lower half, all odds to upper half
 
        fft2(X,    N/2);  // recurse even items
 
        fft2(X+N/2, N/2);  // recurse odd  items
 
        // combine results of two half recursions
 
        for(int k=0; k<N/2; k++) {
 
            complex<double> e = X[k    ];  // even
 
            complex<double> o = X[k+N/2];  // odd
 
                        // w is the "twiddle-factor"
 
            complex<double> w = exp( complex<double>(0,-2.*M_PI*k/N) );
 
            X[k    ] = e + w * o;
 
            X[k+N/2] = e - w * o;
 
        }
 
    }
 
}
 
 
 
// simple test program
 
int main () {
 
    const int nSamples = 64;
 
    double nSeconds = 1.0;                      // total time for sampling
 
    double sampleRate = nSamples / nSeconds;    // n Hz = n / second
 
    double freqResolution = sampleRate / nSamples; // freq step in FFT result
 
    complex<double> x[nSamples];                // storage for sample data
 
    complex<double> X[nSamples];                // storage for FFT answer
 
    const int nFreqs = 5;
 
    double freq[nFreqs] = { 2, 5, 11, 17, 29 }; // known freqs for testing
 
   
 
    // generate samples for testing
 
    for(int i=0; i<nSamples; i++) {
 
        x[i] = complex<double>(0.,0.);
 
        // sum several known sinusoids into x[]
 
        for(int j=0; j<nFreqs; j++)
 
            x[i] += sin( 2*M_PI*freq[j]*i/nSamples );
 
        X[i] = x[i];        // copy into X[] for FFT work & result
 
    }
 
    // compute fft for this data
 
    fft2(X,nSamples);
 
   
 
    printf("  n\tx[]\tX[]\tf\n");      // header line
 
    // loop to print values
 
    for(int i=0; i<nSamples; i++) {
 
        printf("% 3d\t%+.3f\t%+.3f\t%g\n",
 
            i, x[i].real(), abs(X[i]), i*freqResolution );
 
    }
 
}
 
 
 
===An Example===
 
https://github.com/kosme/arduinoFFT/tree/master/Examples
 
  
 
==Authors==
 
==Authors==
Line 239: Line 150:
 
eBox log page [https://classes.engineering.wustl.edu/ese205/core/index.php?title=EBOX_Log here]
 
eBox log page [https://classes.engineering.wustl.edu/ese205/core/index.php?title=EBOX_Log here]
 
==External References==
 
==External References==
https://www.youtube.com/watch?v=spUNpyF58BY&t=21s
+
https://www.youtube.com/watch?v=spUNpyF58BY&t=21s (introduction to a fourier transform)
 +
 
 +
https://www.algorithm-archive.org/contents/cooley_tukey/cooley_tukey.html (cooley tukey better explained)
  
https://www.algorithm-archive.org/contents/cooley_tukey/cooley_tukey.html
+
https://www.youtube.com/watch?v=XtypWS8HZco&t=22s  (introduction to fast fourier transform)
  
https://www.youtube.com/watch?v=XtypWS8HZco&t=22s
+
http://people.scs.carleton.ca/~maheshwa/courses/5703COMP/16Fall/FFT_Report.pdf  (proof of cooley tukey)
  
http://people.scs.carleton.ca/~maheshwa/courses/5703COMP/16Fall/FFT_Report.pdf
+
https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm  (cooley tukey intro)
  
https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm
+
https://www.norwegiancreations.com/2017/08/what-is-fft-and-how-can-you-implement-it-on-an-arduino/    (Arduino FFT code)

Latest revision as of 04:33, 11 December 2018

Overview

Introduction

As the name suggests the Fast Fourier Transform Library enables for the timely computation of a signal's discrete Fourier transform. Instructions on how to download the latest release can be found here. A fast Fourier transform (fFt) would be of interest to any wishing to take a signal or data set from the time domain to the frequency domain.

Materials & Prerequisites

Materials

All items one needs to utilize an FFT with an Arduino are:

    *Computer
    *Arduino
    *USB connector

Prerequisites

Next download latest open-source Arduino Software here.

Process

Background

First, let's begin with a discussion of a general Fourier Transform (FT) and then we will address the Fast Fourier Transform (FFT). The Fourier transform is a mathematical function that decomposes a waveform, which is a function of time, into the frequencies that make it up. The result produced by the Fourier transform is a complex valued function of frequency. The absolute value of the Fourier transform represents the frequency value present in the original function and its complex argument represents the phase offset of the basic sinusoidal in that frequency.

The Fourier transform is also called a generalization of the Fourier series. This term can also be applied to both the frequency domain representation and the mathematical function used. The Fourier transform helps in extending the Fourier series to non-periodic functions, which allows viewing any function as a sum of simple sinusoids. The definition of which is provided below. Note that we use the discrete time definition of the FT, or a Discrete Fourier Transform (DFT), and not the continuous time definition, the main difference between the two being a summation vs an integral. We make this choice because inputs into computers are discrete data points and not continuous so we can not use an integral, and by extension, we can't use the continuous time definition of the FT.

The decomposition of functions into their respective frequencies is a very powerful and useful tool, however, an FT requires massive amounts of computational work. For those of you in computer science, an FT would take O(n^2) time. So although we want to use this tool it is computationally expensive. Or at least this was the case until J.W Cooley and John Tukey came on the scene. They came up with the aptly named Cooley–Tukey FFT algorithm which reduced the time cost on a DFT from O(n^2) to O(nlogn). It recursively breaks down the DFT into smaller DFTs and turns the summation into a dynamic programming problem. So we save on time but we increase our space complexity dramatically, however, due to the continued improvement in transistor technology, modern day computing, for all practical purposes, doesn't care about space complexity until it has to. An example of a field that has to would be computational biology, due to the vast number of different genes in existence that all need to be tested. But that's beside the point, let's continue our discussion of an FFT.

The actual algorithm begins by splitting the matrix into two parts, one with all the even indexed elements, the other part with all the odd indexed elements. We continue spliting the matrices down in the same manner until we can perform a DFT on a manageable matrix. Another way to implement the split, rather than by the even-odd index convention, would be through a reversing the bit value of the array entry's index. You make the choice only on the language you program in; so choose the option that takes up less time to implement for the language.

Coding it will look like:


function cooley_tukey(x)

   N = length(x)
   if (N > 2)
       x_odd = cooley_tukey(x[1:2:N])
       x_even = cooley_tukey(x[2:2:N])
   else
       x_odd = x[1]
       x_even = x[2]
   end
   n = 0:N-1
   half = div(N,2)
   factor = exp.(-2im*pi*n/N)
   return vcat(x_odd .+ x_even .* factor[1:half], x_odd .- x_even .* factor[1:half])

end

FFT on Arduino

Here you can see some classic implementations in other languages, but as you can see the actual implementation is incredibly difficult. You can also find an example of a custom FFT at the same place. Typically one would not code an FFT from scratch unless you had a very specific purpose for it. Many libraries that deal with computational problems regarding any sort of waveform will have an FFT coded in the API that has been vetted by multiple software developers.


To implement a general FFT in an Arduino here are the steps:

1. Get your computer, Arduino, USB-B cable ready

2. Download the Arduino coding terminal on your computer (Web Editor Here)

3. Run the C++ code given below


To really see it in action:

1. Find a digital waveform (an audio signal, voltage signal, anything digital that can be modeled with sinusoids really)

2. Pass the waveform in as the parameter of the FFT

3. Print the output

To download and use Arduino's pre implemented arduino library see instructions here.

Code Implementations

Arduino C

'#'include "arduinoFFT.h" //import the library

'#'define SAMPLES 128 //Must be a power of 2

'#'define SAMPLING_FREQUENCY 1000 //Hz, must be less than 10000 due to ADC

arduinoFFT FFT = arduinoFFT(); // create FFT object

unsigned int sampling_period_us; unsigned long microseconds;

double vReal[SAMPLES]; //Real part of FFT array

double vImag[SAMPLES]; //Imaginary part of FFT aray

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(0);
       vImag[i] = 0;
    
       while(micros() < (microseconds + sampling_period_us)){
       }
   }

   /*FFT*/                                                                // builtin FFT library and methods
   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=0; 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(1000);  //Repeat the process every second OR:
   while(1);       //Run code once

}

  • Note other implementations exist and a few can be found here

More Arduino Examples

More examples on Arduino here.

Authors

  • Jordan Gewirtz
  • Nish Chakraburtty
  • Chanel Lynn

Group Link

eBox project page here

eBox log page here

External References

https://www.youtube.com/watch?v=spUNpyF58BY&t=21s (introduction to a fourier transform)

https://www.algorithm-archive.org/contents/cooley_tukey/cooley_tukey.html (cooley tukey better explained)

https://www.youtube.com/watch?v=XtypWS8HZco&t=22s (introduction to fast fourier transform)

http://people.scs.carleton.ca/~maheshwa/courses/5703COMP/16Fall/FFT_Report.pdf (proof of cooley tukey)

https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm (cooley tukey intro)

https://www.norwegiancreations.com/2017/08/what-is-fft-and-how-can-you-implement-it-on-an-arduino/ (Arduino FFT code)