Vibration sensor (accelerometer) FFT

I am attempting to develop a vibration sensor as a project for college. I would like to have this portable vibration sensor which would read the analog signal from an accelerometer, perform an FFT and print out the first few frequencies on to an LCD. for example. If I were to attach the accel to a tuning fork, I would get the resonant frequencies of the tuning fork. So far I have been able to get the LCD working and acceleration in the time domain to print on the LCD.

While I am developing this project, I have been trying to use the serial library to get the FFT results.
I have also tried to use the library that was translated to arduino code from here: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1286718155

I am having trouble understanding how to isolate the first few resonant frequencies. So far, when I use the attached code, I get gibberish and random characters.

Is this because the serial print function can’t handle the arrays?
Thanks in advance!!

#include <fix_fft.h>

char im[128];
char data[128];
const byte xPin = 0;

void setup(){
  Serial.begin(9600);
}

void loop(){
  int static i = 0;
  static long tt;
  int val;
  
   if (millis() > tt){
	if (i < 128){
	  val = analogRead(xPin);
	  data[i] = val / 4 - 128;
	  im[i] = 0;
	  i++;  
	  
	}
	else{
	  //this could be done with the fix_fftr function without the im array.
	  fix_fft(data,im,7,0);
	  // I am only interessted in the absolute value of the transformation
	  for (i=0; i< 64;i++){
	     data[i] = sqrt(data[i] * data[i] + im[i] * im[i]);
	  }
	  
	  Serial.println(data);
	}
    
    tt = millis();
   }
}

Is this because the serial print function can't handle the arrays?

The Serial.print() function can print an array of characters, but it assumes that the data in the array really represents characters. The values in your array do not.

Using a for loop, and BYTE as the second argument to the Serial.print() function will show you the actual values in the array. Some separation between the values would make them easier to distinguish.

To find the fundamental frequency you should search the data for the highest value. You might want to skip element 0 which is the DC offset. The frequency can be calculated from the sample rate, the number of samples, and the index of the element.

Very bad idea, if I understood your project.

If you attach an accelerometer on a tuning fork, you change the frequency of the oscillation (up or down?)

Plus, most accelerometer evaluation boards have capacitors that filters output to something like 50Hz or less. Tuning fork is probably a few hundred to a couple thousand Hz so you can't get anything meaningful. You should have asked someone with college physics background before you planned this thing out.

You could use a microphone but arduino's ADC is not fast enough for most tuning forks. Use audacity or Processing with minim library instead.

@liudr I was looking at getting a 440Hz fork, I am aware that adding mass would decrease frequency. I was not aware of the low pass filters.

By the way, if people have been creating audio spectrum analyzers (just fancy bar graphs) using the FFT code. I think it's plenty fast enough for my purposes...

I have been toying with the idea of using a piezo disc, now that I see the problem with the accel bandwidth.

A piezo disc will do. The arduino's analog input can get up to 10,000 readings per second so maximal frequency it can capture is 5,000Hz. Your 440Hz fork should be OK. If you use say 2KHz fork then you're pushing it.

Sure, arduino fast enough, up to 33 kHz no sweat.
The code you start playing with, didn’t work for me eather

I’m afraid, that potentially problem could be not with High end, but rather with Low frequencies,
especially if you gonna use accelerometer. It’s not clear what is the limit on low side you 'd like to reach, 10 Hz or less than 1.
Formula: F = 1 / T, shows that the longer period of sampling data, the lower frequencies could be
discriminated. Due limited size of available memory on chip, it reasonable to get no more than 256 samples, with normal speed of ADC 0.1 msec/sample, it gives 25.6 msec overall,
or 1 / 0.025 = 40 Hz.
The only solution to reach good resolution below this value is actually to put delay !!! between
sampling, slow down ADC to 1 msec/sample and even lower.

Magician: Sure, arduino fast enough, up to 33 kHz no sweat.

Where did you get that number?

Please read page 251 of the ATMEGA168_328 documentation.

Up to 15ksps at highest resolution. I believe arduino is able to do 10,000 per seconds.

What's the bandwidth of your accelerometer? Many are limited to only a couple of hundred Hz.

