You are on page 1of 17

Fast Fourier Transform (FFT) (Part 1)

FFT
Conceptually speaking FFT is pretty simple. Practically, FFT is tough! So that I will not go into all
the details of this algorithm and I will stay focused on its use and applications. This series of posts
will end up with a fully featured library that you will able to use for all sorts of applications, without
bothering about the maths.
The plot on an oscilloscope shows a wave which is made of samples which are characterized by their
intensities (Ordinates) and their sampling time (Abscissa). We are in the time domain.

The simpler the signal (e.g. a single sine wave), the simpler the characterization. The Fourier
Transform proposes to decompose any signal into a sum of sin and cos. To each data point from the
FFT power spectrum corresponds a magnitude (Ordinates) and a frequency (Abscissa). We are now
in the frequency domain.

The more complex the signal (e.g. signal + harmonics + noise)

the more complex the characterization.

And guess what? The FFT algorithm can be executed in reverse mode, so that starting from a FFT
you can rebuild a real life signal. We may study later how this property can be used to denoise
signals.
Check the following links for a global explanation, and a more detailed information related to the
algorithm itself. They are many papers dedicated to FFT, and you may also like to learn by doing,
using this applet.
FFT applies to vectors containing n samples, where n must be a power of 2. Executing the algorithm
on this data may lead to unexpected results. The reason being that the vector truncates the signal
information and may contain incompletely described waves (e.g. low frequency waves). This side
effect may be corrected by weighing the signal, giving less importance to the leading and tailing
data. This is the windowing function. The weighing function in the windowing depends upon the
type of signal to be analysed:

Transients whose duration is shorter than the length of the window : Rectangular (Box car)

Transients whose duration is longer than the length of the window : Exponential, Hann

General-purpose applications : Hann

Spectral analysis (frequency-response measurements) : Hann (for random excitation),


Rectangular (for pseudorandom excitation)

Separation of two tones with frequencies very close to each other but with widely differing
amplitudes : Kaiser-Bessel

Separation of two tones with frequencies very close to each other but with almost equal
amplitudes : Rectangular

Accurate single-tone amplitude measurements : Flat top

Sine wave or combination of sine waves : Hann

Sine wave and amplitude accuracy is important : Flat top

Narrowband random signal (vibration data) : Hann

Broadband random (white noise) : Uniform

Closely spaced sine waves : Uniform, Hamming

Excitation signals (hammer blow) : Force

Response signals : Exponential

Unknown content : Hann

Once the windowing is executed, you can run the FFT. The result of this algorithm lies in to vectors
containing the real and imaginary computed values. You need then to apply some math to convert
them into a vector of intensities.
Window type Rectangle (box car)

Window type Hamming:

Window type Flat top

All plots exported from my DSP tool box Panorama


Here is a recap of the pieces of code that we need in order to convert a wave into a frequency
spectrum:

Store data in a vector (Double data type)

Weigh this data according to one function (default is Rectangle, also known as box car, in
other words, no weighing!)

Execute the FFT algorithm

Convert complex values in usable data

Without knowing too much of the details from the various algorithms, you will very quickly face a
dilemma: would you prefer speed or precision? Precision will require large vectors of data, while
speed will require multiple vectors: e.g. instead of computing weighed values for each new set of
data, we could compute weighing factors once and apply them repeatedly. Same thoughts for the bit
reversal at the top of the FFT algorithm.

The proposed example do not use pre-processing of data, to the benefit of the number of samples,
and to the clarity of the code.
Firstly, some constants shall be declared in the header (.h) file
// Custom constants
#define FORWARD 0x01
#define REVERSE 0x00
// Windowing type
#define WIN_TYP_RECTANGLE 0x00// rectangle (Box car)
#define WIN_TYP_HAMMING 0x01// hamming
#define WIN_TYP_HANN 0x02// hann
#define WIN_TYP_TRIANGLE 0x03// triangle (Bartlett)
#define WIN_TYP_BLACKMAN 0x04// blackmann
#define WIN_TYP_FLT_TOP 0x05// flat top
#define WIN_TYP_WELCH 0x06// welch
Then data shall be stored in one of the two fixed size vectors
1
2
3

