arduinoFFT problems

Status
Not open for further replies.

SMaric

Member
Teensy3.1

ADC Sampling at 10KHz - seems OK (have checked LED toggling with scope)

Not sure if I'm feeding the FFT correctly
Or if I'm reading results from FFT correctly --- only seem to get 'noise-like levels' from FFT


Code:
#include "ADC.h"
#include <IntervalTimer.h>
#include "arduinoFFT.h"



// when the ADC measurement finishes, this will be called 
void adc0_isr() {
  int adcVal = adc->adc0->readSingle();
  if(!bFFTDataAvailable){
    adcVal -= 0x800;  //  12Bit ADC Sampling --- form signed int
    vReal[sampleCntr] = (double)(adcVal);
    vImag[sampleCntr++] = 0;  //  clear the imaginary value
    if(sampleCntr >= N_samples){
      bFFTDataAvailable = true;
      sampleCntr = 0;
    }
  }
  
......
code snippet from main loop

  if(bFFTDataAvailable){
    FFT.Windowing(vReal, N_samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);	// Weight data
    FFT.Compute(vReal, vImag, N_samples, FFT_FORWARD); // Compute FFT
    FFT.ComplexToMagnitude(vReal, vImag, N_samples); // Compute magnitudes
    
    //  Now update the control values from the FFT results ( fBin(x) )
    updateControls();
    
    bFFTDataAvailable = false;
  }


In updateControls() - I'm extracting the max value from a group of the frequency buckets & am using that value to drive my controls
But control signal just seems to be 'noise' rather than frequency-aligned


Any thoughts as to what I've got wrong/missed
 
Snippets and stuff may be faster to read - but without complete code it can't be executed and the piece you snip out may in fact be the problem area . . . and thus was born the forum rule . . .

BTW - this is exactly what I want to do and have been distracted a month - so I'm just looking for a reason to get back to my issue.
 
Last edited:
Timing could be an issue.

Never used ArduinoFFT. It seems like vReal and vImag are doubles, because of (double)(adcVal). This will cause the FFT and related functions to spent significant time to do the calculations.
From the snippets you show, you do not seem to stop the interrupt routine, so while calculating the FFT, the ISR continues filling vReal with new data.
Edit: A ping pong buffer approach might solve the issue.

Furthermore, why not do it the 'Teensy way' and use the Audio library?

Edit2: Just had a look at the library and it really looks like they really tried very hard to make the library as slow as possible. (Not a typo, i really said slow). At least have a look at the CMSIS fft library (hints in this post).
 
Last edited:
kpc - good job finding the arduinoFFT.h - I spent a few seconds looking to no avail . . . assume this is a 256_FFT thus the reduced sample rate? All unclear with the snippets provided.

I was going to add as you did that - Teensy specific tools and libs will offer better perf . . . but your saying it has more impact, especially since you looked at the other FFT code.

Like me SMaric wants a different sample Freq 10kHz not 40K+ - which means using ADC to collect the samples. I never found a Queue sample - and you also suggested manually calling the FFT code rather than having the continuous Audio lib process run.

Between having those options and seeing my own Analog mic samples tend to noise - I was off on other things for a bit.
 
Both, thanks for prompt replies

FYI - originally tackled this with the Arduino Mega using the FHT library ( http://wiki.openmusiclabs.com/wiki/ArduinoFHT[/ )
got it working --- BUT 256 sample limit resulted in frequency discrimination problems​
So having looked around for wider FFTs - had to account for bigger SRAM needs - so moved to Teensy3.1
Am looking at min 1024 (preferably 2048) sample FFT - so arduinoFFT looked like a candidate

Can't seem to get the Arduino IDE serial monitor to recognise the Teensy3.1 after downloading code --- so can't user serial.print to dump data


Meanwhile
in adc_isr I use bFFTDataAvailable & sampleCntr combination to control writing to vReal/vImag
(have previously been using RingBuffer --- but thought I'd simplify code while trying to figure out what the problem is)
Main loop clears bFFTDataAvailable after completing the FFT work
adc_isr just reads adc values (& throws them away) while bFFTDataAvailable is true
 
Maybe add this to your setup and see if you end up with reliable USB output - the qBlink is a free bonus showing a heartbeat where ever you drop it. On Windows many samples don't run right for me until I add some version this time limited while() - some have an infinite while which I find problematic as well.

Did you just start with the ADC Example for IntervalTimer?

What Freq resolution to you need? Audio 1024 at 44KHz is about 50Hz - I saw you dropped sample rate to 10KHz so you'd get the same with 256 samples and a quarter of that on a 1024 - as long as you don't need freq over 5KHz.

Code:
#define qBlink() (digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ))
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  qBlink();
[B]  while (!Serial && (millis() <= 6000));[/B]
  qBlink();
}
 
Maybe add this to your setup and see if you end up with reliable USB output - the qBlink is a free bonus showing a heartbeat where ever you drop it. On Windows many samples don't run right for me until I add some version this time limited while() - some have an infinite while which I find problematic as well.

Did you just start with the ADC Example for IntervalTimer?

What Freq resolution to you need? Audio 1024 at 44KHz is about 50Hz - I saw you dropped sample rate to 10KHz so you'd get the same with 256 samples and a quarter of that on a 1024 - as long as you don't need freq over 5KHz.

Code:
#define qBlink() (digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ))
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  qBlink();
[B]  while (!Serial && (millis() <= 6000));[/B]
  qBlink();
}


ADC usage is based on IntervalTimer
timer0_isr CAN blink LED at 10KHz --- no problem

timer0_isr triggers adc conversion

adc0_isr CAN blink LED at 10KHz --- no problem

adc0_isr writes <sampleCntr> samples to vReal[] & then sets bFFTDataAvailable flag
subsequent samples ignored until bFFTDataAvailable cleared​


main loop checks for bFFTDataAvailable - when set

it runs the 3 FFT routines
then update the controls

5Khz range is OK - hence 10KHz sampling rate
am trying to get down to a few Hz frequency discrimination - hence the 1024 (preferably 2048) sample FFT
 
Again, I would not even try to perform a 2048 point fft using the arduinoFFT library.
When you invest a little time using the 1024 point CMSIS FFT, I can help adding a radix-2 butterfly, to combine two 1024 point FFT's, to a single 2048 point FFT
 
Again, I would not even try to perform a 2048 point fft using the arduinoFFT library.
When you invest a little time using the 1024 point CMSIS FFT, I can help adding a radix-2 butterfly, to combine two 1024 point FFT's, to a single 2048 point FFT


More than happy to be convinced of proven solutions - as usual 'just want to get it working' :D

If you can provide a few instructions/links
I'll rebuild code this evening & see what I get
 
Did you start getting Serial.print to show? Assuming you have a scope/counter to see the 10KHz blinks - need to set up a counter device - looking at $20 xProtoLab.

I didn't see the bin count in your snippet so wasn't clear - I'm new to the FFT stuff here just wanting to make sure my understanding matched yours. kpc is the ticket for answers. I'm slacking off, but looking to get similar ADC driven interval samples - I should be good with 10KHz and 256_FFT for my use. I want it to be low impact background task while I focus on 9DOF movement so what works for you getting samples and feeding the FFT will work for me on a smaller scale.

SMaric - as kpc noted before you should keep sampling contiguous using a second buffer while the first 1024 calculates - as it seems that second 1024 sample set can lead directly to a shorter path 2048 FFT. And at 10KHz it will be the sampling that takes the most time, though the calculation burns the most work cycles, the kpc work will spread that work over the sample period.
 
Although defragster has roughly the idea of what I did. It is not exactly like that.
The two FFT's process the odd and the even samples respectively. You cannot start the FFT after half the samples came in. For a 1024 point fft, you should just sample the 1024 like you did before. Do not split it in two. (The ping-pong buffer remark was when I still thought you kept on sampling). Also the remark about spreading the work over the sample period is only valid in combination for the Audio library. Since this is custom code, there is no need to spread the load, as you can choose your intervals equal to the fft size.
 
thanks for clarity kpc: I was of course referring to what I saw of your Audio work - it is good to know that won't be the same for manually calling the FFT code if I read correctly now.

I can help adding a radix-2 butterfly, to combine two 1024 point FFT's, to a single 2048 point FFT

Perhaps I wrongly assumed this radix-2 butterfly combination would be after each 1024 was calculated alone. Was it rather as I recall from 'somewhere' the calculation is done all at once where the two set of real values were merged 'adjacent' given that there no complex component?

Do you have a coded sample of this post showing which are calls using the FFT library code, versus the calculations that are done 'by the caller' around those calls - I think my only question is the window_function execution and I found the following:

arm_cfft_radix4_init_q15(&fft_inst, 256, 0, 1); // one time start up call
------ repeat for each sample group::
>caller code> Collect and place formatted data
(Then multiply every sample by a window function.) ? // apply_window_to_fft_buffer(void *buffer, const void *window) ??
arm_cfft_radix4_q15(&fft_inst, buffer); // perform the FFT
>caller code> the magnitude can be calculated ...
>caller code> Correlating a frequency ...

Let me know if this is the right summary and I can start with this list and ask you to comment on my sketch.
Given these calls are just using a *buffer [512 bytes for FFT_256] it is done without using AudioMemory()?
 
I do not have a ready made example

Declarations
Code:
        #define N 1024 // can be 64, 256 or 1024
        int16_t buffer[N * 2] __attribute__ ((aligned (4)));
        uint16_t output[N / 2];
        arm_cfft_radix4_instance_q15 fft_inst;
only perform this once:
Code:
        arm_cfft_radix4_init_q15(&fft_inst, N, 0, 1);
This is the main code:
Code:
        [Place the real samples in every even buffer index.]
        [Make every odd index 0.]
        // Multiply all samples by a window
        [you can indeed copy the apply_window_to_fft_buffer() from the Audio fft library.]
        // Perform the FFT
        arm_cfft_radix4_q15(&fft_inst, buffer);
        // Calculate the magnitude:
        for (int i=0; i < N / 2; i++) {
                uint32_t tmp = *((uint32_t *)buffer + i); // real & imag
                uint32_t magsq = multiply_16tx16t_add_16bx16b(tmp, tmp);
                output[i] = sqrt_uint32_approx(magsq);
        }
 
Maybe add this to your setup and see if you end up with reliable USB output - the qBlink is a free bonus showing a heartbeat where ever you drop it. On Windows many samples don't run right for me until I add some version this time limited while() - some have an infinite while which I find problematic as well.

Did you just start with the ADC Example for IntervalTimer?

What Freq resolution to you need? Audio 1024 at 44KHz is about 50Hz - I saw you dropped sample rate to 10KHz so you'd get the same with 256 samples and a quarter of that on a 1024 - as long as you don't need freq over 5KHz.

Code:
#define qBlink() (digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ))
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  qBlink();
[B]  while (!Serial && (millis() <= 6000));[/B]
  qBlink();
}


