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.
122 lines
4.4 KiB
122 lines
4.4 KiB
4 years ago
|
/* 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)
|
||
|
}
|