const uint8_t samples = 64;


double vReal[samples];
double vImag[samples];

And this is the weighing routine


void PlainFFT::windowing(double *vData, uint8_t samples, uint8_t windowType, uint8_t dir) {
// The weighing function is symetric; half the weighs are recorded
double samplesMinusOne = (double(samples) - 1.0);
for (uint8_t i = 0; i < (samples >> 1); i++) {
double indexMinusOne = double(i);
double ratio = (indexMinusOne / samplesMinusOne);
double weighingFactor = 1.0;
// compute and record weighting factor
switch (windowType) {
case WIN_TYP_RECTANGLE: // rectangle (box car)
weighingFactor = 1.0;
break;
case WIN_TYP_HAMMING: // hamming
weighingFactor = 0.54 - (0.46 * cos(2.0 * pi * ratio));
break;
case WIN_TYP_HANN: // hann
weighingFactor = 0.54 * (1.0 - cos(2.0 * pi * ratio));
break;
case WIN_TYP_TRIANGLE: // triangle (Bartlett)
weighingFactor = 1.0 - ((2.0 * abs(indexMinusOne - (samplesMinusOne / 2.0))) /
samplesMinusOne);
break;
case WIN_TYP_BLACKMAN: // blackmann
weighingFactor = 0.42323 - (0.49755 * (cos(2.0 * pi * ratio))) + (0.07922 * (cos(4.0 * pi *
ratio)));
break;

case WIN_TYP_FLT_TOP: // flat top


weighingFactor = 0.2810639 - (0.5208972 * cos(2.0 * pi * ratio)) + (0.1980399 * cos(4.0 *
pi * ratio));
break;
case WIN_TYP_WELCH: // welch
weighingFactor = 1.0 - sq((indexMinusOne - samplesMinusOne / 2.0) / (samplesMinusOne /
2.0));
break;
}
if (dir == FORWARD) {
vData[i] *= weighingFactor;
vData[samples - (i + 1)] *= weighingFactor;
}
else {
vData[i] /= weighingFactor;
vData[samples - (i + 1)] /= weighingFactor;
}
}
}
Notes:

There is a little trick here. As the weighing function is symetrical, why bother computing
them all? Half of them are computed and applied symetrically to the vector of data

The dir parameter stands for direction: remember, FFT is reversible, so that we can apply it in
FORWARD or REVERSE mode

FFT stands for fast Fourier Transform. The DFT (Discret Fourier Transform) applies to vectors
containing any number of signal samples. But it is
sssssssssllllllllllllllllllllllloooooooooooooowwwwwwww due to the repeated number of operation.
Computing a DFT of n points takes n^2 arithmetical operations, while an FFT can compute the same
result in only n log2(n) operations. The implemented algorithm is the CooleyTukey algorithm
which is the far most popular. The only limitation is that the number of samples in the signal must
be a power of two. However, if the number of samples is less than that, it is possible to replace
missing data with 0 values without dramatic alteration of the final result: this is the zero padding. As
a result of padding zeros to the existing data, the spectral resolution will be degraded.
Once the FFT algorithm is executed, we get complex values, made of imaginary values and real
values. We need to apply some maths in order to convert them in magnitude values. This is quite
simply done thanks to the following routine which converts the complex (so as to say rectangular)
values into a polar value:
void PlainFFT::complexToMagnitude(double *vR, double *vI, uint8_t samples) {
// vM is half the size of vR and vI
for (uint8_t i = 0; i < samples; i++) {
vR[i] = sqrt(sq(vR[i]) + sq(vI[i]));
}
}
Read this document about conversion (p 6)

