Go Down

Topic: Spectrum Analyzer delay (Read 667 times) previous topic - next topic

erosten

Hi all. I've been trying to make a spectrum analyzer, but the LED matrix output is a bit delayed from the sound I'm hearing and I was wondering if I can get some advice on how to reduce that. First off some specs:


To get the sound into the arduino, I'm using what I think to be a pretty standard setup shown below.




The audio is coming from a breadboard aux jack, amplified by the non inverting amplifier and level shifted to oscillate around 2.5V. I've oscilliscoped the input vs the output of this stage into the arduino and there seems to be little delay, although I'm obviously open to the chance something might be happening in this analog portion.

Once inside the arduino, I'm processing with an FHT and outputting to an 8x8 MAX7221. The code is below and the fht implementation can be found here.

I've tried both the fft and fht from the same source and the fht is noticeably faster. I was wondering what the difference in the output is between the two (FFT I understand pretty well), but that may be a conversation for another topic. In any case, the FHT seems faster.

The MAX7221 is being controlled with another library which can be found here.

My full code is below.
Code: [Select]

//
#define LOG_OUT 0 // use the log output function
#define FHT_N 256 // set to 256 point fht
#define LIN_OUT 1
//#define DIN 12
//#define CS 11
//#define CLK 10

#include <LedControl.h>
#include <FHT.h>

int DIN = 12;
int CS = 11;
int CLK = 10;
//int delayTime = 5;
//const float factors[8] = {1, 1.1, 1.35, 1.45, 1.55, 1.7, 1.9, 2.1};       //factors to increase the height of each column
//const float factors[8] = {1, 1, 1, 1, 1, 1.1, 1.3, 1.3};       //factors to increase the height of each column
const float factors[8] = {1, 1, 1, 1, 1, 1, 1, 1};       //factors to increase the height of each column
boolean clipping = 0;
int clippingCounter = 5000;

unsigned char hs[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};              //height of each column



LedControl lc = LedControl(DIN, CLK, CS, 0);

void setup() {
  lc.shutdown(0, false);      //The MAX72XX is in power-saving mode on startup
  lc.setIntensity(0, 2);     // Set the brightness to maximum value
  lc.clearDisplay(0);         // and clear the display
  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
  Serial.begin(115200); // use the serial port
  pinMode(13,OUTPUT);//led indicator pin

}
//const unsigned char borders[9] = {0, 3, 5, 7, 13, 32, 64, 100, 127};   //borders of the frequency areas
//const unsigned char borders[9] = {0, 3, 7, 10, 13, 16, 24, 32, 69};   //borders of the frequency areas
const unsigned char borders[9] = {0, 3, 6, 9, 10, 13, 20, 32, 69};   //borders of the frequency areas


void setColumn(int column, unsigned char height) {
  // maps height from 0 to 255 to 0 to 8
  unsigned char h = (unsigned char) map(height, 0, 255, 0, 8);
  // apply multipliers to heights
  h = (unsigned char)(h * factors[column]);
  // if new h is less than previous value, decrement
  if (h < hs[column]) {
    hs[column]--;
  } else if (h > hs[column]) {
    hs[column] = h;
  }
//    hs[column] = h;

  // light up LEDs
  for (unsigned char y = 0; y < 8; y++) {
    if (hs[column] > y) {
      // addr then col then row
      lc.setLed(0, column, y, true);
    } else {
      lc.setLed(0, column, y, false);
    }

  }
}

void loop() {
  while (1) { // reduces jitter
    cli();  // UDRE interrupt slows this way down on arduino1.0
    for (int i = 0 ; i < FHT_N ; i++) { // 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
      fht_input[i] = k; // put real data into even bins
//      fht_input[i + 1] = 0; // set odd bins to 0
        if (j == 0 || j == 255){//if clipping
    digitalWrite(13,HIGH);//set pin 13 high
    clipping = 1;//currently clipping
    clippingCounter = 5000;
  }
    if (clipping){//if currently clipping
      clipping = 0;
    }else{
          clippingCounter--;
       if (clippingCounter == 0){
    digitalWrite(13,LOW);//turn off clipping led indicator (pin 13)
  }
    }
   
   
    }
    fht_window(); // window the data for better frequency response
    fht_reorder(); // reorder the data before doing the fht
    fht_run(); // process the data in the fht
    fht_mag_lin(); // take the output of the fht
    sei();

    // sets DC components to 0
    fht_lin_out[0] = 0;
    fht_lin_out[1] = 0;
   
    // for each column
    for (int i = 0; i < 8; i++) {
      // find the maximum value within the specified frequency borders
      unsigned char maxVal = 0;
      for (unsigned char j = borders[i]; j < borders[i + 1]; j++) {
        if ((unsigned char)fht_lin_out[j] > maxVal) {
          maxVal = (unsigned char) fht_lin_out[j];
        }
//        if (maxVal < 100){
//          maxVal = 1;
//        }
//        if (maxVal < 50){
//          maxVal = 0;
//        }
      }
      // determine height and light up LED
      setColumn(i, maxVal);
   
      Serial.print(maxVal);
      Serial.print("\t");
    }
    Serial.println("");
  }
}


Ultimately, I'm wondering if the Arduino UNO simply isn't fast enough to run this FHT and send out the data to the ports. My other suspicion is that either the LedControl library is too slow, or the LED drivers on the MAX7221 are the problem. The datasheet for the MAX7221 can be found here.

Any suggestions would be very helpful. I can also make a small video if necessary to show the delay.

Thank you!

DVDdoug

Quote
I've oscilliscoped the input vs the output of this stage into the arduino and there seems to be little delay, although I'm obviously open to the chance something might be happening in this analog portion.
No, there's no delay in the analog amplifier.  It could be a characteristic of your 'scope.

