Measuring frequency

Hello

If we create two arrays, one of them is data that is generated from an oscillating object, and the other is the time elapsed. Both of a certain buffer size. I want to find the natural frequency of the wave. Can someone please help me figure out how to output the average frequency? I've come to the conclusion that I will have to use Fast Fourier Transform (FFT). However, even after reading plenty about it, I couldn't figure out how implement it.

I appreciate any help.
Thank you!

Maybe this helps? arduinoFFT library

Thank you for your reply.
All I know about Fourier is that it represents the wave as a combination of sines and cosines. I read through a lot of explanations, but not sure if I used it correctly on my data. I got the fft code from this website: link. My data is basically acceleration from an accelerometer that is oscillating due to vibration. I intend to output the frequency of the vibration. The results I’m getting in the serial monitor make no sense. Here is my code so far:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include "arduinoFFT.h"

#define SAMPLES 128             //Must be a power of 2
#define SAMPLING_FREQUENCY 1000 //Hz, must be less than 10000 due to ADC

Adafruit_BNO055 bno = Adafruit_BNO055();
arduinoFFT FFT = arduinoFFT();

unsigned int sampling_period_us;
unsigned long microseconds;

double vReal[SAMPLES];
double vImag[SAMPLES];

void setup() {
  Serial.begin(115200);
  /* Initialise the sensor */
  if (!bno.begin())
  {
    Serial.print("Wiring error");
    while (1);
  }
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));
}

void loop() {

  imu::Vector<3> euler = bno.getVector(Adafruit_BNO055::VECTOR_LINEARACCEL);  //measuring linear acceleration with respect to time

  /*SAMPLING*/
  for (int i = 0; i < SAMPLES; i++)
  {
    microseconds = micros();    //Overflows after around 70 minutes!

    vReal[i] = euler.y();   //acceleration data generated
    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.

  delay(100);  //Repeat the process every second OR:

}

I would appreciate any help.
Thank you!

  /*SAMPLING*/
  for (int i = 0; i < SAMPLES; i++)
  {
    microseconds = micros();    //Overflows after around 70 minutes!

    vReal[i] = euler.y();   //acceleration data generated
    vImag[i] = 0;

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

What is this supposed to do?

You don’t know how time calculations on Arduino work and the thing you do try makes that clear.

Unsigned rollover is not a problem when you get difference between 2 times by subtracting start time from end time. It ALWAYS WORKS up to the longest interval the time variables can hold minus 1.

while ( micros() - period_start_time < sampling_period ); // micros is granular to 4usecs

Arduino float and double are the same 32-bit IEEE 6/7 place floating point variables running as slow as could be expected on an 8 bit processor with no FPU.

Whatever frequencies you expect to detect, they need to be much slower than how you intend to detect them.

You can time > 60 milliseconds as 16 bit unsigned microseconds just more than twice as fast as using 32 bit unsigned longs. As long as you use unsigned integers the word length only limits the maximum interval.

You should be able to collect and store 8 analog reads per ms.

Thank you for your reply.
Here’s what I understood, I’m supposed to change the "long"s to "int"s, change the micros from 32 bits to 16 bits, and finally have the while loop be a duration rather than an instant. However the serial monitor is still outputting results that are constant. Here’s the updated code:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include "arduinoFFT.h"

#define SAMPLES 128             //Must be a power of 2
#define SAMPLING_FREQUENCY 1000 //Hz, must be less than 10000 due to ADC

Adafruit_BNO055 bno = Adafruit_BNO055();
arduinoFFT FFT = arduinoFFT();

unsigned int sampling_period_us;
unsigned int microseconds;

double vReal[SAMPLES];
double vImag[SAMPLES];

void setup() {
  Serial.begin(115200);
  /* Initialise the sensor */
  if (!bno.begin())
  {
    Serial.print("Wiring error");
    while (1);
  }
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));
}

void loop() {

  imu::Vector<3> euler = bno.getVector(Adafruit_BNO055::VECTOR_LINEARACCEL);  // 

  /*SAMPLING*/
  for (int i = 0; i < SAMPLES; i++)
  {
    uint16_t ts1 = micros();   //Overflows after around 70 minutes!

    vReal[i] = euler.y();   //acceleration data generated
    vImag[i] = 0;

    while ( micros() - ts1 < 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.

  delay(100);  //Repeat the process every second OR:
 
}

Thank you!

uint16_t ts1 = micros();   //Overflows after around 70 minutes!

Make that 65 ms. This as an unsigned int can not store values >65535 before overflowing back to 0. This must be an unsigned long aka uint32_t. Same type as micros() returns.

fsalloum:
Thank you for your reply.
Here’s what I understood, I’m supposed to change the "long"s to "int"s, change the micros from 32 bits to 16 bits, and finally have the while loop be a duration rather than an instant. However the serial monitor is still outputting results that are constant. Here’s the updated code:

unsigned int, unsigned long, must be unsigned is the point.

  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));

I get 1000000 / 1000 = 1000.

  /*SAMPLING*/
  for (int i = 0; i < SAMPLES; i++)
  {
    uint16_t ts1 = micros();   //Overflows after around 70 minutes!

    vReal[i] = euler.y();   //acceleration data generated
    vImag[i] = 0;

    while ( micros() - ts1 < sampling_period_us );
  }

ts1 gets the lower 16 bits of micros() however the while () should be

while ( word( micros()) - ts1 < sampling_period_us ); // word( value ) makes value fit in an unsigned int

wvmarle:

uint16_t ts1 = micros();   //Overflows after around 70 minutes!

Make that 65 ms. This as an unsigned int can not store values >65535 before overflowing back to 0. This must be an unsigned long aka uint32_t. Same type as micros() returns.

65 ms is far longer than the 1 ms events he seeks to generate.

I know I've posted code that uses 16-bit unsigned timing many times over the past 8 years, did you miss them all?

No, you don't have to use all 32 bits of millis() or micros() returns to work with time in Arduino but you do have to keep all the terms the same size and type.

Arduino gives us functions to change 32 bit values to 16 bits by cutting the high 16 bits off. That is NOT a problem when calculating time differences within 16 bit range. We tell beginners to stick with unsigned longs just to not have to make explanations that overload people still trying to deal with arrays and if-then logic.