Arduino FFT results of "beautiful" signal seem inconsistent

I have a BEAUTIFUL doppler radar signal here from a 24.050-24.250 GHZ sensor measuring a very small object at 143 fps speed (speed from another 10 GHZ radar) in a measuring area of about 300mm-500mm in front of the sensor(s). The signal was limited to around 3V with a zener diode.

I took several measurements (at this speed and object size and distance from the sensors) and there were about 8-20 wave peaks on the average from above the trigger threshold level (2.58V) until the end of the trigger threshold level box where the peaks start falling off.

Looking at the graph, one can discard the first 3 peaks and the last 2-3 peaks above the trigger threshold which leaves a decent area (orange box) for sampling of the peaks for frequency/period. So in this case we have about 17 "good" peaks inside the orange box to be measured. In this case the orange box (measuring sample area) is 5 x 500 us = 2500 us in length.

I know that an FFT would be the best way to measure, but the one FFT program I used with Arduino gave me inconsistent results. Not sure how to set the sample size (must be in power of 2 increment from 2 to 256). And the maximum sampling frequency is 10kHz (hardware limitations).

I set sample sizes to 4 and 16 and 128 at 10Khz frequency, but get inconsistent results. I suspect logarithmic results, but even those do not add up. Sometime I get within 2-3% but at times it's off by 30% with no apparent correlation.

What I know:

  • with this 24 GHZ radar (no angles) the speed = frequency / 44 to arrive to km/h
  • in this case the o-scope measured 6613 Hz with a 151.2 us period, but that was for the entire area above the 2.58V trigger threshold. But it also had some much more varying readings as noticeable in the first 2-3 peaks after the trigger and the last 2-3 peaks that are falling off to below the trigger threshold. I excluded these from the orange box.

How do I set up this FFT, or another FFT, or similar method, that would determine the dominant frequency from data inside the orange box? The orange box sample size will always seems to be between the 8-20 number of peaks range. I measured frequency and periods from "positive rising edge to positive rising edge".

I tried with an Adafruit nRF52840 Express (16 Mhz clock) and an Adafruit 32u4 Bluefruit (8 Mhz clock).

I am using this Arduino FFT library: GitHub - kosme/arduinoFFT: Fast Fourier Transform for Arduino

Some broad info about this FFT itself: What Is FFT and How Can You Implement It on an Arduino? – Norwegian Creations

Thank you.

#include "arduinoFFT.h"
 
#define SAMPLES 4         //Must be a power of 2     128, 256?
#define SAMPLING_FREQUENCY 10000 //Hz, must be less than 10000 due to ADC
 
arduinoFFT FFT = arduinoFFT();

 
unsigned int sampling_period_us;
unsigned long microseconds;
 
double vReal[SAMPLES];
double vImag[SAMPLES];
 
void setup() {
    Serial.begin(115200);

    sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
}
 
void loop() {

 
  if (analogRead(A0) >600) {
   
    /*SAMPLING*/
    for(int i=0; i<SAMPLES; i++)
    {
        microseconds = micros();    //Overflows after around 70 minutes!
     
        vReal[i] = analogRead(A0);
        vImag[i] = 0;
     
        while(micros() < (microseconds + sampling_period_us)){
        }
    }
 
    /*FFT*/
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
    double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);


    /*PRINT RESULTS*/
    Serial.println(peak);     //Print out what frequency is the most dominant.
 
  //  for(int i=0; i<(SAMPLES/2); i++)
   // {
        /*View all these three lines in serial terminal to see which frequencies has which amplitudes*/
         
        //Serial.print((i * 1.0 * SAMPLING_FREQUENCY) / SAMPLES, 1);
        //Serial.print(" ");
      // Serial.println(vReal[i], 1);    //View only this line in serial plotter to visualize the bins
  //  }


 }
 
    //delay(1000);  //Repeat the process every second OR:
   // while(1);       //Run code once
}

