Use ADC free running mode to increase sampling frequency for FFT

Hi all, I’m working on a spectrum analyzer that requires me to apply FFT to an audio signal. I’ve already finished the hardware part, and I’m looking for some guidance on how to alter this code that I found / any good resources for beginners so that I can display the spectrum analyzer on an 8x8 LED matrix.

The code I found is pasted below: it uses the analogRead() function, which has a max sampling frequency of appx 10kHz, I believe. According to Nyquist’s theorem, I need to set the ADC prescalar to 32 in order to get a sampling frequency of 38.46kHz.

My question is, how can I override the analogRead function and setup the ADC? What is the point of the variables ‘unsigned int sampling_period_us’ and ‘unsigned long microseconds’? Also, how would I “scale down” the results from the FFT so that it will fit on the 8x8 LED matrix?

#include "arduinoFFT.h"
 
#define SAMPLES 128             //Must be a power of 2
#define SAMPLING_FREQUENCY 1000 //Hz, must be less than 10000 due to ADC
 
arduinoFFT FFT = arduinoFFT();
 
unsigned int sampling_period_us;
unsigned long microseconds;
 
double vReal[SAMPLES];
double vImag[SAMPLES];
 
void setup() {
    Serial.begin(115200);
 
    sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
}
 
void loop() {
   
    /*SAMPLING*/
    for(int i=0; i<SAMPLES; i++)
    {
        microseconds = micros();    //Overflows after around 70 minutes!
     
        vReal[i] = analogRead(0);
        vImag[i] = 0;
     
        while(micros() < (microseconds + sampling_period_us)){
        }
    }
 
    /*FFT*/
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
    double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
 
    /*PRINT RESULTS*/
    //Serial.println(peak);     //Print out what frequency is the most dominant.
 
    for(int i=0; i<(SAMPLES/2); i++)
    {
        /*View all these three lines in serial terminal to see which frequencies has which amplitudes*/
         
        //Serial.print((i * 1.0 * SAMPLING_FREQUENCY) / SAMPLES, 1);
        //Serial.print(" ");
        Serial.println(vReal[i], 1);    //View only this line in serial plotter to visualize the bins
    }
 
    //delay(1000);  //Repeat the process every second OR:
    while(1);       //Run code once
}

Well the first thing I notice is the sampling timing loop is broken, it doesn’t sample accurately.

It can be fixed:

    /*SAMPLING*/
    microseconds = micros();  // get initial time
    for(int i=0; i<SAMPLES; i++)
    {
        vReal[i] = analogRead(0);
        vImag[i] = 0;
     
        while(micros() - microseconds < sampling_period_us)) // correct test, no wrap-around issues
        {}
        microseconds += sampling_period_us;  // progress the target time accurately
    }

The point of the variables is to track time accurately, so the samples are evenly spaced (essential for a
meaningful FFT result). The period is the time between sample points.

If you change the ADC prescaler you can still use analogRead(), it doesn’t know anything about timing, it
just sets a conversion going and waits for the result to be flagged as ready.

MarkT:
If you change the ADC prescaler you can still use analogRead(), it doesn't know anything about timing, it
just sets a conversion going and waits for the result to be flagged as ready.

Thanks for the reply, Mark. Are you saying that if I change the ADC prescalar to 32 I will still get a sampling rate of 38.46kHz, and can still use analogRead() to fill the vReal array? I read somewhere that the whole point of changing the ADC prescalar is so that the FFT can read higher frequencies, since the analogRead function has a sampling rate of 9600kHz or something, which means the frequency range only goes up to 4800kHz.