Computing the phase information is quite easy, but I do not really care about it and it requires the
arctangent() trigonometric which does not come standard in Arduino language. However if you need
it, you will have to use the math library) and compute the pahse values using the following formula:
phase[FFT(A)]=arctangent(|Imag[FFT(A)]| / |Real[FFT(A)]|).
So far so good, we got our magnitude spectrum. We may now analyze it just like any other spectrum
in order to get meaningful information. In our case, we may want to identify the main frequencies
from the spectrum. Firstly we will run a peak picking algorithm in order to locate major peaks, and
then keep the most intense one. Those who want to perform spectral comparison will have to keep all
significant peaks.
There are lots of peak picking algorithm! But the one we need has to be small and fast. This one is
pretty trivial but it works great.
Secondly, because of the poor peak shapes (lack of large vectors of data) we need to interpolate the
peak apex in order to get an accurate frequency. I am using a non iterative quadratic interpolation
which gives good results too.
Both routines are merged in one function
double majorPeak(double *vD, uint8_t samples) {
double maxY = 0;
uint8_t IndexOfMaxY = 0;
for (uint8_t i = 1; i < (samples - 1); i++) {
if ((vD[i-1] < vD[i]) && (vD[i] > vD[i+1])) {
if (vD[i] > maxY) {
maxY = vD[i];
IndexOfMaxY = i;
}
}
}
double delta = 0.5 * ((vD[IndexOfMaxY-1] - vD[IndexOfMaxY+1]) / (vD[IndexOfMaxY-1] (2.0 * vD[IndexOfMaxY]) + vD[IndexOfMaxY+1]));
double interpolatedX = (IndexOfMaxY + delta) / samplingDuration;
return(interpolatedX);
}
PlainFFT is a simple but effective library which contains all the previously described functions for
running FFT on vectors of data.
Here is the example code from the library files which demonstrates the (pretty easy) use of the FFT.
The content of the vectors of interest is printed on completion of each FFT stage
#include "PlainFFT.h"
PlainFFT FFT = PlainFFT(); // Create FFT object
// These values can be changed in order to evaluate the functions
const uint16_t samples = 64;
double signalFrequency = 1000;
double samplingFrequency = 5000;
uint8_t signalIntensity = 100;
// These are input and output vectors

double vReal[samples];
double vImag[samples];
uint8_t runOnce = 0x00;
#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02
void setup(){
Serial.begin(115200);
Serial.println("Ready");
}
void loop() {
if (runOnce == 0x00) {
runOnce = 0x01;
// Build raw data
double cycles = (((samples-1) * signalFrequency) / samplingFrequency);
for (uint8_t i = 0; i < samples; i++) {
vReal[i] = uint8_t((signalIntensity * (sin((i * (6.2831 * cycles)) / samples) + 1.0)) / 2.0);
}
printVector(vReal, samples, SCL_TIME);
FFT.windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); // Weigh
data
printVector(vReal, samples, SCL_TIME);
FFT.compute(vReal, vImag, samples, FFT_FORWARD); // Compute FFT
printVector(vReal, samples, SCL_INDEX);
printVector(vImag, samples, SCL_INDEX);
FFT.complexToMagnitude(vReal, vImag, samples); // Compute magnitudes
printVector(vReal, (samples >> 1), SCL_FREQUENCY);
double x = FFT.majorPeak(vReal, samples, samplingFrequency);
Serial.println(x, 6);
}
}
void printVector(double *vD, uint8_t n, uint8_t scaleType) {
double timeInterval = (1.0 / samplingFrequency);
for (uint16_t i = 0; i < n; i++) {
// Print abscissa value
switch (scaleType) {
case SCL_INDEX:
Serial.print(i, DEC);
break;
case SCL_TIME:
Serial.print((i * timeInterval), 6);
break;
case SCL_FREQUENCY:
Serial.print((i / (timeInterval * (samples-1))), 6);
break;
}

Serial.print(" ");
// Print ordinate value
Serial.print(vD[i], 6);
Serial.println();
}
Serial.println();
}
Attention:

The distributed code is slightly different from the posted code due to last minute changes and
optimizations

Samples value must be a power of 2

