FFT Results

Hello,
I've been trying to use the FFT library to measure the frequency from a function generator connected to the analogue pins of the Arduino. The set up is simple: A crocodile probe is connected from the function generator to the A0 and Ground of the Arduino. When I generate functions that are low in frequency (10Hz - 90Hz), the results aren't too bad. When I go above 100Hz, it just picks up odd frequencies. I would like it to be more accurate and consistent, but I don't know what to change in the code. I kept messing around with the number of samples, sampling frequencies, and time intervals, but I never got the frequency that the function generator is producing. I've been working on this for quite some time, but I got nowhere. I would truly appreciate any help. Thank you! Please find code below:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include "arduinoFFT.h"

arduinoFFT FFT = arduinoFFT(); /* Create FFT object */

/*
  These values can be changed in order to evaluate the functions
*/

const uint16_t samples = 128; //This value MUST ALWAYS be a power of 2
const uint8_t amplitude = 100;
const double samplingFrequency = 1000;
unsigned long sampling_period = 1000000 / samplingFrequency; // since time is in micros here

/*
  These are the input and output vectors
  Input vectors receive computed results from FFT
*/

double vReal[samples];
double vImag[samples];

#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02
#define SCL_PLOT 0x03

void setup()
{
  Serial.begin(115200);
  Serial.println("Ready");
}

void loop()
{
  uint32_t ts1 = micros();
  int reading = analogRead(A0);  // Analogue 0
  float signalFrequency = reading * (5.0 / 1023.0); // normalize

  /* Build raw data */
  double cycles = (((samples - 1) * signalFrequency) / samplingFrequency); //Number of signal cycles that the sampling will read
  for (uint16_t i = 0; i < samples; i++)
  {
    vReal[i] = int8_t((amplitude * (sin((i * (twoPi * cycles)) / samples))) / 2.0);/* Build data with positive and negative values*/
    //vReal[i] = uint8_t((amplitude * (sin((i * (twoPi * cycles)) / samples) + 1.0)) / 2.0);/* Build data displaced on the Y axis to include only positive values*/
    vImag[i] = 0.0; //Imaginary part must be zeroed in case of looping to avoid wrong calculations and overflows
  }
  /* Print the results of the simulated sampling according to time */
  Serial.println("Data:");
  PrintVector(vReal, samples, SCL_TIME);
  FFT.Windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);  /* Weigh data */
  Serial.println("Weighed data:");
  PrintVector(vReal, samples, SCL_TIME);
  FFT.Compute(vReal, vImag, samples, FFT_FORWARD); /* Compute FFT */
  Serial.println("Computed Real values:");
  PrintVector(vReal, samples, SCL_INDEX);
  Serial.println("Computed Imaginary values:");
  PrintVector(vImag, samples, SCL_INDEX);
  FFT.ComplexToMagnitude(vReal, vImag, samples); /* Compute magnitudes */
  Serial.println("Computed magnitudes:");
  PrintVector(vReal, (samples >> 1), SCL_FREQUENCY);
  double x;
  double v;
  FFT.MajorPeak(vReal, samples, samplingFrequency, &x, &v);
  Serial.print(x, 6);
  Serial.print(", ");
  Serial.println(v, 6);


  while ( (micros() - ts1) < sampling_period );  // constant time interval
}

void PrintVector(double *vData, uint16_t bufferSize, uint8_t scaleType)
{
  for (uint16_t i = 0; i < bufferSize; i++)
  {
    double abscissa;
    /* Print abscissa value */
    switch (scaleType)
    {
      case SCL_INDEX:
        abscissa = (i * 1.0);
        break;
      case SCL_TIME:
        abscissa = ((i * 1.0) / samplingFrequency);
        break;
      case SCL_FREQUENCY:
        abscissa = ((i * 1.0 * samplingFrequency) / samples);
        break;
    }
    Serial.print(abscissa, 6);
    if (scaleType == SCL_FREQUENCY) {
      Serial.print("Hz");
      Serial.print(" ");
      Serial.println(vData[i], 4);
    }
    Serial.println();
    Serial.print("\n");
  }
}

The basis of the code was taken from this link

That coded doesn't sample the signal from A0. It reads A0 once and uses that one-time sample to set the frequency of a virtual sine wave that it synthesizes.

Try the OpenMusicsLabs FFT example code, which actually does sample the analog input, as data to be transformed.

...I've never used the FFT library.

The set up is simple: A crocodile probe is connected from the function generator to the A0 and Ground of the Arduino.

You need to bias the input. (I assume this is an AC sine wave?)

[u]Here[/u] are some schematics.

The Arduino can't read negative voltages (it can be damaged by negative voltages) so without the bias you're reading a half-wave rectified sine wave, which has lots of harmonics.

The DC (zero-Hz) bias does give you FFT results in the zero-Hz bin, but you can simply ignore it.

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

For higher frequencies change this number, for stm32 is working with 40 000 (40kHz) which is working for signals up to 40/2 = 20 kHz

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

Thank you all for your replies. Regarding the negative voltages, the function generator has an offset to regard for that. I tried the link proposed by "jermington" (ArduinoFFT - Open Music Labs Wiki).
The example uses Serial.write. I changed it to Serial.println and added a for loop. I'm not sure if the for loop is correct. The results don't make sense; they're shown in the figure below. In addition, please find my code below:

/*
  fft_adc.pde
  guest openmusiclabs.com 8.18.12
  example sketch for testing the fft library.
  it takes in data on ADC0 (Analog0) and processes them
  with the fft. the data is sent out over the serial
  port at 115.2kb.  there is a pure data patch for
  visualizing the data.
*/

// do #defines BEFORE #includes
#define LOG_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft

#include <FFT.h> // include the library

void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter - delay() and millis() killed
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
}

