FFT and ADC in "free running" mode on Mega board. - SOLVED

I have been working on adding a spectrum analyser to a large-ish WS2812b matrix.

I have been looking into a few different libraries and I have gotten it to work some latency issues using analogRead().

I have seen a few examples (with an UNO or NANO) that put the ADC in a "free running mode".

void setup() {
    
    ADCSRA = 0b11100101;      // set ADC to free running mode and set pre-scalar to 32 (0xe5)
    ADMUX = 0b00000000;       // use pin A0 and external voltage reference

}
 
void loop() {
   // ++ Sampling
   for(int i=0; i<SAMPLES; i++)
    {
      while(!(ADCSRA & 0x10));        // wait for ADC to complete current conversion ie ADIF bit set
      ADCSRA = 0b11110101 ;               // clear ADIF bit so that ADC can do next operation (0xf5)
      int value = ADC - 512 ;                 // Read from ADC and subtract DC offset caused value
      vReal[i]= value/8;                      // Copy to bins after compressing
      vImag[i] = 0;                         
    }
    // -- Sampling

When I try this, it appears that I have no data in my bins. So, I am assuming that the ADC registers are setup and accessed differently.
I am currently using A0 as my analog input. Later, I wan't to go to A9.
Can anyone help me out and point me in the right direction?

...I've never actually used FFT.

Your code doesn't show any FFT, but I assume you're not showing your whole program.

I don't know why you're getting "nothing" but FFT won't work right unless the algorithm knows the sample rate.

some latency issues using analogRead().

I'm not sure what you mean, but as I understand the
FFT library it has to pause reading (or maybe ignore the readings in free-run) while it does the calculations.

And of course it takes some time to write to the LEDs and you can't be running FFT while you're doing that.

Sorry that I didn't post the code. It is rather large. I will condense it down and post it.

This line

while(!(ADCSRA & 0x10));        // wait for ADC to complete current conversion ie ADIF bit set

Negates any advantage of using the free running mode.
It is not the free running mode that gives you any increase in sample rate, it just allowed you to do other stuff while you are waiting for the sample acquisition to complete.

To speed up the sample rate make adjustments to the pre scaler register.

Note that a faster sample rate will not reduce the latency, just shift what information is in what bin. The latency is due to your large array. Just how big is it?

Ok, I have condensed my code down to the bare minimum to help illustrate my question. Note, while condensing it, I found a few issues that probably added to the latency issue.

I changed the code to simply print the content of the FFT bins. Here, I am using the analogRead() method.

#define version_string "basic_fft_serial_AR.ino"


#include <arduinoFFT.h>
#define SAMPLES 32            //Must be a power of 2


double vReal[SAMPLES];
double vImag[SAMPLES];
int yvalue;
arduinoFFT FFT = arduinoFFT();                                    // FFT object
 


void setup() {
  Serial.begin(115200);  // 19200


  
//    ADCSRA = 0b11100101;      // set ADC to free running mode and set pre-scalar to 32 (0xe5)
//    ADMUX = 0b00000000;       // use pin A0 and external voltage reference
}

void loop() {
       // ++ Sampling
   for(int i=0; i<SAMPLES; i++)
    {
//      while(!(ADCSRA & 0x10));        // wait for ADC to complete current conversion ie ADIF bit set
//      ADCSRA = 0b11110101 ;               // clear ADIF bit so that ADC can do next operation (0xf5)
//      int value = ADC - 512 ;                 // Read from ADC and subtract DC offset caused value
      int value = analogRead(0) - 512 ;                 // Read from ADC and subtract DC offset caused value
      vReal[i]= value/8;                      // Copy to bins after compressing
      vImag[i] = 0;                         
    }
    // -- Sampling
 
    // ++ FFT
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
    // -- FFT

  
    // ++ send to display according measured value 
    for(int i=0; i<SAMPLES/2; i++){
      Serial.print(vReal[i]);
      Serial.print('\t');

      
     }
     Serial.println();

     // -- send to display according measured value 
}

When I run it and inject a 2500 HZ sine wave into pin A0, I get this on the serial monitor.

1.59	1.13	1.34	2.15	1.43	0.72	0.23	1.47	2.73	41.97	55.28	14.21	3.20	2.20	3.52	2.14	
3.34	1.99	1.46	1.35	2.91	3.33	1.22	1.75	7.76	46.54	49.11	8.95	1.98	3.45	1.81	0.40	
1.91	0.99	2.09	1.99	1.14	1.58	0.94	0.30	3.85	41.32	54.48	12.41	1.10	1.03	1.21	3.40	
3.64	2.81	0.20	2.56	3.54	2.81	0.65	1.49	8.23	47.49	51.14	8.34	1.46	2.38	1.78	3.91	
2.74	3.27	0.77	2.33	1.89	4.04	1.91	2.66	2.87	44.11	54.79	14.44	1.40	1.64	0.85	1.08

This is inline with what I expected. Usable date in bins 9 and 10.

But, when make these changes

void setup() {
  Serial.begin(115200);  // 19200


  
    ADCSRA = 0b11100101;      // set ADC to free running mode and set pre-scalar to 32 (0xe5)
    ADMUX = 0b00000000;       // use pin A0 and external voltage reference
}

void loop() {
       // ++ Sampling
   for(int i=0; i<SAMPLES; i++){
      while(!(ADCSRA & 0x10));        // wait for ADC to complete current conversion ie ADIF bit set
      ADCSRA = 0b11110101 ;               // clear ADIF bit so that ADC can do next operation (0xf5)
      int value = ADC - 512 ;                 // Read from ADC and subtract DC offset caused value
//      int value = analogRead(0) - 512 ;                 // Read from ADC and subtract DC offset caused value
      vReal[i]= value/8;                      // Copy to bins after compressing
      vImag[i] = 0;                         
    }
    // -- Sampling

I get this in the serial monitor (no matter what kind of signal I put on A0)

1059.66	470.28	10.45	3.83	2.01	1.23	0.83	0.58	0.43	0.32	0.24	0.18	0.13	0.10	0.06	0.03	
1059.66	470.28	10.45	3.83	2.01	1.23	0.83	0.58	0.43	0.32	0.24	0.18	0.13	0.10	0.06	0.03	
1059.66	470.28	10.45	3.83	2.01	1.23	0.83	0.58	0.43	0.32	0.24	0.18	0.13	0.10	0.06	0.03	
1059.66	470.28	10.45	3.83	2.01	1.23	0.83	0.58	0.43	0.32	0.24	0.18	0.13	0.10	0.06	0.03	
1059.66	470.28	10.45	3.83	2.01	1.23	0.83	0.58	0.43	0.32	0.24	0.18	0.13	0.10	0.06	0.03
  1. Define int value outside of any function and so make it a global variable.
  2. Drop the "int" bit before the "value" assignment inside the loop function.
  3. Forget the printing of the FFT for a moment and print just the value of the variable "value". Is it changing?

I made "value" a global variable and printed it's value. I print a constant 511 with the sine wave applied.

Is this the correct method to directly access the ADC of a MEGA 2560 board?

ADCSRA = 0b11110101 ;
value = ADC - 512 ;

I made "value" a global variable and printed it's value. I print a constant 511 with the sine wave applied.

So the A/D is not actually running. Or something like a wiring fault is preventing the signal getting to the input. But given that it works with a normal analogue read that looks unlikely.

Have you looked at the examples that came with the FFT library and seen how that code samples the input?

Is this the correct method to directly access the ADC of a MEGA 2560 board?

Well it looks like not. Check with the Mega’s data sheet that the registers have the same names as you are using. Try and understand what each line does and why it is in the code.

The register names are ADCL and ADCH; read ADCL first. Who knows what the compiler does with ADC?

jremington:
The register names are ADCL and ADCH; read ADCL first. Who knows what the compiler does with ADC?

i.e. like this:

  unsigned int reading = ADCL ;
  reading |= ADCH << 8 ;
  reading &= 0x3FF ;

Here is some code that will plot a FFT on a Mega.
Use the serial plotter set to 250000 baud to see the result. Note it uses the octave grouping of the bins which is what you want for a spectrum display.

/*
fft_adc_serial.pde - Modified by Grumpy Mike
guest openmusiclabs.com 7.7.14
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 250kb.   
*/

#define LOG_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#define OCTAVE 1

#include <FFT.h> // include the library

int timer0, k;
byte m , j;

void setup() {
  Serial.begin(250000); // use the serial port
  timer0 = TIMSK0;
  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
    TIMSK0 = 0; // turn off timer0 for lower 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
      m = ADCL; // fetch adc data
      j = ADCH;
      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
    }
    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 in log form
    fft_mag_octave();
    sei(); // enable interrupts
    TIMSK0 = timer0; // restart the timer
    // send out the bins to the plotter
    for (byte i = 0 ; i < FFT_N/2 ; i++) { 
      for(byte j=0; j<3; j++){ // each bin three times
        Serial.println(fft_log_out[i]); // send out the data
      }
    }
      for(byte j=0; j<116; j++) Serial.println(0);
     delay(2000); // so you can see it
  }
}

This uses V3.0 of the FFT library taken from ArduinoFFT - Open Music Labs Wiki

thanks to all. Lots of great info.

Grumpy_Mike

You are the man. excellent info. I have already seen a benefit from the code that you posted.

I need more time to iterate this new knowledge.

Thank you so much.

Again, I want to thank everyone for their help and advice. I have been busy but, I am pleased with the results of this new knowledge. Here is a short video of what we can accomplish.

I still need to dial in the parameters to meet the "eye candy" details that I want but, I am off to a good start.

I was thinking that I should mark this thread as "solved" as you have answered my question.

How to use ADC in free running mode on a Mega board

void setup() {
  timer0 = TIMSK0;
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
}

void loop(){
    TIMSK0 = 0; // turn off timer0 for lower jitter
    cli();  // UDRE interrupt slows this way down on arduino1.0
    for (int i = 0 ; i < FFT_N *2 ; i += 2) { // save 256 samples
      while(!(ADCSRA & 0x10)); // wait for adc to be ready
      ADCSRA = 0xf5; // restart adc
      m = ADCL; // fetch adc data
      j = ADCH;
      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
    }
}

I still need to optimize and incorporate it into the larger display code but, thanks to you guys, I have made great steps.