Using ArduinoFFT with an accelerometer to detect vibration Freq

Hi Folks,

This is my first post on the forum, i have read the "how to use this forum", but didn't found anything about introducing new user on special topic, so i just hope i don't violate anything... If so, let me know.

I'm working on a project with an ESP32 and an ADXL337, which purpose is to detect vibration frequency and amplitude on mechanical devices.

From what i have seen, max sampling rate is 1600Hz on X&Y and 550Hz on Z for the ADXL337, which let me work in the frequency range 0->800Hz on X&Y and 0-275Hz for Z. That's fine for what i'm looking at.

I have use ArduinoFFT (GitHub - kosme/arduinoFFT: Fast Fourier Transform for Arduino) to compute Frequency/Amplitude from the Time/amplitude acquisition.

Actually i use only X axis for testing. I'm sampling at 512Hz and i'm using 1024 samples (which means that my acquisition lasts 2s, but no problem, because i don't need real time).

Shaking the ADXL337 i can actually achieve easily 5/6 hz Freq. The result of the FFT is nice for the frequency, which gives me a huge peak at 5.5 hz. By the way, i don't understand the amplitude.

it is said that the amplitude, should be the same that in the time/amplitude domain. As i'm using an ESP32, the range of the ADC is 0-4095. So amplitude in the Freq/Amplitude domain should not be greater than my ADC range. But at the peak Frequency (5.5Hz) i have an amplitude of 613881 !!!

Do i miss something, is there some normalization to do on the amplitude ?

Here is the code, largely inspired from examples :

//----
//inspiré de https://github.com/G6EJD/ESP32-8266-Audio-Spectrum-Display
//        de https://github.com/kosme/arduinoFFT/blob/master/Examples/FFT_01/FFT_01.ino (ArduinoFFT)
//----

#include <arduinoFFT.h>

//max Sampling rate of ADXL337 is 1600Hz on X&Y and 550Hz on Z

arduinoFFT FFT = arduinoFFT();      
#define SAMPLES 1024              //Must be a power of 2
#define SAMPLING_FREQUENCY 512   //Hz. Determines maximum frequency that can be analysed by the FFT.
                           

unsigned int sampling_period_us;
unsigned long microseconds;
double vRealADC[SAMPLES];
double vReal[SAMPLES];
double vImag[SAMPLES];


// ADXL337 is connected to ESP32 Analog pins :
const int ADXL_ZPin = 32; //Z
const int ADXL_YPin = 35; //Y
const int ADXL_XPin = 34; //X

// variable for storing the analog value for raw acceleration on each axis
int rawZ = 0;
int rawY = 0;
int rawX = 0;
// Scaled values for each axis
float scaledX, scaledY, scaledZ;

void setup() {
  Serial.begin(256000);
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));
  pinMode(ADXL_ZPin, INPUT);
  pinMode(ADXL_YPin, INPUT);
  pinMode(ADXL_XPin, INPUT);
  delay(1000);
}

void loop() {
  rawX=0;
  
  for (int i = 0; i < SAMPLES; i++)
    {
      microseconds = micros();
      rawX = analogRead(ADXL_XPin);
      vRealADC[i] = rawX;
      vReal[i] = rawX;
      vImag[i] = 0;
      //wait for next sampling time according to freq
      while (micros() < (microseconds + sampling_period_us))  { }
    }

 
  FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

  Serial.println("+++ Freq / Amplitude +++++");
  for(int i=0; i<(SAMPLES/2); i++)  {
      Serial.print((i * 1.0 * SAMPLING_FREQUENCY) / SAMPLES, 1);
      Serial.print("\t");
      Serial.println(vReal[i]);    
  }

  Serial.println("*** ADC val ***");

  for(int i=0; i<(SAMPLES); i++)  {
      Serial.println(vRealADC[i]);    
  }
}

Many thx for your help

Cédric
:wink:

1 Like

It's me again :slight_smile:

Here is some graphs which may help you to understand better what i'm talking about :wink:

Thx again

Do i miss something, is there some normalization to do on the amplitude ?