void loop() {
  while (1) { // reduces jitter
    cli();  // UDRE interrupt slows this way down on arduino1.0
    for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
      while (!(ADCSRA & 0x10)); // wait for adc to be ready
      ADCSRA = 0xf5; // restart adc
      byte m = ADCL; // fetch adc data
      byte j = ADCH;
      int k = (j << 8) | m; // form into an int
      k -= 0x0200; // form into a signed int
      k <<= 6; // form into a 16b signed int
      fft_input[i] = k; // put real data into even bins
      fft_input[i + 1] = 0; // set odd bins to 0
    }
    // window data, then reorder, then run, then take output
    fft_window(); // window the data for better frequency response
    fft_reorder(); // reorder the data before doing the fft
    fft_run(); // process the data in the fft
    fft_mag_log(); // take the output of the fft
    sei(); // turn interrupts back on
    Serial.println(255); // send a start byte

    for (int j = 0 ; j < 256 ; j++) {
      Serial.println(fft_log_out[j], 128); // send out the data
    }
    Serial.print("\n");
  }
}

Results: Please find in attachment.

the function generator has an offset to regard for that

What, exactly, is the function generator output?

The "for loop" is not correct. There are only 128 unique values in the Fourier transform of a 256 element real input array, and you are not correctly using Serial.println().

Delete this line:

   Serial.println(255); // send a start byte

Change this:

   for (int j = 0 ; j < 256 ; j++) {
      Serial.println(fft_log_out[j], 128); // send out the data

to this:

   for (int j = 0 ; j < 128 ; j++) {
      Serial.println(fft_log_out[j]); // print the result on the serial monitor

It is always best to test unfamiliar code with an example where you know what the result should be. Try this test, which generates a input waveform and transforms it.

/*
 fft_test_sine
 example sketch for testing the fft library.
 This generates a simple sine wave data set consisting
 of two frequences f1 and f2, transforms it, calculates 
 and prints the amplitude of the transform.
 */

// do #defines BEFORE #includes
#define LIN_OUT 1 // use the lin output function
#define FFT_N 64 // set to 64 point fft

#include <FFT.h> // include the library

void setup() {
  Serial.begin(9600); // output on the serial port
  int i,k;
  float f1=2.0,f2=5.0;  //the two input frequencies (bin values)

  for (i = 0 ; i < FFT_N ; i++) { // create samples
    // amplitudes are 1000 for f1 and 500 for f2
    k=1000.*sin(2*PI*f1*i/FFT_N)+500.*sin(2*PI*f2*i/FFT_N);
    fft_input[2*i] = k; // put real data into even bins
    fft_input[2*i+1] = 0; // set odd bins to 0
  }
  
//  fft_window();  //Try with and without this line, it smears

  fft_reorder(); // reorder the data before doing the fft
  fft_run(); // process the data using the fft
  fft_mag_lin(); // calculate the magnitude of the output

  // print the frequency index and amplitudes

  Serial.println("bin  amplitude");
  for (i=0; i<FFT_N/2; i++) {
    Serial.print(i);
    Serial.print("       ");
    Serial.println(2*fft_lin_out[i]); //*2 for "negative frequency" amplitude
  }
  Serial.println("Done");
}

void loop() {}

Thank you so much for your reply!

I generate for example 100 Hz Sine function that oscillates between 0 and 5 Volts. I understand your answer and I tried out the test you proposed and it works! I've also modified the code accordingly (code below). However the values outputted are in bins. These are a sample of the results I get:

Results:


19
0
0
19
0
24
0
8
8
0
8
24
16
8
0
19
0
8
0
19
24
8
8
8
8
19
8
0
0
8
16
19
19
0
19
0
8
0


/*
  fft_adc.pde
  guest openmusiclabs.com 8.18.12
  example sketch for testing the fft library.
  it takes in data on ADC0 (Analog0) and processes them
  with the fft. the data is sent out over the serial
  port at 115.2kb.  there is a pure data patch for
  visualizing the data.
*/

// do #defines BEFORE #includes
#define LOG_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft

#include <FFT.h> // include the library

void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter - delay() and millis() killed
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
}

void loop() {
  while (1) { // reduces jitter
    cli();  // UDRE interrupt slows this way down on arduino1.0
    for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
      while (!(ADCSRA & 0x10)); // wait for adc to be ready
      ADCSRA = 0xf5; // restart adc
      byte m = ADCL; // fetch adc data
      byte j = ADCH;
      int k = (j << 8) | m; // form into an int
      k -= 0x0200; // form into a signed int
      k <<= 6; // form into a 16b signed int
      fft_input[i] = k; // put real data into even bins
      fft_input[i + 1] = 0; // set odd bins to 0
    }
    // window data, then reorder, then run, then take output
    fft_window(); // window the data for better frequency response
    fft_reorder(); // reorder the data before doing the fft
    fft_run(); // process the data in the fft
    fft_mag_log(); // take the output of the fft
    sei(); // turn interrupts back on

    for (int j = 0 ; j < 128 ; j++) {
      Serial.println(fft_log_out[j]); // send out the data
    }
  }
}

How am I supposed to interpret these results?
How can I convert it to Hz?

Thank you!

fsalloum:
How am I supposed to interpret these results?
How can I convert it to Hz?

Assuming N bins, then the frequency for bin n would be:

n * Δf for 0 ≤ n < N/2

and

(n - N) * Δf for N/2 ≤ n < N

where Δf = 1 / (T * N)
and T = sampling period.

I'll suggest that you print the input array, after it's been collected, and before it's been processed, to see what it really looks like.

How am I supposed to interpret these results?
How can I convert it to Hz?

I suspect that now might be a good time to read an introductory tutorial on what the FFT does.

The result you posted does not show an obvious signal and may be just noise.

Thank you all!