Just tried this code - when I try to open Arduino IDE Tools|Serial Monitor I get
Board at COM4 is not available

Not a big issue - but would have been useful to be able to prove I'm getting data -- just have to carry on with the guesswork
 
@kpc: again thanks. That should confirm what I need when I can get back to it. I thought I was missing more until I presented it in the post after finding the window code.

SMaric: Check the menu: Tools / Port for active ports. Indeed sometimes a port shows that is the right port and may not be responsive while running - other times you can see more ports that devices as one is orphaned and until reboot that one will need to be ignored. Try the 'TYQT tool' if you want. I use it often, especially with multiple active Teensy's.

I do a 'Ctrl+R' verify then push the right Teensy Button. On the UI tabs is a Monitor tab that will show the serial output from any indicated Teensy. If the Teensy isn't visible it should appear after the next Button upload. You can see multiple Teensies in one copy, or multiple copies. It drops connection on the button and typically returns quickly to show the USB text. If this works for you that is great - if not then something else isn't quite right.
 
@kpc: again thanks. That should confirm what I need when I can get back to it. I thought I was missing more until I presented it in the post after finding the window code.

SMaric: Check the menu: Tools / Port for active ports. Indeed sometimes a port shows that is the right port and may not be responsive while running - other times you can see more ports that devices as one is orphaned and until reboot that one will need to be ignored. Try the 'TYQT tool' if you want. I use it often, especially with multiple active Teensy's.