Make sure you have enough memory before incresing the size of vectors

Vectors must contain unsigned integers (0 to 127), you can easily change that to 16 bits
integers (signed or not) to the cost of doubling the vectors size

Remember the Nyquist theorem when changing the sampling and the signal frequencies

Note: The huge popularity of PlainDSP (merge of PlainFFT and PlainADC libraries) and the
numerous requests for help drove me to think about a convenient solution for all designers, artists,
students, professors, R&D people, prototypists who need to understand, experiment, create
applications featuring advanced Digital Signal Processing on Arduino. The result of intense thoughts,
design and writing is the collection PlainDSP kits, starting with the audio kit.
Those who exercized the library and tried to change the default value from the customizable
variables (and most probably the size of both data vectors) probably faced some lockups, inifinite
loops or unpredicted operations.
In most cases, the answer to these problems lies in the limited size of the arduino memory Lets be
positive and study this subject.
Depending upon the type of Arduino platform, you may use one of these microprocessors:

Arduino Diecimila: ATMega168 (data sheet)

Arduino Duemilanove: ATMega368 (data sheet)

Arduino Uno: ATmega328 (data sheet)For other platforms, follow this thread
For older platforms, follow this threadEach of these microprocessors has three separate
memories:

FLASH memory: this is a non volatile memory space where the sketch is stored

SRAM (Static Random Access Memory): this is the memory sapce where the sketch creates
and manipulates variables at run time

EEPROM (Electrically-Erasable Programmable Read-Only Memory): this is a non volatile


memory space where data can be read/write at run time. This memory has limitations: the

number of read/write cycle is estimated to be no more than 100.000, and it takes real time to
get access to it

Lets go back to our problem. The size of the SRAM (where the vectors are created at run
time) depends upon the microprocessor:

ATMega168: 1024 bytes

ATMega368: 2048 bytes

That is not much and every byte counts!Now lets consider the vectors. Firstly, we have to
decide about the type of data to be recorded:

boolean or char or unsigned char or byte or uint8_t: 1 byte each

int or unsigned int or uint16_t or int16_t: 2 bytes each

long or unsigned long or uint32_t or int32_t: 4 bytes each

float or double: 4 bytes each

For more information on data types, follow this threadThis means that the following vectors
will occupy:
byte vX[32]; // 32*8=256 bytes
int vX[32]; // 32*16=512 bytes
float vX[32]; // 32*32=1024 bytes

So that it is a good idea to check the available memory space prior to running sketches. This
how you can do this:
int memoryTest() {
// This function will return the number of bytes currently free in SRAM
int byteCounter = 0; // initialize a counter
byte *byteArray; // create a pointer to a byte array
// Use the malloc function to repeatedly attempt allocating a certain number of bytes to memory
while ( (byteArray = (byte*) malloc (byteCounter * sizeof(byte))) != NULL ) {
byteCounter++; // If allocation was successful, then up the count for the next try
free(byteArray); // Free memory after allocating it
}
free(byteArray); // Also free memory after the function finishes
return byteCounter; // Send back the number number of bytes which have been successfully
allocated
}

This code has been written by Rob Faludi