Very likely. It depends on how the FFT operation is defined in the code. Just doing the straight summation forward and reverse scales up the transform by a factor of #samples.

It is extremely important that you test other people's programs with examples, where you know exactly what to expect, before applying it to the real world. Make sure that you understand the results before blindly applying it.

Make up some fake data and transform it, as shown in the example below for the OpenMusicLabs FFT package.

/*
  fft_test_sine
  example sketch for testing the fft library.
  This generates a simple sine wave data set consisting
  of two frequences f1 and f2, transforms it, calculates
  and prints the amplitude of the transform.
*/

// do #defines BEFORE #includes
#define LIN_OUT 1 // use the lin output function
#define FFT_N 32 // set to 32 point fft

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

void setup() {
  Serial.begin(9600); // output on the serial port
}

void loop() {
  int i, k;
  float f1 = 2.0, f2 = 5.0; //the two input frequencies (bin values)
  for (i = 0 ; i < FFT_N ; i++) { // create 32 samples
    // amplitudes are 1000 for f1 and 500 for f2
    k = 1000 * sin(2 * PI * f1 * i / FFT_N) + 500.*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_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]); //add in "negative frequency contribution"
  }
  Serial.println("Done");
  while (1); //wait here
}

This expression is the wrong way around and will fail rather too often. See this tutorial.

    while (micros() < (microseconds + sampling_period_us))  { }

Finally, the peak clipping of the input waveform results in lots of extra peaks in the transform. Set the accelerometer to use a higher acceleration scale to avoid that distortion.

The ArduinoFFT library comes with examples. The FFT_01 example does exactly what I suggest above.

Run it and study the output carefully. Then check the FFT source code and note that for the inverse transform, it scales by 1/N (so that you get the same output as originally input to the forward transform).

Hello,

Thanks for your replies and for your help. Don't worry i'm used to test all the examples given with a library before doing my own mess :slight_smile:

Before coding my own test with the accelerometer, i have done some test with the examples given with ArduinoFFT library, and i have obtained the same findings than with my test.

I have reloaded FFT01 to an ESP32 and here is the result graphed from the serial output :


image uploader

As you can see, there is also a difference between the amplitude like in my tests. So, let's say it's how ArduinoFFT works.

from another forum, i've seen that to obtain the same amplitude that you have inputed in the FFT, you have to divide the magnitude from the FFT by the number of samples, then multiply by 2 (to take care of both side of the curve). In FFT01, If i do this math, at peak frequency, i have an amplitude of 23 (instead of 738). Which is not near the amplitude of the program (100), or even the half of it.

I'm not an math expert, i'm just trying to find a way to have an amplitude which is barely the same that the one which is inputed in the FFT. i don't have problem with the Frequency, which seems very good to me, and even the harmonics in my case (at 11hz, and following which is normal).

i don't understand about the "inverse transform" you're talking about. there is some Time & Frequency formatting to output nicely data in serial monitor in FFT01, but nothing about amplitude.

Also, thx for your tutorial on delaying, i will study it.

About accelerometer, ADXL337 is working at max -3/3G, it's not a MPU6050 which is scalable . But for virbation, it should be OK, because i will probably never reach 3G.

Thx

Don't worry i'm used to test all the examples given with a library before doing my own mess

Obviously, you did not run that example, or you would have noticed the scaling issue.

There are two problems with your approach:

  1. The amplitude won't match the test FFT input unless the input frequency falls exactly on a frequency bin (which means that you have an exact number of cycles over one sampling period). Otherwise the peak is spread out over several bins. Windowing smears as well.

  2. There IS a scale or normalization factor which you did not take into account. In your particular case, it is 1/sqrt(number of samples). For 64 points, that is 1/8, which approximately accounts for the plot shown above.

  3. All sensors need to be calibrated, in order to interpret the results quantitatively, so the actual scaling factor in the FFT is irrelevant.

Hello,

Obviously, you did not run that example, or you would have noticed the scaling issue.

Yes i did, and yes i have noticed this scaling 'issue' on the examples given with the library. I thought it was more relevant to talk about my concrete case than to use an example.