From what I understand, the FFT & FHT libraries take a certain number of samples, then run the FFT/FHT on it and the analysis has to finish before it reads another set of samples.  So, it's not analyzing continuously and there is a delay.

The LED drivers are probably "faster than the eye".  I've used a different MAX driver (and a different sound-activated effect) and I slowed it down to make sure the LEDs are on or off for at least 1/10th of second so I could see it.



BTW - If you turn the pot to zero resistance you'll have no feedback and (nearly) infinite gain.   That's OK in your application but it's "dangerous" in an audio application (where you might blow-out a power amplifier or speaker.

pjrc

Even though you have 115200, all the serial printing can't be helping.

jremington

Quote
Ultimately, I'm wondering if the Arduino UNO simply isn't fast enough to run this FHT
No need to wonder. The time it takes to run is cited on the FHT web page, about 4 ms for a 256 point transform, using a standard Arduino Uno.

erosten

Even though you have 115200, all the serial printing can't be helping.
I only use it to debug. I've usually comment it out, but delay still seems present.

No need to wonder. The time it takes to run is cited on the FHT web page, about 4 ms for a 256 point transform, using a standard Arduino Uno.
Right, pretty stupid of me. Seems a bit long in-between updates. Would you advise using a 128 point transform instead? Or if I want a faster response, move away from the Arduino UNO altogether and use something like a Teensy or Arduino Zero?

Thanks for all the help everyone.


jremington

#5
Sep 05, 2018, 05:49 am Last Edit: Sep 05, 2018, 05:51 am by jremington
It takes a certain amount of time to collect 256 samples, then you do window(), reorder(), run() and magnitude() on each set. Finally, you run through all those results, set up the LED columns, and print the max. Each of those steps takes a known amount of time, which you can look up.

Approximately the same output could be accomplished in roughly half the time if you took 128 samples, or 1/4 the time if you took 64 samples, etc.

Or, you could go to a faster processor.

erosten

Right. Assuming a faster processor as a solution, do you think a faster Arduino is a good alternative, or should I move to a different microcontroller altogether?

pjrc

#7
Sep 05, 2018, 12:35 pm Last Edit: Sep 05, 2018, 12:38 pm by pjrc
Or if I want a faster response, move away from the Arduino UNO altogether and use something like a Teensy or Arduino Zero?
I can tell you this works very well on Teensy 3.2 & higher.

Here's an example I wrote a few years ago which does 1024 pin FFT and shows the result on a 60x32 array of LEDs.

https://github.com/PaulStoffregen/OctoWS2811/blob/master/examples/SpectrumAnalyzer/SpectrumAnalyzer.ino

The FFT is done 50% overlapped on 44 kHz sampled audio, so the final refresh rate is ~86 Hz.  Visually it responds very rapidly.

DVDdoug

Quote
No need to wonder. The time it takes to run is cited on the FHT web page, about 4 ms for a 256 point transform, using a standard Arduino Uno.
4ms is only 4/1000ths of a second.   That (alone) is not enough time-difference between the eye and ear to notice.    Sound travels at about 1ms per foot, so if someone is 4 feet away and they clap their hands you see then clap before you hear it but you are not aware of the time difference.  

There is also a delay while you sample (and I assume it's not interrupting and sampling while processing the previous sample-set).   Of course that depends on the number of samples and the sample rate.

And, there's another delay while you write to your display.

All of the delays add-up and a faster processor will be faster, except it always takes the same amount of time to read-in a given number of samples at a given sample rate.

MarkT

Another approach, since you have only 8 frequency buckets, is to drive 8 bandpass digital filters in parallel
from the samples and derive the levels from each filtered value by envelop detection.  Using fixed-point
calculations and simple 2-pole filters ought to be feasible, and has minimum delay
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

pjrc

This is true, but it doesn't take into account simple blocking way 8 bit AVR is typically used, which makes things much worse.

All of the delays add-up and a faster processor will be faster, except it always takes the same amount of time to read-in a given number of samples at a given sample rate.
When done with blocking operations, the unavoidable delays add up in bad ways.

On a more powerful processor with DMA, the input of ADC samples and the output of pixel data can be done with non-blocking I/O.  The processor which is already much faster can spend nearly all of its power doing the analysis, free from the burden of bringing samples in and sending pixels out.

Leveraged correctly, as done in that example I posted, the input is sampled continuously.  There are never any "blind times" where the processor can't keep acquiring the signal because it's busy doing analysis or sending pixels.  Those blind times aren't technically a delay, but they can make the entire system seem unresponsive if the missed audio samples contain a dramatic event like a percussive sound or the sharp attack of a loud musical instrument.

The other "blind" spot of simple processing involves the window scaling applied before the FFT.  A Hanning or similar window scales the samples so only the middle of the acquired time has a strong influence.  This is necessary to prevent "spectral leakage" due to discontinuities between each FFT's analysis time.  The solution to this problem is to perform twice as many FFTs, where each FFT uses 50% of the prior FFT's data.  Each one is mostly sensitive to the middle of time span of its sample set.

Eliminating all the blindness to input and keeping a consistent >60 Hz refresh rate of the LEDs does indeed give a highly responsive display.  If you run that example on a Teensy 3.2 with a big array of LEDs, you'll see it is very "fast" in response to music.

Unfortunately, with a 8 bit AVR which is so limited in CPU power and requires taxing the CPU heaving to bring in ADC samples and to push out the pixels, it's extremely difficult to build a highly responsive-to-music spectrum display.  For limited AVR chips, offloading the analysis to a chip like MSEG7 is the best way.




Go Up