I received some questions related to the use of the FFT library. This example illustrate how to
interface the FFT function to an acquisition engine, such as the optimized one from the
PlainADC library.
/*

Example of use of the ADC and FFT libraries


Copyright (C) 2010 Didier Longueville
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Printing function options */
#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02
#include
#include
PlainADC ADC = PlainADC(); /* Create ADC object */
PlainFFT FFT = PlainFFT(); /* Create FFT object */
/* User defined variables */
const uint16_t samples = 128;
uint16_t frequency = 20000;
uint8_t channel = 0;
/* Data vectors */
uint8_t vData[samples];
double vReal[samples];
double vImag[samples];
void setup(){
/* Initialize serial comm port */
Serial.begin(115200); //
/* Set acquisition parameters */
ADC.setAcquisitionParameters(channel, samples, frequency);
}
void loop() {
/* Acquire data and store them in a vector of bytes */
ADC.acquireData(vData);
/* Convert 8 bits unsigned data in 32 bits floats */
for (uint16_t i = 0; i < samples; i++) {

vReal[i] = double(vData[i]);
}
/* Weigh data */
FFT.windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
/* Compute FFT: vReal and vImag vectors contain the source data
and will contain the result data on completion of executing the function*/
FFT.compute(vReal, vImag, samples, FFT_FORWARD);
/* Compute magnitudes: the resulting data can be read from the vReal vector */
FFT.complexToMagnitude(vReal, vImag, samples);
/* Upload frequency spectrum */
printVector(vReal, (samples >> 1), SCL_FREQUENCY);
/* Pause */
delay(5000);
}
void printVector(double *vD, uint8_t n, uint8_t scaleType) {
/* Mulitpurpose printing function */
double timeInterval = (1.0 / frequency);
for (uint16_t i = 0; i < n; i++) {
/* Print abscissa value */
switch (scaleType) {
case SCL_INDEX: Serial.print(i, DEC); break;
case SCL_TIME: Serial.print((i * timeInterval), 6); break;
case SCL_FREQUENCY: Serial.print((i / (timeInterval * (samples-1))), 6); break;
}
Serial.print(" ");
/* Print ordinate value */
Serial.print(vD[i], 6);
Serial.println();
}
Serial.println();
}

Note: The huge popularity of PlainDSP (merge of PlainFFT and PlainADC libraries) and the
numerous requests for help drove me to think about a convenient solution for all designers,
artists, students, professors, R&D people, prototypists who need to understand, experiment,
create applications featuring advanced Digital Signal Processing on Arduino. The result of
intense thoughts, design and writing is the collection PlainDSP kits, starting with the audio
kit.
Here are a few comments on the use of PlainFFT.
First of all: PlainFFT performs in place calculation, which means that the results are recorded
within the vectors containing the source data.
Usable results are available on completion of the ComplexToReal() execution. Before
execution, vReal and vImag vectors contain the transformed results from the FFT. After
execution, the vReal vector contains two times the frequency spectrum in a perfectly
symetric way.
The PlainFFT_01.ino skecth example shows

1 /* Compute magnitudes */
2 FFT.ComplexToReal(vReal, vImag, samples, FFT_SCL_TYP_AMPLITUDE);

While in fact we could save processing time with

1 /* Compute magnitudes */
2 FFT.ComplexToReal(vReal, vImag, samples >> 1, FFT_SCL_TYP_AMPLITUDE);

As long as, in any way the sketch uses

1 /* Print frequency spectrum */


2 PrintVector(vReal, (samples >> 1), SCL_FREQUENCY);

for printing the spectrum. CQFD.


The sampling frequency (Lets call this variable Fs) determines the frequency range of the
spectrum while the number of points acquired during signal acquisition (Lets call this
variable N) determines the resolution frequency. As a consequence of that::
To Increase the frequency range, increase the signal sampling frequency
To increase the frequency resolution for a given frequency range, increase the number of
points acquired at the same sampling frequency.
The result from the FFT can be viewed as a collection of N/2 bins. To each bin corresponds
a frequency range (or bandwidth), centred on a frequency value, as reported in the vReal
vector. The width of each bin (Lets call this variable Df) is equal to Fs/N. The last bin is
centred on the max frequency (Lets call this variable Fmax) at (Fs/2)-(Fs/N). The first bin is
a little bit special: It is centred on 0Hz, so as to say on the DC component of the signal. So
that the graphical bin representation should be half the size of the other bins!
All these parameters can be pictured in the following way
Bin 1 Bin 2 Bin 3
Bin N/2
. . . . // . .
. . . . // . .
. . . . // . .
. : . : . : . // . : .
| | |
| |
| | |
| -- Nyquist frequency(Fs/2)
| | |
------- Max. Frequency (Fmax)
| | --------------------- Df * 2
| --------------------------- Df * 1
--------------------------------- Df * 0, 0Hz (DC)
In the real world, the amount of signal resulting from:
Df * 0 +/- Df /2, in other words 0hz, is read from vReal [0] Df * 1 +/- Df /2 is read from
vReal [1] Df * 2 +/- Df /2 is read from vReal [2] And so on, up to
Df * N/2 +/- Df /2, so as to say Fmax, is read from vReal [N/2]
Each bin contains the power of one or more signals which frequencies fit in the band
bandwith. From this point, it is obvious that the narrower the bin, the better the selectivity of
the filter. In some, not to say most, circumstances, the spectrum representation does not
exactly looks like pure histograms.
For a given signal characterized by a pure sinusoidal function, the most abundant power is
read from the bin which corresponds to the signal frequency, but the adjacent bins may
contain some power abundances too. This is due to the frequency leakage. We may use one
from the next options to overcome this phenomenon:
Increase the number of samples (N) in order to improve the selectivity
Apply appropriate windowing
Apply spectrum peak interpolation
Peak interpolation helps improving the most accurate peak apex location thanks to some
linear (e.g. Quadratic fit) or non-linear (e.g. Gaussian fit). While this interpolation applies to a