And besides, I don't understand why I will have to justify myself, as if I were a liar :D. I greatly appreciate your wise advice, but I don't understand this student/teacher approach. It may be your style for newcomers, but I must admit it's a little strange :wink:

) The amplitude won't match the test FFT input unless the input frequency falls exactly on a frequency bin (which means that you have an exact number of cycles over one sampling period). Otherwise the peak is spread out over several bins. Windowing smears as well.

That's an interesting remark. I took the opportunity to review the theory of the Fourier transforms, and indeed, my memories were a little bit rusty. It's been 30 years since I've worked with FFTs and a little refreshment doesn't hurt.

I studied other projects, such as a spectrum analyzer, where I was able to verify that the different bins were aggregated to create the frequency bands.
This means that to get closer to the real amplitude at the peak frequency, it is necessary to take into account the adjacent frequency ranges. I will take it into account.

  1. There IS a scale or normalization factor which you did not take into account. In your particular case, it is 1/sqrt(number of samples). For 64 points, that is 1/8, which approximately accounts for the plot shown above.

this is also a nice remark. i don't know where you have find this, but it's true that in FFT01 example case, it seems to work. by the way, it doesn't in my real case.

in my last post, i was talking about normalization too. here is my source :

https://www.mathworks.com/help/matlab/ref/fft.html

Fs = 1000;            % Sampling frequency                    
T = 1/Fs;             % Sampling period       
L = 1500;             % Length of signal
t = (0:L-1)*T;        % Time vector
S = 0.7*sin(2*pi*50*t) + sin(2*pi*120*t);
X = S + 2*randn(size(t));
plot(1000*t(1:50),X(1:50))
title('Signal Corrupted with Zero-Mean Random Noise')
xlabel('t (milliseconds)')
ylabel('X(t)')
Y = fft(X);
P2 = abs(Y/L);
P1 = P2(1:L/2+1);
P1(2:end-1) = 2*P1(2:end-1);
f = Fs*(0:(L/2))/L;
plot(f,P1) 
title('Single-Sided Amplitude Spectrum of X(t)')
xlabel('f (Hz)')
ylabel('|P1(f)|')

Link is included, it's Matlab code. Pretty easy to understand. As you can see, Normalization is not the same than the one you talk about. here magnitude is divided by sample numbers and multiplicated by 2.

I'm not good enough in mathematics to understand the origin of all this. I was just looking here for someone who could have asked the same question as me when using this library. There's too much speculation for the moment.

So I contacted the author of the library, I think he can tell us more. I'll come back here to post his conclusions.

Thank you again

CU guys

i don't know where you have find this

I looked at the code, ran the example and examined the output. The scale factor could hardly be more obvious.

 // Scaling for reverse transform
 if (dir != FFT_FORWARD) {
 for (uint16_t i = 0; i < samples; i++) {
 vReal[i] /= samples;
 vImag[i] /= samples;
 }

Calibration against a known standard solves the scaling issue completely.

r5t2:
Shaking the ADXL337 i can actually achieve easily 5/6 hz Freq. The result of the FFT is nice for the frequency, which gives me a huge peak at 5.5 hz. By the way, i don't understand the amplitude.

it is said that the amplitude, should be the same that in the time/amplitude domain. As i'm using an ESP32, the range of the ADC is 0-4095. So amplitude in the Freq/Amplitude domain should not be greater than my ADC range. But at the peak Frequency (5.5Hz) i have an amplitude of 613881 !!!

Do i miss something, is there some normalization to do on the amplitude ?

Yes, there are several ways to deal with an FFT - either scale by 1/N, 1/(sqrt(N) or by 1.
And usually the inverse FFT is scaled to match the forwards scaling.

For spectral analysis you always want the scale by 1/N option, but if your FFT library doesn't give
you that option you'll have to scale the output yourself.

You'll also need to scale for negative frequencies and the FFT window you choose anyway.

All the grizzly details of doing this correctly are in this great paper: https://holometer.fnal.gov/GH_FFT.pdf