Trouble with using output of FFT Library

Hi guys,

I’m currently working on a project that requires FFT. I searched online, and the library I found that best matched my needs (as far as being well explained and doing an FFT) was gotten from here: ArduinoFFT - Open Music Labs Wiki . For my project, I’m trying to trigger different electromagnets (using Arduino signals) according to different frequencies present in audio; 1 electromagnet would map to a particular range of frequencies.

I have successfully interfaced the audio signal from a 3.5mm headphone jack with an Arduino UNO using the A0 analog port (and some other electronics, not important for this problem). Because this signal is DC offset by 2.5V, I subtract the bias, and then run the FFT on it. The FFT spits out results, just as I expected, and everything looks fine.

The problem arises when I try to use the FFT results (that are being very rapidly sampled) to blink an LED. Basically this LED is just a marker to tell me that the Arduino is actually sending a signal like I expect it to. If this LED was to work, then I could interface it with the electromagnet (using more electronics). Please find my code below:

#define LIN_OUT8 1 // use the lin8 output function
#define FFT_N 256 // set to 256 point fft

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

int total = 0;
const int bassLED = 13;
void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter
  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)-511.0; // form into an int and subtract DC Offset
      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
    }
    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_lin8(); // take the output of the fft
    sei();
    Serial.println("start");
    for (byte i = 0 ; i < FFT_N/2 ; i++) { 
      Serial.println(fft_lin_out8[i]); // send out the data
    }
  }
  for (int i = 0; i<129; i++) {
   total = total+fft_lin_out8[i]; // adding all items in the fft output list for use in the following "if" filter
}
if (total<170){
  if (fft_lin_out8[128] == 0){
    digitalWrite(bassLED, HIGH); //NOTHING HAPPENS. WHYYY.
  }
}
}

Again, I’m pretty sure I’m good on the electronics side. The LED works, I know what electronics I need. The code above compiles fine, but it just doesn’t do what I want it to. I’m guessing the problem has something to do with the fact that the ADC is in free-running mode.

Any advice on the software side of things is much appreciated.

Thanks!
And sorry, I’m new to software, more experienced with hardware.

Nothing happens because the code never executes that statement because the if statements prevent it. Use serial print to see what the values of those two variables actually is. Are you sure you want to use an index of 128 and not 127?

This isn’t doing what the comments imply.

      int k = ((j << 8) | m)-511.0; // form into an int and subtract DC Offset
      k -= 0x0200; // form into a signed int
      k <<= 6; // form into a 16b signed int

The ADC produces numbers in the range 0 - 1023 (a 10-bit unsigned number). To remove the DC bias, you should subtract 512 which will change the range to -512 to +511 (the correct range for a 10-bit signed number).
This number is now a 16-bit signed integer. Subtracting 512 (0x200) from it again changes the range of the numbers to -1024 to -1 which isn’t useful at all and does not “form into a signed int”. Now all of your samples are negative. You’ve gone from having ADC samples with a +512 DC bias to samples with a -512 DC bias.
The shift left of 6 bits does not “form into a 16b signed int”. It amplifies the original digital signal so that the original 10-bit samples are now 16-bit samples. Nothing wrong with that except that the most negative half of the range will have an incorrect sign bit because you will have shifted it one bit too far.
The simple fix is to use -512 instead of -511 to remove the DC bias and delete this statement “k -= 0x200;”

Plus there’s the problem that @Grumpy_Mike mentioned.

And on top of that, you don’t set total to zero before you add all the magnitudes. The first time through it will be zero because it was declared to be global and initialized to zero. After that, it will be whatever value was computed in the previous pass through the while loop.

Pete

Here is the original OpenMusicLabs code, which does not have the silly floating point “-511.”

   int k = (j << 8) | m; // form into an int
   k -= 0x0200; // form into a signed int
  k <<= 6; // form into a 16b signed int

Hey guys,

Thanks for all the input. So far, I have:

  • Changed my “-511” to a “-512”
  • Removed the line “k -= 0x0200”
  • Changed the index of my if statement from 128 to 127
  • Removed the if statement ENTIRELY (for easier troubleshooting purposes).
  • Unassigned the value “0” to the variable “total” at the beginning of my sketch

Here’s my new code (doesn’t fulfill the same purpose, just meant for troubleshooting):

/*
fft_adc_serial.pde
guest openmusiclabs.com 7.7.14
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.
*/

#define LIN_OUT8 1 // use the lin8 output function
#define FFT_N 256 // set to 256 point fft

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

int total;
const int bassLED = 13;
void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter
  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)-512; // form into an int and subtract DC Offset
      //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
    }
    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_lin8(); // take the output of the fft
    sei();
    Serial.println("start");
    for (byte i = 0 ; i < FFT_N/2 ; i++) { 
      Serial.println(fft_lin_out8[i]); // send out the data
    }
  }

  for (int i = 0; i<128; i++) {
   total = total+fft_lin_out8[i]; // adding all items in the fft output list for use in the following "if" filter
}
Serial.println("Total");
Serial.println(total);
digitalWrite(bassLED, HIGH);
/*if (total>0){
  if (fft_lin_out8[127] == 0){
    digitalWrite(bassLED, HIGH); //NOTHING HAPPENS. WHYYY.
    Serial.println(total);
  }
}*/
}

Notice the last three statements at the end of the sketch (before the last commented out section). These are 3 different (simple) commands that I give to the arduino after it executes the fft.