limited number of consecutive data points, we may apply simplified formula for computing
apex locations. Peak interpolation is almost mandatory when all significant peaks from a
frequency spectrum must be correlated (e.g. Finding harmonics).
The following pictures illustrate various use cases of PlainFFT for plotting frequency
spectrum. In all cases, a Hann window was applied as well as Gaussian peak apex
interpolation.
Here is the frequency spectrum from a sinusoidal signal acquired with a sampling rate of
1kHz over 256 samples:

Here is the frequency spectrum from a sinusoidal signal acquired with a sampling rate of
1kHz over 32768 samples:

Here is the frequency spectrum from a sinusoidal signal acquired with a sampling rate of
500Hz over 256 samples:

Note: The peak looks broader than in picture 1, but this feeling is related to the narrower
scale!
This is probably on over-simplified explanation, but it is sufficient for using PlainFFT in the
most appropriate manner. Feel free to add your comments and suggestions.
Now that we know how to get nice, clean frequency spectra comes the time for extracting
valuable information. For this pupose, I added a couple of DSP functions to the (now very
popular) PlainFFT library.
As we will deal with peaks, I created a dedicated variable which will be update by these
functions:

typedef struct strPeakProperties {


double position;
double height;
uint16_t bin;
};

The first function searches for the strongest frequency peak within a frequency range. The
principle of operation is pretty straightforward but quite effective. Firstly, the function
converts the lower and upper frequency range into a lower and an upper bin. Then it prevents
searching out the data vector bounds. For each data point within the sought range, the
function looks for data points which are surrounded by lowest signals. Once a peak is
identified, its height is compared to the previously highest recorded signal.
Finally, the function updates the peak variable. Note that it is the pointer to structure which
contains the result which passed to the function as an argument.
Here is a copy of the MajorPeak function:

void PlainFFT::MajorPeak(double *vData, uint16_t samples, double samplingFrequency, double


loFrequency, double upFrequency, struct strPeakProperties *result)
/*
Find the major peak within a frequency spectrum
A peak is determined by three consecutive points where the central one is above the others
The major peak is the highest peak within a range
*/
{
int16_t loBin = ((loFrequency * samples) / samplingFrequency);
int16_t upBin = ((upFrequency * samples) / samplingFrequency);
/* Check parameters */
if (loBin < 1) {
loBin = 1;
}
if (upBin > ((samples >> 1) - 1)) {
upBin = ((samples >> 1) - 1);
}
/* Compute results */
double yMax = 0;
uint16_t binOfYMax = 0;
for (uint16_t i = loBin; i < upBin; i++) {
double y_m1 = *((vData + i) - 1); /* y - 1 value */
double y = *(vData + i); /* actual y value */
double y_p1 = *((vData + i) + 1); /* y + 1 value */
/* Is the actual point above its right and left neighbors? */
if ((y_m1 < y) && (y > y_p1)) {
/* Is the actual point higher than any other previuously analysed point? */
if (y > yMax) {
/* Record values */
yMax = y;
binOfYMax = i;
}
}
}
/* Update results */
result->bin = binOfYMax;
result->position = ((binOfYMax * samplingFrequency) / samples);
result->height = yMax;
};

