FFT Analysis: Questions about values in bins

Hi all, I modified example code to print out the values in each bin of FFT performed on ~1000Hz. I just have a couple of questions regarding these values. Attached below is a file containing the results and my code.

  1. Why are the values in the first two bins so high? I read in another post that it may be due to the DC offset. I thought I accounted for it, as I used a simple voltage divider to feed 2.5v to A0, and subtracted the offset (512) before feeding the values into the FFT input array.

  2. There seems to be a “bell curve” of values surrounding the 1050Hz bin. Is this normal?

  3. The value in the 1050Hz bin is high, over 6500. What does this value represent, and is it an acceptable value?

  4. The values in the other bins are low (<10), but not 0. Relative to the dominant frequencies, are these low values acceptable? If not, how can I reduce these numbers?

/*
  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 LIN_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#define DC_OFFSET 512
#define SAMPLING_FREQ (16000000/(13*32))

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

int adc_val;

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
      adc_val = k - 512;
      fft_input[i] = adc_val; // 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_lin(); // take the output of the fft
    sei(); // turn interrupts back on

    Serial.println("start");
    for (int j = 0 ; j < 128 ; j++) {
      Serial.print("Frequency: "); Serial.print(j * (SAMPLING_FREQ / FFT_N)); Serial.print(" ");
      Serial.println(fft_lin_out[j]); // print out the data
    }
    Serial.print("\n");
  }
}

results1.txt (13.1 KB)

  1. The offset in your code is being subtracted twice, incorrectly. Measure the offset using the ADC and use that value.
    This line subtracts 512, represented as a hexadecimal constant for no good reason.
      k -= 0x0200; // form into a signed int
  1. Yes, the "bell curve" is normal. The sample interval is not an integer multiple of the peak input frequency, which introduces sampling errors.

  2. The value is what it is, and is related proportionally to the input signal amplitude, the number of samples and other factors.

  3. The low values are noise, and are unavoidable.

jremington:

  1. The offset in your code is being subtracted twice, incorrectly. Measure the offset using the ADC and use that value.
    This line subtracts 512, represented as a hexadecimal constant for no good reason.
      k -= 0x0200; // form into a signed int

Hi jremington - I measured the adc offset using the below code, and I got values of 510-511, so I changed DC_OFFSET to 511. I also removed the line that subtracts 512 as a hexadecimal constant. However, I’m still getting large values in the first two bins. Also, when I tried playing a 1000Hz tone, I’m getting relatively large values in all of the bins, with an expected peak in the 1050Hz bin, but also peaks in other bins. I’d really appreciate it if you could help me with another diagnosis. (Files attached below)

int value;
void setup() {
  Serial.begin(9600);          //  setup serial
 }

void loop() {
  value = analogRead(A0);
  Serial.println(value);
}

noSound_1.txt (4.47 KB)

1000Hz_1.txt (4.71 KB)

Post the revised code.

I don't see any problems with 1000Hz_1.txt. From the spectrum, it looks like you are applying a 1000 Hz square wave, with the expected odd harmonics at 3kHz, 5 kHz, etc.

You forgot to tell us about the signal that you are presenting to the input. How is it generated, what is the waveform and what is its amplitude? Please post a hand drawn circuit diagram showing how you have applied the input signal.

If the signal is not a very pure sine wave, or if the signal voltage plus bias exceeds the allowed analog input voltage range, expect peaks at submultiples and multiples of the base frequency in the spectrum.

jremington:
Post the revised code.

/*
  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 LIN_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#define DC_OFFSET 511
#define SAMPLING_FREQ (16000000/(13*32))

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

int adc_val;

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 <<= 6; // form into a 16b signed int
      adc_val = k - DC_OFFSET;
      fft_input[i] = adc_val; // 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_lin(); // take the output of the fft
    sei(); // turn interrupts back on

    Serial.println("start");
    for (int j = 0 ; j < 128 ; j++) {
      Serial.print("Frequency: "); Serial.print(j * (SAMPLING_FREQ / FFT_N)); Serial.print(" ");
      Serial.println(fft_lin_out[j]); // print out the data
    }
    Serial.print("\n");
  }
}

jremington:
I don’t see any problems with 1000Hz_1.txt. From the spectrum, it looks like you are applying a 1000 Hz square wave, with the expected odd harmonics at 3kHz, 5 kHz, etc.

What about the values in the other bins, should they not be 0? Also, I’m still not sure as to why there are large values in the first two bins when it is silent.

jremington:
You forgot to tell us about the signal that you are presenting to the input. How is it generated, what is the waveform and what is its amplitude? Please post a hand drawn circuit diagram showing how you have applied the input signal.

I’m using a tone generator app, which unfortunately does not give me the amplitude of the wave. It should be a sine wave. (I don’t own an oscilloscope). Audio is fed to my circuit via my iPhone 6 plus jack, which is connected to a female socket with pins for ground, right, and left channels. I’m going to look for a better signal source.

What about the values in the other bins, should they not be 0?

No, of course not. There is noise in the signal generator, the ADC, etc. All measurements, without exception, suffer from background noise.

The 1000Hz_1.txt shows that you are correctly handling the DC offset. It is very clear from the spectrum that the signal being transformed is NOT a sine wave.

However, since you failed to post a circuit diagram showing how the bias and the signal are applied to the Arduino, it is impossible to guess what the problem with noSound_1.txt might be.

This is how some people have made the connection:
Audio Bias.jpg

If you want to learn how Fourier transforms work (and experience some of the special considerations that go along with the FFT method), it is much better to start with a known signal than an unknown signal.

This example shows how to create a data sample with two sine waves, then transform that to see how the spectrum appears.

#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.0*sin(2*PI*f1*i/FFT_N)+500.0*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() {}

jremington:
However, since you failed to post a circuit diagram showing how the bias and the signal are applied to the Arduino, it is impossible to guess what the problem with noSound_1.txt might be.

Hi jremington, thank you for your continued help. Your suggestions are giving me a lot of insight into my problems, and I thank you for that.

I tried uploading my circuit into my last reply, but it didn't seem to work. This should work:

I just noticed that the code is still not handling the offset correctly and now I don’t understand the 1000Hz.txt output.

Instead of:

      k <<= 6; // form into a 16b signed int
      adc_val = k - DC_OFFSET;

use

   k -= DC_OFFSET;
   adc_val = k<<6;

After changing that, please post the noSound output.

In general the input circuit is fine, but the gain of the op amp depends on how you have set the 10K variable resistor. What is the value of that resistor? If you don’t have a multimeter, set the resistor to maximum (for minimum gain) then gradually decrease it, while observing the FFT spectrum.

The voltage output by the op amp output is easily capable of exceeding 5V (or negative values) to the Arduino, and could damage the Arduino. Put a 10K resistor between the junction of the voltage divider and the analog input to protect the Arduino input.

Finally, op amp circuits generally require a bypass capacitor, typically 0.1 uF, directly across the power pins of the op amp. Otherwise you risk self oscillation.

The spectrum makes it clear that you are not presenting a sine wave to the analog input.

jremington:
After changing that, please post the noSound output.

noSound file below after making the change in code (large values in first two bins still present), and using an online sine wave generator. Online Tone Generator - generate pure tones of any frequency. Have not yet added 10k resistor and bypass capacitor.

jremington:
In general the input circuit is fine, but the gain of the op amp depends on how you have set the 10K variable resistor. What is the value of that resistor? If you don't have a multimeter, set the resistor to maximum (for minimum gain) then gradually decrease it, while observing the FFT spectrum.

I've attached files for 1000Hz at max and min resistance. Min resistance shows a substantial gain, which is in line with what should be expected. However, there are still somewhat large values present in the first two bins.

jremington:
The voltage output by the op amp output is easily capable of exceeding 5V (or negative values) to the Arduino, and could damage the Arduino. Put a 10K resistor between the junction of the voltage divider and the analog input to protect the Arduino input.

Forgive me if this is a noob question, but wouldn't that create a voltage drop from the 2.5v node to A0, thus screwing up the DC offset?

jremington:
Finally, op amp circuits generally require a bypass capacitor, typically 0.1 uF, directly across the power pins of the op amp. Otherwise you risk self oscillation.

Is placing a 10uF electrolytic capacitor with its positive lead at VCC+ of the op amp and negative lead at VCC- sufficient?

Files are attached here

noSound_2.txt (4.47 KB)

1000Hz_2_max_resistance.txt (4.46 KB)

1000Hz_2_min_resistance.txt (4.49 KB)

I've attached files for 1000Hz at max and min resistance.

Both are improved substantially by fixing the offset correction. The spectrum output at max resistance (minimum gain) is close to that of a pure sine wave, while that at min resistance (max gain) is characteristic of serious signal distortion, with peaks at odd multiples of the input frequency, due to peak clipping. The peak at 0 Hz, due to the remaining DC offset, is close enough to zero, and doesn't matter in this case. As you can see, the amplitude of the spectral peak depends on the amplitude of the input signal.

wouldn't that create a voltage drop from the 2.5v node to A0, thus screwing up the DC offset?

No, the inputs draw no current, unless the input voltage exceeds 5V or is below 0, either of which can destroy the input. Hence the 10K resistor.

placing a 10uF electrolytic capacitor

No, in general you need about 100 nF ceramic, as close a possible to the IC. Look up "IC bypass capacitor".

In theory, an ideal 10 microfarad capacitor would work just fine. In practice, a 10 microfarad electrolytic capacitor tends to have long leads, both externally and internally, that will tend to defeat your purpose. It may work but be prepared to change capacitors.

Where is the op amp getting its DC bias on the non-inverting input? What circuit components exactly are "left" and "right" connected to? If it's just DC blocking capacitors, you have left the input "floating".

jremington:
Both are improved substantially by fixing the offset correction. The spectrum output at max resistance (minimum gain) is close to that of a pure sine wave

The peak at 0 Hz, due to the remaining DC offset, is close enough to zero, and doesn't matter in this case.

So the remaining values in the first two bins are good enough for displaying on an LED matrix for a spectrum analyzer? I was trying to get them as close as zero as possible, as I was worried they would show up on the spectrum analyzer.

aarg:
Where is the op amp getting its DC bias on the non-inverting input? What circuit components exactly are "left" and "right" connected to? If it's just DC blocking capacitors, you have left the input "floating".

This is how my circuit is getting its input. 3.5mm audio cable is connected to my iPhone 6 jack. The white and green wires are the right and left channels, and the white cable closer to the jack is the ground wire. As shown in my circuit drawing in reply #7, the green and white cables connect to the 5k resistors which end at the same node, which is input to the op-amp. Op amp output is then fed into the DC bias. The 10uF capacitor at the op-amp output is my DC blocking capacitor, so I don't think I've done anything wrong, right?

We don't know what is inside the iPhone. There might be capacitors in series with the outputs, which would lead to a floating input on the op amp.

I suggest to connect a 100K resistor from the + terminal of the op amp to ground, as there should be a path to ground for the op amp input bias current.

So the remaining values in the first two bins are good enough for displaying on an LED matrix for a spectrum analyze

There is no reason to send the 0 Hz bin to the display. You can also subtract the background that you observe with no sound input (set negative results to zero).