Unfortunately, here are the results (in order of how the lines are written):

  • The arduino DID NOT print “Total” to the serial monitor
  • The arduino DID NOT print the value of “total” to the serial monitor
  • The arduino DID NOT blink the LED attached to pin 13.

The arduino did however, execute and print out the fft results on the serial monitor.

My problem has still not been solved. Anymore ideas what could be wrong?

Thanks a lot guys.

This sketch’s loop() function contains a while loop that never terminates. That while loop’s opening statement is:  while(1) { // reduces jitterThe closing brace - “}” - for that while loop comes before the code that illuminates the LED, here:

    Serial.println("start");
    for (byte i = 0 ; i < FFT_N/2 ; i++) { 
      Serial.println(fft_lin_out8[i]); // send out the data
    }  //  <-- END OF while(1) LOOP
  for (int i = 0; i<129; i++) {
   total = total+fft_lin_out8[i]; // adding all items in the fft output list for use in the following "if" filter

Once the sketch enters that while loop, it never leaves it. The code that prints the total and lights the LED is unreachable.

It’s not easy to see, because the the statement indention is inconsistent. You can use Tools > AutoFormat to make it easier to identify.

You got the bit about the total wrong. It needs to be set to zero every time before you calculate a new one. Plus everything above.

You guys are my heroes. We have progress.

The LED now turns on.

HOWEVER…It is very dim for some reason. At first, I thought this had something to do with the speed at which this program is running (maybe it was too fast for the LED to come fully on before the program reiterates) but I don’t think this is the case because I made an “artificial_delay” that makes the LED blink at a very slow rate. Then again, I might also be thinking about this very simplistically.

Here’s my code:

/*
  fft_adc_serial.pde
  guest openmusiclabs.com 7.7.14
  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.
*/

#define LIN_OUT8 1 // use the lin8 output function
#define FFT_N 256 // set to 256 point fft

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

int total;
int artificial_delay;
const int bassLED = 13;
void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter
  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) - 512; // form into an int and subtract DC Offset
      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
    }
    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_lin8(); // take the output of the fft
    sei();
    Serial.println("start");
    for (byte i = 0 ; i < FFT_N / 2 ; i++) {
      Serial.println(fft_lin_out8[i]); // send out the data
    }
    for (int i = 0; i < 128; i++) {
      total = total + fft_lin_out8[i]; // adding all items in the fft output list for use in the following "if" filter
    }
    Serial.println("Total"); //WORKS
    Serial.println(total); //WORKS
    digitalWrite(bassLED, HIGH); //works but the LED is very dim for some reason
    total = 0; //Thanks Grumpy_Mike!
    artificial_delay = artificial_delay+1;
    if (artificial_delay >= 10){
      digitalWrite(bassLED, LOW);
      artificial_delay = 0;
    }//Thanks tmd3!
  }


  /*if (total>0){ //IGNORE EVERYTHING IN THIS COMMENTED OUT SECTION.
    if (fft_lin_out8[127] == 0){
      digitalWrite(bassLED, HIGH);
      Serial.println(total);
    }
    }*/
}

To be clear, here are the additional things I’ve done since my last reply:

  • Inserted my troubleshooting lines “Serial.println (“Total”);”,“Serial.println(total);”,“digitalWrite (bassLED, HIGH);” into the while loop so they actually get executed. It worked.
  • Reset the value of my “total” to 0 after each total summation.
  • Created an artificial_delay feature to try to fix the dim LED problem (unsuccessfully).

Thanks for your help so far guys. Any clue as to why the LED would be very dim?

Just to be clear, the fault isn’t with the LED. I ran the Arduino Blink sketch on it before posting this, and the LED was fully bright when running that sketch. Any idea what is wrong with my code?

How long do you think that artificial delay is? It is less than about 1uS, you need something real. Just use the delay function with 100 in it to see something.

Grumpy_Mike: Just use the delay function with 100 in it to see something.

I fear that won't work, without other modifications to the code. One of the first things that the sketch does is turn off the Timer0 overflow interrupt, with this statement:  TIMSK0 = 0; // turn off timer0 for lower jitterThat will make delay(), and all the timing functions, unusable.

... artificial delay ... It is less than about 1uS ...

I think that the "artificial delay" delay is longer than that. As I see it, variable artificial_delay is used to count 10 iterations of the main loop, which includes 256 analog samples at 26 us each, running the fft, and Serial.print()'ing a bunch of stuff each time through. My tests show it's close to 400 ms from the time the LED is turned on until it's turned off again.

I think I know what's making the LED dim. I don't want to deprive the OP of the joy of figuring it out, or of the chance to improve his self-reportedly limited programming skills, so I won't blurt it. Instead, I'll note that the LED is bright with the Blink sketch and dim with this one, and I'll ask the OP: what does the Blink sketch do that this sketch doesn't do?

wow.

tmd3, you are literally my hero.

I can't believe I overlooked something so simple.

The problem was that I forgot to set "pinMode(bassLED, OUTPUT);" at the beginning of the sketch.

I was under the impression that the sketch just wouldn't compile if something like that was missing. I was wrong.

Thanks for your input Grumpy_Mike and tmd3 (and anyone else who helped). I'm so happy this community exists. You've just made my day.

Thanks for everything, A very happy engineer.

P.S. I'll post on this thread if any other problems arise. I love you guys man.