I do a 'Ctrl+R' verify then push the right Teensy Button. On the UI tabs is a Monitor tab that will show the serial output from any indicated Teensy. If the Teensy isn't visible it should appear after the next Button upload. You can see multiple Teensies in one copy, or multiple copies. It drops connection on the button and typically returns quickly to show the USB text. If this works for you that is great - if not then something else isn't quite right.


TyQT doesn't register my Teensy3.1
Windows 'bleeps' on Teensy USB plug-in/removal
Arduino IDE can download to Teensy

??Any ideas??
 
Make sure the IDE Monitor is closed. Not showing after Button on Teensy? Fresh system restart? Different cable?
 
Make sure the IDE Monitor is closed. Not showing after Button on Teensy? Fresh system restart? Different cable?

Sequence
Booted machine
Windows login
plugged-in Teensy3.1 usb
Read your msg
Downloaded/installed TyQT
Launched TyQT
Information tab -- all fields greyed out

Tried another USB cable

No joy
?????


UPDATE
Tried a different Teensy3.1 -- this one is detected by TyQT

Difference between them is that 1st one has the pcb trace cut -- so that it gets power from external 5v dc mains adapter (not powered from USB)

??Does that make sense -- or do I just have one 'faulty' unit & one good one
 
Last edited:
@SMaric: I was just curious about the performance of the arduinoFFT library. I had to modify it a little, to get it to work with more than 128 samples. They use uint8_t's for some counters, modified them to uint16_t and it worked.
If I then check a 1024 point FFT, including windowing and calculating the magnitude, the total time takes 140 ms. Not at all bad for an implementation using doubles. The same using the CMSIS q15 FFT takes about 1.7 ms. So the arduinoFFT library is only a factor of 80 slower. I would have thought it to be much worse. As ever, the Teensy keeps on amazing me.
 