What do you really want to measure? If you are interested in only the dominant frequency, that would be 1/period as read off the scope face.

The frequency content of that signal is very high, because it is a rectangular wave (not a sine wave), and with the FFT on Arduino, you run into all kinds of additional problems due to the short sample length, low sample frequency, and signal aliasing, which can make the spectrum almost uninterpretable.

To use the FFT with Arduino, you almost always need a very good low pass filter before the signal even gets to the ADC, to ensure that frequencies above (ADC sample frequency)/2 are not present in the ADC input. For higher, timer based sample frequencies, try the OpenMusicLabs FFT or FHT library.

For more information look up "sampling theorem" and "signal aliasing".

Thank you for the advice. You hit on something that I should have included in my comment, yes, the signals are square, which is from the OP AMP circuit (and the zener diode as the voltage limiter). Would I be better of with a sinus wave? I need to check into a sinus wave amplifier OP AMP circuit.

I am hoping to measure speeds of 100 fps -4000 fps. For a 24 GHZ doppler signal envelope that is about 208 us period (4.8KHz) to 5.2 us period (192 KHz). But the most-used envelope would be 208 us (4.8Khz) to 20 us (48 Khz)

Now that I did some more calculations: with such high frequencies there is an Arduino FFT program limitation from a hardware of 10 Khz sampling. The attached FFT code mentions it too...because of the ADC's capabilities.

Running the ADC in free-running mode would get 100 KHz but still way too short of the 192 Khz at the highest range of the envelope. And I believe I need at least 2 x times the frequency I want to measure per Nyquist (as you mentioned with the ADC). I have some really fast OP AMPs (10MHz -200 MHZ) or maybe I can use the TDC7200 TOS counter?

The sampled incoming signal is not running but in a one shot-mode, every few seconds or minutes apart. The measurement envelope is about 200-300 mm wide from trigger-start to trigger-stop, so the signal's time envelope is about 10 us to 246 us long.

I will look into the links you mentioned to get a better grip on the challenges.

Since you have a square wave the easiest way to measure the frequency is to measure the cycle time. Here is an example of how to measure cycle times in 16ths of a microsecond:

// Measures the HIGH width, LOW width, frequency, and duty-cycle of a pulse train
// on Arduino UNO Pin 8 (ICP1 pin).  


// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().