@AWOL - looking at the datasheet, the ADXL322 breakout board has 0.1uF capacitors, giving me 50Hz of bandwidth :(

I guess I'll stick with the piezo disc. I was almost ready to give up last night! Does the resistor at the leads of the piezo disc control the sensitivity?

@Magician Let me try those corrections.

You could reduce the values of the bandwidth-limiting caps to get the BW up to around 500Hz, but that would increase the noise.

Quote from: Magician on May 30, 2011, 09:51:45 PM Sure, arduino fast enough, up to 33 kHz no sweat.

Where did you get that number?

Please read page 251 of the ATMEGA168_328 documentation.

Up to 15ksps at highest resolution. I believe arduino is able to do 10,000 per seconds.

You can actually get it up to ~307ksps but you have to change the ADC prescalar value. Technically there is a prescalar value that would allow ~615ksps but when I tried setting it it didn't work.

My own experiments with this register-preselector show results: " ADCSRA = 0x87; //freq = 1/128, 125 kHz. ~112 us/sample

ADCSRA = 0x84; //freq = 1/16, 1 MHz. ~14 us/sample

ADCSRA = 0x82; //freq = 1/4, 4 MHz. ~3.5 us/sample " That is in very good agreement with data publish in the "Arduino Cookbook" by Michael Margolis, p. 572: " The reduction of time from 113 microseconds to 17 microseconds is a significant improvement." I agree that his value 17 usec (my is 14 usec) is due analogread function used, and that what he says in the book: "With a 16 MHz board, the timebase rate is increased from 125 KHz to 1 MHz. The actual performance improvement is slightly less than eight times because there is some overhead in the Arduino analogRead function that is not improved by the timebase change. "

Changing ADCSRA = 0x87 to ADCSRA = 0x84 alone increase upper frequency up to ~ 4.2 * 8 = 33.6 kHZ !!!!!! ( 4.2 * 6.65 = 27.9 kHz with analogread). It's Nyquist frequency, sampling is double this value.

When I overdrive ADC of the Atmega328p 4 times more than that,applying ADCSRA = 0x82 (//freq = 1/4, 4 MHz) and pull raw data with on serial monitor, it print out the same value 512 for 2.5 V DC offset as it supposed to be. There was increasing in the noise floor, but it will require additional estimation if this a problem. -):

According to the data sheet, you can get up to 76.9ksps but it says up to 15ksps at highest resolution. I didn't read all the details but sampling beyond 15ksps will definitely reduce the significant digits you can keep in the 10-bit result. I can guess that you will lose a digit if you sample twice as fast :D

The ADC accuracy also depends on the ADC clock. The recommended maximum
ADC clock frequency is limited by the internal DAC in the conversion circuitry. For
optimum performance, the ADC clock should not exceed 200 kHz. However,
frequencies up to 1 MHz do not reduce the ADC resolution significantly.

And

When using single-ended mode, the ADC bandwidth is limited by the ADC clock
speed. Since one conversion takes 13 ADC clock cycles, a maximum ADC clock of 1
MHz means approximately 77k samples per second. This limits the bandwidth in
single-ended mode to 38.5 kHz, according to the Nyquist sampling theorem.

IMHO, probability to lost a digits and decrease resolution 1 bit for every doubling F only happened if cycle is NOT complete, that begins above 1 MHz. Whatever done, is done with 10 bits.
Their “do not reduce the ADC resolution significantly” could be related to overhead in algorithm,
some procedure calibration/linearizion/averaging/thermo-compensation, that buried inside the chip,
and nobody willing to disclose “know-how” of Atmel.

ADC_DOC2559.PDF (140 KB)

I got some code together to try and serial print the fundamental frequency of vibration on the accel ( under 50Hz).

The serial print is not returning any values. It does give me the max value’s index though. Am I missing something fundamental? :slight_smile:

#include <fix_fft.h>
char im[128], data[128], lastpass[64];
//char x=32, ylim=90;
int i=0,val;
int Fund=0;
void setup()
    {                                          
    
   for (int z=0; z<64; z++) {lastpass[z]=80;};    //  fill the lastpass[] array with dummy data
    Serial.begin(9600);
    }
void loop()
    {
    for (i=0; i < 128; i++){                                     // We don't go for clean timing here, it's
      val = analogRead(0);                                      // better to get somewhat dirty data fast
      data[i] = val/4 -128;                                       // than to get data that's lab-accurate
      im[i] = 0;                                                       // but too slow, for this application.
      }

    fix_fft(data,im,7,0);
    
  Fund = getIndexOfMaximumValue(data,64);  //Get max value's index
 
  Serial.println(data[Fund]); //Use index to return max value (Fundamental Frequency)
  delay(100);
} 

//Thanks AlphaBeta!
int getIndexOfMaximumValue(char* array, int size){ // This Function returns the max value's index in the array
  int maxIndex = 0;
  int max = array[maxIndex];
  for (int i=1; i<size; i++){
    if (max<array[i]){
      max = array[i];
      maxIndex = i;
    }
  }
  return maxIndex;
}

The index of the maximum value is related to the frequency. The data itself would just be the volume at the frequency.

To calculate the frequency you need to know the time, T, over which the samples were taken. Perhaps you can call micros() before and after the sampling and subtract the before from the after to get the time span in microseconds.

The frequency steps should be 1/T cycles per second. For example if the sampling takes 5000 microseconds (5 milliseconds, 0.005 seconds) then the steps between samples would be 1/0.005 Hz or 200 Hz. The value in data would the volume at frequency i * 200 Hz.

You are missing a math in a code.

  1. Sensor bandwidth, according to data sheet with capacitor 0.1 uF is 50 Hz, , one period equals
    T = 1 / 50 = 0.02 or 20 millisecond…
    Sampling frequency by Nyquist has to be at least twice higher, 100 Hz.
    Analog samples, stored in input raw data (ird) array should be taken with
    T = 1 / F = 1 / 100 = 0.01 or 10 millisecond period between them.

In your code, posted above, analogread inside “for” loop, and samples taken with a maximum speed of the analogread, that approximately 0.1 millisecond. All ird array would be filled up in a
0.1 * 128 = 12.8 millisecond, that is too fast.
As you can see, all data filed in a half of the period incoming signal (let presumed it’s 50 Hz, 20 millisecond), for lower frequency it even less than that.
Easiest way to fix it, put a delay in the loop:

for (i=0; i < 128; i++){                                     // We don't go for clean timing here, it's
      val = analogRead(0);                                      // better to get somewhat dirty data fast
      data[i] = val/4 -128;                                       // than to get data that's lab-accurate
      im[i] = 0;        
      delay (10);   //  <- period between samples
  1. If we assume fft_fix works fine (that is not true, plz read my previous comments),
    after processing input data we should get output array with 128 elements, “bins”, each representing a range of frequencies.
    The width of a bin equals:
    D = 1 / 2 * T, where T is ird array sampling timing, in our case T = 0.01 * 128 = 1.28 sec.
    D = 1 / (2 * 1.28) = 0.391 Hz.
    So, the value of data[0] is DC offset - 0.39 Hz;
    data[1] is amplitude in a range 0.39 - 0.78 Hz;
    data[2] is amplitude in a range 0.78 - 1.17 Hz;

    data[127] is amplitude in a range 49.61 - 50.00 Hz;

I'm still confused... how does the fundamental frequency (The frequency the accel is shaking at) relate to sample rate... I guess i'm confused about the freq I'm looking for vs the sampling frequency...

In general, they are not related each other.
But in real world resources are limited. For microprocessor the bottle neck is memory, 2 Kbyte (UNO).
Basic formula still the same,

D = 1 / 2*T , where D - resolution or accuracy your measurements, and
T is period of sampling data. (I’ve attached a doc., have a look)

So, if you’d like to get accuracy/precision 1 Hz ( it’s 2 % error for 50 Hz fundamental ),
that is not the best of the art, but acceptable, T must be:

T = 1 / 2D = 1/ 21 Hz = 0.5 second.

If memory limitless, you can read samples with default speed,
that is t=0.1 millisecond (10^-4 second), and accumulate N samples:

N = T / t = 0.5 s / 10^-4 = 5 000.

As you can see, it’s 5 Kbyte of data (let’s assume, for simplicity, that size of data 1 byte, not 2).
There is no memory to hold this amount of information.
You have to trade. And only variable on market is t = T / N.
For maximum N = 10^3 (don’t forget, not all 2 kByte could be consumed, as it required to run a stack and main program) t = T / N = 0.5 / 10^3 = 0.5 millisecond.
You have to increase t at least 5 times compare to default.
After that, two frequency (fundamental and sampling) become related, they bind by memory limits.

fourier-transform.pdf (121 KB)