Hello,
I've been trying to use the FFT library to measure the frequency from a function generator connected to the analogue pins of the Arduino. The set up is simple: A crocodile probe is connected from the function generator to the A0 and Ground of the Arduino. When I generate functions that are low in frequency (10Hz - 90Hz), the results aren't too bad. When I go above 100Hz, it just picks up odd frequencies. I would like it to be more accurate and consistent, but I don't know what to change in the code. I kept messing around with the number of samples, sampling frequencies, and time intervals, but I never got the frequency that the function generator is producing. I've been working on this for quite some time, but I got nowhere. I would truly appreciate any help. Thank you! Please find code below:
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include "arduinoFFT.h"
arduinoFFT FFT = arduinoFFT(); /* Create FFT object */
/*
These values can be changed in order to evaluate the functions
*/
const uint16_t samples = 128; //This value MUST ALWAYS be a power of 2
const uint8_t amplitude = 100;
const double samplingFrequency = 1000;
unsigned long sampling_period = 1000000 / samplingFrequency; // since time is in micros here
/*
These are the input and output vectors
Input vectors receive computed results from FFT
*/
double vReal[samples];
double vImag[samples];
#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02
#define SCL_PLOT 0x03
void setup()
{
Serial.begin(115200);
Serial.println("Ready");
}
void loop()
{
uint32_t ts1 = micros();
int reading = analogRead(A0); // Analogue 0
float signalFrequency = reading * (5.0 / 1023.0); // normalize
/* Build raw data */
double cycles = (((samples - 1) * signalFrequency) / samplingFrequency); //Number of signal cycles that the sampling will read
for (uint16_t i = 0; i < samples; i++)
{
vReal[i] = int8_t((amplitude * (sin((i * (twoPi * cycles)) / samples))) / 2.0);/* Build data with positive and negative values*/
//vReal[i] = uint8_t((amplitude * (sin((i * (twoPi * cycles)) / samples) + 1.0)) / 2.0);/* Build data displaced on the Y axis to include only positive values*/
vImag[i] = 0.0; //Imaginary part must be zeroed in case of looping to avoid wrong calculations and overflows
}
/* Print the results of the simulated sampling according to time */
Serial.println("Data:");
PrintVector(vReal, samples, SCL_TIME);
FFT.Windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); /* Weigh data */
Serial.println("Weighed data:");
PrintVector(vReal, samples, SCL_TIME);
FFT.Compute(vReal, vImag, samples, FFT_FORWARD); /* Compute FFT */
Serial.println("Computed Real values:");
PrintVector(vReal, samples, SCL_INDEX);
Serial.println("Computed Imaginary values:");
PrintVector(vImag, samples, SCL_INDEX);
FFT.ComplexToMagnitude(vReal, vImag, samples); /* Compute magnitudes */
Serial.println("Computed magnitudes:");
PrintVector(vReal, (samples >> 1), SCL_FREQUENCY);
double x;
double v;
FFT.MajorPeak(vReal, samples, samplingFrequency, &x, &v);
Serial.print(x, 6);
Serial.print(", ");
Serial.println(v, 6);
while ( (micros() - ts1) < sampling_period ); // constant time interval
}
void PrintVector(double *vData, uint16_t bufferSize, uint8_t scaleType)
{
for (uint16_t i = 0; i < bufferSize; i++)
{
double abscissa;
/* Print abscissa value */
switch (scaleType)
{
case SCL_INDEX:
abscissa = (i * 1.0);
break;
case SCL_TIME:
abscissa = ((i * 1.0) / samplingFrequency);
break;
case SCL_FREQUENCY:
abscissa = ((i * 1.0 * samplingFrequency) / samples);
break;
}
Serial.print(abscissa, 6);
if (scaleType == SCL_FREQUENCY) {
Serial.print("Hz");
Serial.print(" ");
Serial.println(vData[i], 4);
}
Serial.println();
Serial.print("\n");
}
}
That coded doesn't sample the signal from A0. It reads A0 once and uses that one-time sample to set the frequency of a virtual sine wave that it synthesizes.
The Arduino can't read negative voltages (it can be damaged by negative voltages) so without the bias you're reading a half-wave rectified sine wave, which has lots of harmonics.
The DC (zero-Hz) bias does give you FFT results in the zero-Hz bin, but you can simply ignore it.
Thank you all for your replies. Regarding the negative voltages, the function generator has an offset to regard for that. I tried the link proposed by "jermington" (ArduinoFFT - Open Music Labs Wiki).
The example uses Serial.write. I changed it to Serial.println and added a for loop. I'm not sure if the for loop is correct. The results don't make sense; they're shown in the figure below. In addition, please find my code below:
/*
fft_adc.pde
guest openmusiclabs.com 8.18.12
example sketch for testing the fft library.
it takes in data on ADC0 (Analog0) and processes them
with the fft. the data is sent out over the serial
port at 115.2kb. there is a pure data patch for
visualizing the data.
*/
// do #defines BEFORE #includes
#define LOG_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#include <FFT.h> // include the library
void setup() {
Serial.begin(115200); // use the serial port
TIMSK0 = 0; // turn off timer0 for lower jitter - delay() and millis() killed
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0
DIDR0 = 0x01; // turn off the digital input for adc0
}
void loop() {
while (1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
while (!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= 0x0200; // form into a signed int
k <<= 6; // form into a 16b signed int
fft_input[i] = k; // put real data into even bins
fft_input[i + 1] = 0; // set odd bins to 0
}
// window data, then reorder, then run, then take output
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_log(); // take the output of the fft
sei(); // turn interrupts back on
Serial.println(255); // send a start byte
for (int j = 0 ; j < 256 ; j++) {
Serial.println(fft_log_out[j], 128); // send out the data
}
Serial.print("\n");
}
}
the function generator has an offset to regard for that
What, exactly, is the function generator output?
The "for loop" is not correct. There are only 128 unique values in the Fourier transform of a 256 element real input array, and you are not correctly using Serial.println().
Delete this line:
Serial.println(255); // send a start byte
Change this:
for (int j = 0 ; j < 256 ; j++) {
Serial.println(fft_log_out[j], 128); // send out the data
to this:
for (int j = 0 ; j < 128 ; j++) {
Serial.println(fft_log_out[j]); // print the result on the serial monitor
It is always best to test unfamiliar code with an example where you know what the result should be. Try this test, which generates a input waveform and transforms it.
/*
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 64 // set to 64 point fft
#include <FFT.h> // include the library
void setup() {
Serial.begin(9600); // output on the serial port
int i,k;
float f1=2.0,f2=5.0; //the two input frequencies (bin values)
for (i = 0 ; i < FFT_N ; i++) { // create 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_window(); //Try with and without this line, it smears
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]); //*2 for "negative frequency" amplitude
}
Serial.println("Done");
}
void loop() {}
I generate for example 100 Hz Sine function that oscillates between 0 and 5 Volts. I understand your answer and I tried out the test you proposed and it works! I've also modified the code accordingly (code below). However the values outputted are in bins. These are a sample of the results I get:
/*
fft_adc.pde
guest openmusiclabs.com 8.18.12
example sketch for testing the fft library.
it takes in data on ADC0 (Analog0) and processes them
with the fft. the data is sent out over the serial
port at 115.2kb. there is a pure data patch for
visualizing the data.
*/
// do #defines BEFORE #includes
#define LOG_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#include <FFT.h> // include the library
void setup() {
Serial.begin(115200); // use the serial port
TIMSK0 = 0; // turn off timer0 for lower jitter - delay() and millis() killed
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0
DIDR0 = 0x01; // turn off the digital input for adc0
}
void loop() {
while (1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
while (!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= 0x0200; // form into a signed int
k <<= 6; // form into a 16b signed int
fft_input[i] = k; // put real data into even bins
fft_input[i + 1] = 0; // set odd bins to 0
}
// window data, then reorder, then run, then take output
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_log(); // take the output of the fft
sei(); // turn interrupts back on
for (int j = 0 ; j < 128 ; j++) {
Serial.println(fft_log_out[j]); // send out the data
}
}
}
How am I supposed to interpret these results?
How can I convert it to Hz?