Last edited:
Again still being curious, I replaced all doubles by floats and alls cos by cosf. This was I think as the library intended, since IIRC on arduino double == float.
This dropped the total time to 85 ms, so only a factor of 50 slower.
In other words, performing calculations with floats is approximately twice a fast than with doubles.
 
@SMaric: I was just curious about the performance of the arduinoFFT library. I had to modify it a little, to get it to work with more than 128 samples. They use uint8_t's for some counters, modified them to uint16_t and it worked.
If I then check a 1024 point FFT, including windowing and calculating the magnitude, the total time takes 140 ms. Not at all bad for an implementation using doubles. The same using the CMSIS q15 FFT takes about 1.7 ms. So the arduinoFFT library is only a factor of 80 slower. I would have thought it to be much worse. As ever, the Teensy keeps on amazing me.


Yeah I figured out the uint8_t flaw - thanks for confirming
Without being able to see result data or it operating as expected - I wasn't sure If that was the only problem
So am still hunting down why my outputs don't seem to reflect changing input signals

For test purposes - I have just hooked up a few LEDs which I'm driving based on various frequency energies - but what I'm seeing just looks like arbitrary 'noise' output levels
So I'm either not feeding Reals in properly or I'm not reading the adc (even though I think I've proven that by some more basic code)

Here's my adc_isr --- can you see if there's anything obviously wrong

Code:
// when the ADC measurement finishes, this will be called 
void adc0_isr() {
  int adcVal = adc->adc0->readSingle();
  if(!bFFTDataAvailable){
    //  mask in only the 12 bits
    adcVal = adcVal & 0x0fff;
    //  we have a dc biased i/p signal
    //  So we're getting 0x0000 to 0x07ff as -ve signal & 0x0800 to 0x0fff as +ve signal
    //  need to convert to a 16Bit signed number
    if(adcVal & 0x0800 == 0x0800){
      adcVal = adcVal & 0x07ff;
    }else{
      //  Now 2's complement it
      adcVal = ~adcVal;
      adcVal++;
    }

    vReal[sampleCntr] = (double)(adcVal);
    vImag[sampleCntr++] = (double)0;  //Reset the imaginary value

    if(sampleCntr >= N_samples){
      bFFTDataAvailable = true;
      sampleCntr = 0;
    }
  }
  
  // restore ADC config if it was in use before being interrupted by the analog timer 
  if (adc->adc0->adcWasInUse){
    // restore ADC config, and restart conversion 
    ADC0_CFG1 = adc->adc0->adc_config.savedCFG1;
    ADC0_CFG2 = adc->adc0->adc_config.savedCFG2;
    ADC0_SC2 = adc->adc0->adc_config.savedSC2;
    ADC0_SC3 = adc->adc0->adc_config.savedSC3;
    ADC0_SC1A = adc->adc0->adc_config.savedSC1A;
  }
  ADC0_RA; // clear interrupt
}
 
Have you already checked with just inserting a sine wave instead of samples?
Code:
    vReal[sampleCntr] = sin(3. * sampleCntr / 1024. * 2. * M_PI);
The peak should be in bin 3.
 
Status
Not open for further replies.
Back
Top