I am planning to extract the amplitude of a frequency range or a specific audio frequency with the help of microphone and Arduino as well as FFT and print it as a number or the average of the amplitudes in the serial port, the existing examples are confusing me.
Also, if I intend to use another processor such as stm 32 or esp 8266, do I need to run ADC manual configuration? If necessary, how do I do it?
I've never actually used FFT but I know a little about it... And I know a lot about audio...
There is probably more than one FFT library (and there is an FHT library which is "similar").
Make sure you are using the examples & documentation from the correct library.
I assume you are using a microphone board. You need a preamp with a biased output.
There is more than one kind of microphone board so make sure you are using one that puts-out the amplified (and biased) audio signal. (Or there are some that put-out digital audio).
Make sure you are getting good amplitude readings first. Run the Analog Read Serial Example (but take-out the delay). With "silence" you should get readings at about half the ADC range (~512 on a regular Arduino) and louder sounds should deviate higher and lower around that bias. Note that since you are sampling a wave the readings will "look random" even with a continuous tone but you should see bigger deviations with louder sounds.
FFT analyzes a number of samples as a batch, sometimes called the sample size or FFT size. For each batch it gives you set of numbers, each representing the amplitude of a frequency-range. The frequency ranges are called "bins". The bins are linear... If there is one bin between 10Hz and 20Hz, there is also one bin between 1000Hz and 1010Hz. So usually they are combined to get octaves (or fractional octaves, etc.). The library might take care of that but I don't know.
From what I've read, FFT on the regular Arduino reads the data and then stops reading while it runs the FFT analysis (and then looping and starting over). That means some data is being skipped. It also has a limited sample rate so you won't get the highest frequencies. (It's good enough for a lighting effect.) You might be able to get-around that with a faster Arduino but the existing library probably still pauses for analysis.
For every 'batch" of samples you'll get a result for each bin so if you want an average you'll have to do that yourself.
Since it normally runs in a "fast loop" you'll probably have to pause after reading & displaying the results long enough to read & mentally comprehend the results.
A spectrum analyzer typically shows the spectrum graphically (usually LEDs with audio) so your brain can keep-up with what's happening. Looking at the raw numbers in real-time would be a mess (unless you have a constant tone).
You'll have to check the library to see what it supports. The FFT itself is "just math" but it has to know the actual sample rate and the library may use some internal registers or assembly/machine language that's specific to a particular processor.
If you are planning any "serious analysis" you'll need an anti-aliasing filter (low pass) on the input to limit the signal to half the sample rate. (You need at-least one sample for the positive half of the waveform and one sample for the negative half.). If the signal is higher than half the sample rate you'll get aliasing (false frequencies). That has to be an analog filter before the signal is sampled, because once it's sampled it's to late! With regular program material the high frequencies are weaker than the lower frequencies and there are many simultaneous frequencies anyway so you can often get-away without the filter.
For testing & debugging Audacity can generate constant test-tones.
P.S.
If you don't need more than 7 frequency bands you may be able to use the MSGEQ7 chip. It takes care of the frequency filtering so you don't need FFT.
You will need a microphone amplifier to bring the signal voltage into the measurable range and possibly, a low pass filter.
No component of the audio signal can have frequency higher than 1/2 of the sampling frequency, or the data can be hopelessly confused by aliasing. Look up the Sampling Theorem and Nyquist Limit to learn more.
For AVR-based Arduinos, the OpenMusicLabs FFT example works well, and automatically sets up the ADC for fast sampling.
Assuming, you are able to do a FFT analysis (calling the FFT in order to see the spectrum), just to recap the differences between time and frequency domains (FFT):
- a peak in volume level (time domain) is for any frequency, the current amplitude level of the audio signal (does not telling you the amplitude on a particular frequency)
- if you convert the time domain signal to FFT (frequency domain) - you can see now of which frequencies (and levels) the signal is
- a peak in volume level (time domain) does not mean a peak in frequency domain. And you can see peaks in FFT (e.g. a strong particular frequency) without to see volume peaks in (time domain) signal.
And when you do an FFT: there is always a "Window": you capture several samples, e.g. 1024 and you call the FFT to analyze the spectrum. But a "real correct" FFT is over endless samples (per definition and math). But if you do in chunks ("Windows") - there are aliases for the FFT result: You have to choose a "Window" function for the FFT, e.g. rectangular (very bad) or Chebyshev Window. Otherwise, you get wrong results and artefacts on the FFT results.
FFT with Chebyshev Window
BTW: you do not need a low pass filter (anti-aliasing). You have to bear in mind just the Nyquist (or Shannon) theorem: if you want to get signal with frequency F, the sample rate must be 2*F.
if you sample too slow - it is already such a low pass filter (you get only the lower frequencies).
But the FFT can be "tricky" if you do not consider the "Window-Effect" and not choosing the right "Window Function" for the FFT.
BTW: how do you do the FFT?
I have used the ARM DSP libraries and it works pretty well (and fast).
BTW2:
If your FFT process would really stop receiving the audio (just one or the other done), you do not analyze every "Window" (instead: you stall the sound input) - the results can be very inaccurate (not a smooth audio signal, even the Window is needed, but with stalled audio in between - a big jump in the spectrum possible).
I would use a fast external ADC, like the MAX11168, then the esp would work well to do the FFT, it must just have enough memory to store the window of data points and fft you wish to process. For example you may choose to use 1024 int data points, then you also need 1024 ints for the fft. As the data set is larger, the microcontroller will be slower, so you have to be wise.
Thank you for the complete explanation and the time you took
I assume you are using a microphone board. You need a preamp with a biased output.
Yes, I have made a preamplifier circuit with the help of two transistors and a condenser microphone, and everything is perfect in the oscilloscope.
If you are planning any "serious analysis" you'll need an anti-aliasing filter (low pass) on the input to limit the signal to half the sample rate.
Yes, there is also a low-pass filter before the analog-to-digital converter of the microcontroller, in fact I need a limited frequency range (0-5000 Hz), I am mostly focused on the bass frequency range, but I want the range to be in my hands. Higher frequencies are removed by the physical filter.
If you don't need more than 7 frequency bands you may be able to use the MSGEQ7 chip. It takes care of the frequency filtering so you don't need FFT.
I would be very happy if these problems could be solved with the help of a chip. In our country, chips are either rare or very expensive, so I cannot use any advanced hardware chip.
Your explanation was very complete and useful, as I said before, I don't need the entire frequency range, so I don't think there is such a limit, can you provide me with a simple code? which will show me the amplitude on a certain frequency
Using peripheral chips is not cost-effective for me
Somewhere was another forum thread with using an external chip for FFT.
Something like this MSGEQ7:
MSGEQ7 FFT chip
If you have an ARM processor and you can add ARM DSP library (part of CMSIS), best with enabling the HW FPU - you can do FFT in SW.
ARM DSP
Here, my FFT.c - see the function int32_t FFT_Filter(int ch), from an STM32F769I-Discovery board (with LCD, display FFT on LCD):
FFT.c (20.7 KB)
Important to know is:
the ARM DSP uses fix point math (see this q31_t as type). You have to convert sample values (audio PCM values) into this type before you call the FFT via arm_cfft_f32()
Here the most important part of the code:
//prepare buffer for FFT: as 32bit signed, single channel
while (bufLen--)
{
*inBuf = (q31_t)(*sampleBuf++) << 16; //scale up into highest value range for q31
sampleBuf++; //skip the other stereo channel
DC_BlockerSample(inBuf++, ch); //remove DC from samples
}
inBuf = sFFTBuf;
//convert as Q31 (as int32_t) to float for FFT
arm_q31_to_float(inBuf, input_f32, FFT_SAMPLES);
{
//convert the float buffer into complex value buffer (store just real part, the other imaginary part remains 0)
for (i = 0, j = 0; i < FFT_SIZE; i++)
{
if (sHanningWindow)
{
input_c32[2 * i] = input_f32[i] * sHanningWindowCoeff[j];
if (j != 127)
{
if (j < 127)
j++;
else
j--;
}
}
else
input_c32[2 * i] = input_f32[i];
input_c32[2 * i + 1] = 0.0f;
}
/* Process the data through the CFFT/CIFFT module - depends on FFT size */
#if FFT_SIZE == 1024
arm_cfft_f32(&arm_cfft_sR_f32_len1024, input_c32, ifftFlag, doBitReverse);
#elif FFT_SIZE == 512
arm_cfft_f32(&arm_cfft_sR_f32_len512, input_c32, ifftFlag, doBitReverse);
#elif FFT_SIZE == 256
arm_cfft_f32(&arm_cfft_sR_f32_len256, input_c32, ifftFlag, doBitReverse);
#else
#warning "Error: NOT supported FFT size"
#endif
/* Process the data through the Complex Magnitude Module for calculating the magnitude at each bin */
arm_cmplx_mag_f32(input_c32, fftResult, FFT_SIZE);
{
//correct low frequency bins
int i;
for (i = 0; i < 8; i++)
{
fftResult[i] = fftResult[i] * sHanningWindowCoeff[i + 120-1];
}
}
fftResultReady = 0;
/* Calculates maxValue and returns corresponding BIN value */
arm_max_f32(&fftResult[skipFFTBins], FFT_SIZE / 2 - skipFFTBins, &sFFTmaxValue, &testIndex);
arm_min_f32(&fftResult[skipFFTBins], FFT_SIZE / 2 - skipFFTBins, &sFFTminValue, &testIndex);
BTW: I use a Hanning Window (see the coefficients in code).
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.