You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OpenAudio_ArduinoLibrary/examples/FFTFrequencyMeter/FFTFrequencyMeter.ino

122 lines
4.4 KiB

/* FFTFrequencyMeter.ino
* Bob Larkin 20 Feb 2021 Pblic Domain
*
* This INO demonstrates the use of FFT measurement of frequency.
* It generates a known, random frequency between 300 and 3000 Hz,
* measures that frequency with FFT interpolation, and prints the
* two frequencies and the difference between them (the error).
* This is mathematically correct for the Hann window only. A run
* of 400 samples of this INO showed:
* Average error = -0.0003 Hz
* Standard Deviation = 0.0247 Hz
* Maximum +/- error 0.042 Hz
* The method was generously provided by DerekR
* https://forum.pjrc.com/threads/36358-A-New-Accurate-FFT-Interpolator-for-Frequency-Estimation
*
* Homework: Create a known S/N by adding a SynthGaussianWhiteNoise
* object combined along with the sine wave by an AudioMixer_F32 object.
* Cut the Serial output and paste it
* into your favorite spread sheet. Find the power S/N where
* the standard deviation of the error rises to a tenth of the 44100/1024
* = 43 Hz bin spacing.
*
* This INO was originally put together to demonstrate the use of the
* myFFT.getData(); that returns a pointer to the FFT output data. See
* findFrequency() below.
*
* The Knuth and Lewis random noise generator might be useful elsewhere.
*/
#include "OpenAudio_ArduinoLibrary.h"
#include "AudioStream_F32.h"
#include "Audio.h"
AudioSynthWaveformSine_F32 wv;
AudioAnalyzeFFT1024_F32 myFFT;
AudioOutputI2S_F32 i2sOut;
AudioConnection_F32 patchCord1(wv, 0, myFFT, 0);
void setup(){
Serial.begin(9600);
delay(1000);
AudioMemory_F32(20);
Serial.println("FFT Frequency Meter");
Serial.println("Actual Measured Difference");
wv.amplitude(0.5); // Initialize Waveform Generator
wv.frequency(1000);
myFFT.setOutputType(FFT_POWER);
myFFT.windowFunction(AudioWindowHanning1024);
}
void loop() {
static float sineFrequency, fftFrequency;
Serial.print(sineFrequency, 3); Serial.print(", ");
fftFrequency = findFrequency();
Serial.print(fftFrequency, 3); Serial.print(", ");
Serial.println(fftFrequency - sineFrequency, 3);
// Find a new frequency for the next time around the loop.
sineFrequency = 300.0f + 2700.0f*uniformRandom();
wv.frequency(sineFrequency);
delay(250); // Let things settle
}
// Return the estimated frequency, based on powers in FFT bins.
// Following from DerekR
// https://forum.pjrc.com/threads/36358-A-New-Accurate-FFT-Interpolator-for-Frequency-Estimation
// " 1) A record of length 1024 samples is windowed with a Hanning window
// 2) The magnitude spectrum is computed from the FFT, and the two (adjacent)
// largest amplitude lines are found. Let the largest be line L, and the
// other be either L+1, of L-1.
// 3) Compute the ratio R of the amplitude of the two largest lines.
// 4) If the amplitude of L+1 is greater than L-1 then
// f = (L + (2-R)/(1+R))*f_sample/1024
// otherwise
// f = (L - (2-R)/(1+R))*f_sample/1024 "
float findFrequency(void) {
float specMax = 0.0f;
uint16_t iiMax = 0;
// Get pointer to data array of powers, float output[512];
float* pPwr = myFFT.getData();
// Find biggest bin
for(int ii=2; ii<510; ii++) {
if (*(pPwr + ii) > specMax) { // Find highest peak of 512
specMax = *(pPwr + ii);
iiMax = ii;
}
}
float vm = sqrtf( *(pPwr + iiMax - 1) );
float vc = sqrtf( *(pPwr + iiMax) );
float vp = sqrtf( *(pPwr + iiMax + 1) );
if(vp > vm) {
float R = vc/vp;
return ( (float32_t)iiMax + (2-R)/(1+R) )*44100.0f/1024.0f;
}
else {
float R = vc/vm;
return ( (float32_t)iiMax - (2-R)/(1+R) )*44100.0f/1024.0f;
}
}
#define FL_ONE 0X3F800000
#define FL_MASK 0X007FFFFF
/* The "Even Quicker" uniform random sample generator from D. E. Knuth and
* H. W. Lewis and described in Chapter 7 of "Numerical Receipes in C",
* 2nd ed, with the comment "this is about as good as any 32-bit linear
* congruential generator, entirely adequate for many uses."
*/
float uniformRandom(void) {
static uint32_t idum = 54321;
union {
uint32_t i32;
float32_t f32;
} uinf;
idum = (uint32_t)1664525 * idum + (uint32_t)1013904223;
uinf.i32 = FL_ONE | (FL_MASK & idum); // Generate random number
return uinf.f32 - 1.0f; // resulting uniform deviate on (0.0, 1.0)
}