void setup()
{
  Serial.begin(115200);
  while (!Serial);


  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz


  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;


  TIFR1 |= _BV(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt


  TCCR1B = _BV(CS10) | // start Timer 1, no prescaler
           _BV(ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)


  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  interrupts ();
}


volatile uint32_t PulseHighTime = 0;
volatile uint32_t PulseLowTime = 0;
volatile uint16_t Overflows = 0;


ISR(TIMER1_OVF_vect)
{
  Overflows++;
}


ISR(TIMER1_CAPT_vect)
{
  static uint32_t firstRisingEdgeTime = 0;
  static uint32_t fallingEdgeTime = 0;
  static uint32_t secondRisingEdgeTime = 0;


  uint16_t overflows = Overflows;


  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;


  if (PulseLowTime == 0)
  {
    if (TCCR1B & _BV(ICES1))
    {
      // Interrupted on Rising Edge
      if (firstRisingEdgeTime)  // Already have the first rising edge...
      {
        // ... so this is the second rising edge, ending the low part
        // of the cycle.
        secondRisingEdgeTime = overflows; // Upper 16 bits
        secondRisingEdgeTime = (secondRisingEdgeTime << 16) | ICR1;
        PulseLowTime = secondRisingEdgeTime - fallingEdgeTime;
        firstRisingEdgeTime = 0;
      }
      else
      {
        firstRisingEdgeTime = overflows; // Upper 16 bits
        firstRisingEdgeTime = (firstRisingEdgeTime << 16) | ICR1;
        TCCR1B &= ~_BV(ICES1); // Switch to Falling Edge
      }
    }
    else
    {
      // Interrupted on Falling Edge
      fallingEdgeTime = overflows; // Upper 16 bits
      fallingEdgeTime = (fallingEdgeTime << 16) | ICR1;
      TCCR1B |= _BV(ICES1); // Switch to Rising Edge
      PulseHighTime = fallingEdgeTime - firstRisingEdgeTime;
    }
  }
}


void loop()
{
  noInterrupts();
  uint32_t pulseHighTime = PulseHighTime;
  uint32_t pulseLowTime = PulseLowTime;
  interrupts();


  // If a sample has been measured
  if (pulseLowTime)
  {
    // Display the pulse length in microseconds
    Serial.print("High time (microseconds): ");
    Serial.println(pulseHighTime / 16.0, 2);
    Serial.print("Low time (microseconds): ");
    Serial.println(pulseLowTime / 16.0, 2);


    uint32_t cycleTime = pulseHighTime + pulseLowTime;
    Serial.print("Cycle time (microseconds): ");
    Serial.println(cycleTime / 16.0, 2);


    float dutyCycle = pulseHighTime / (float)cycleTime;
    Serial.print("Duty cycle (%): ");
    Serial.println(dutyCycle * 100.0, 2);


    float frequency = (float)F_CPU / cycleTime;
    Serial.print("Frequency (Hz): ");
    Serial.println(frequency, 2);
    Serial.println();


    delay(1000);  // Slow down output


    // Request another sample
    noInterrupts();
    PulseLowTime = 0;
    interrupts();
  }
}

Thank you @johnwasser Since it's a one-shot with a 8-17 average data points, I think you are right about a simple period between rising edges measurement vs a continuous or triggered FFT string reading. I actually already done that before but I thought the FFT would lead to "magic" results. That's because I knew/know little about FFT. I will compare your code you kindly added to the one I already used previously and see how it pans out.

I have an AVR code with 125ns resolution and an nRF52 code with 62.5ns resolution to count input capture pulses.

The main task that I may face difficulty with is the removal of the first and last 2-3 readings that usually contain the largest variations from the norm, in addition to any zero values or duplicates within the usable string itself. But this part actually may be fun to try...will see how I fare.

Since the 'good' part of your signal is about 3 milliseconds you should get good results by waiting for the first rising edge and then sample the cycle time about 1 millisecond later.

Good idea.. At least until I am able to identify/isolate the correct data pattern for the actual speed. I spent hours yesterday using a reference doppler radar, an oscope, and several boards of 32u4 AVR and nRF52 Arduino input-counters (62.5ns and 125ns tick) and free-running AVR ADC (100 Khz) programs I already had working before (AVRs mostly Nick Gammon) but managed only marginal results.

I did a quick and dirty manual measurement of the o-scope reading by increasing the horizontal pane and counting RISING EDGE TO RISING EDGE pulse widths by hand (like the snapshot I posted), but need a closer look here. I am pretty sure that the oscope Freq and Period results represent the entire signal above the trigger and is averaged. So it's not a true representative of the segment of the snapshot that corresponds to the actual speed, but with manually narrowing/filtering the data I am still not getting a pattern.

I also did a quick and dry estimation of the length of space the radar is measuring and it came out to about 80-120mm with my current test conditions. So with slower (below 100 fps) velocities I may see some larger varying range of RISING EDGE to RISING EDGE difference in reading, but with a 530 fps speed I should get close values. In all cases it should be proportional and pattern-like.

So far I am doing the data selection/isolation by hand and no matter how I massage it I cannot establish a matching pattern that is within even 10% range of consistently. More like 5%, and 10% and then 50% off.... Does not even look like a pattern, even if there is a logarithmic component. Not sure if it's a theory or actual measurement issue. I verified that the reference doppler radar is within +/-1%.

I need to take a 30,000 feet look and see what am I doing wrong. Having fairly ideal testing conditions, I must be missing something fundamental. So I am reading up more on doppler signals and processing.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.