In many cases, we will look for the strongest peak within the whole frequency spectra, thus
the overloaded function:

void PlainFFT::MajorPeak(double *vData, uint16_t samples, double samplingFrequency, struct


strPeakProperties *result)
{
MajorPeak(vData, samples, samplingFrequency, 0.0, (samplingFrequency / 2.0), result);
};

Note: The huge popularity of PlainDSP (merge of PlainFFT and PlainADC libraries) and the
numerous requests for help drove me to think about a convenient solution for all designers,
artists, students, professors, R&D people, prototypists who need to understand, experiment,
create applications featuring advanced Digital Signal Processing on Arduino. The result of
intense thoughts, design and writing is the collection PlainDSP kits, starting with the audio
kit.
Here is an other peak related DSP function. The purpose of this one is to get the peak
information of a targeted frequency peak. It uses the same early steps as in the MajorPeak
function. The filtering process includes a peak interpolation (quadratic type), and the
calculated peak apex position is compared to the target value. The function updates the
structure (passed by its address) containing information related to best fitting peak.

void PlainFFT::TargetPeak(double *vData, uint16_t samples, double samplingFrequency, double


targetPosition, double tolerance, struct strPeakProperties *result)
/* Finding quadratically interpolated peaks */
{
int16_t loBin = (((targetPosition - tolerance) * samples) / samplingFrequency);
int16_t upBin = (((targetPosition + tolerance) * samples) / samplingFrequency);
/* Check parameters */
if (loBin < 1) {
loBin = 1;
}
if (upBin > ((samples >> 1) - 1)) {
upBin = ((samples >> 1) - 1);
}
double freqInterval = (samplingFrequency / samples);
double least_x_err = DBL_MAX;
double best_x_calc = 0.0;
double best_y_calc = 0.0;
uint16_t bestBin = 0;
for (uint16_t i = loBin; i < upBin; i++) {
double x = (i * freqInterval); /* actual x value */
double y_m1 = *((vData + i) - 1); /* y - 1 value */
double y = *(vData + i); /* actual y value */
double y_p1 = *((vData + i) + 1); /* y + 1 value */
if ((y_m1 < y) && (y > y_p1)) {
double x_diff = (((y_m1 - y_p1) / (y_m1 - (2.0 * y) + y_p1)) / 2.0);
double x_calc = (x + (x_diff * freqInterval));
double y_calc = (y - (((y_m1 - y_p1) * x_diff) / 4.0));
double x_err = fabs(x - targetPosition);
if (x_err < least_x_err){
/* Retain best peak properties */

least_x_err = x_err;
best_x_calc = x_calc;
best_y_calc = y_calc;
bestBin = i;
}
}
}
/* Returned results*/
result->bin = bestBin;
result->position = best_x_calc; /* equ to (*result).position = best_x_calc; */
result->height = best_y_calc;
}

Other interpolation methods can be used . Their performances depend very much on the peak
shapes which are themselves dependent on the windowing type and the number of data
points describing the peaks. In our particular case, we are often short in data points (restricted
number of samples due to limited amount of memory) and we do not want to spend too long
at modelling peak shapes thus the choice for the quadratic model.
Note: The huge popularity of PlainDSP (merge of PlainFFT and PlainADC libraries) and the
numerous requests for help drove me to think about a convenient solution for all designers,
artists, students, professors, R&D people, prototypists who need to understand, experiment,
create applications featuring advanced Digital Signal Processing on Arduino. The result of
intense thoughts, design and writing is the collection PlainDSP kits, starting with the audio
kit.

You might also like