1

I am a beginner in Arduino, coding, and electrical engineering (learning on my own time) and am working on creating an EEG. I've got the hardware down and have managed to get numerical data output from the analog (A0) of Arduino. The next step is transforming these numbers to outputting dominant frequency and I have managed to find a code that uses FFT to find the dominant frequency from the input of A0. The following is the code:

#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

}

The problem I have is with the line microseconds = micros(). The comment state that this will overflow after 70 minutes and after some days of research, I sorta understand what is meant by this. However, I need this program to run over 9 hours, so I can log the dominant frequency of the brainwave throughout the night. Is that possible?

Chris Do
  • 11
  • 5

2 Answers2

3

Chris Do, there is indeed a rollover problem when micros is used that way. When that happens, the samples are taken at full speed without interval. That will create bad data for the FFT.
The solution is in the Blink Without Delay example. The magic to avoid a rollover problem is the substraction with two unsigned long values.

The arduinoFFT library is in the Library Manager of the Arduino IDE.
The example "FFT_03.ino" uses micros in the wrong way.

The next sketch shows how to avoid the rollover problem:

// Use micros() without rollover problem for a fixed sample rate
// for a certain amount of samples.

#define SAMPLES 128

unsigned long previousMicros;
const unsigned long intervalMicros = 1000UL;   // 1000 Hz is 1000 us

float vReal[SAMPLES];
float vImag[SAMPLES];

void setup() {
}

void loop() {

  previousMicros = micros();  // set the previousMicros just before entering the for-statement
  for(int i=0; i<SAMPLES; i++) {

    // A while-statement to wait.
    // Note that the ';' at the end means 'do nothing'.
    while( micros() - previousMicros < intervalMicros);

    // The 'previousMicros' is increased with a fixed number, for a fixed sample rate.
    previousMicros += intervalMicros;  // for next interval

    vReal[i] = float(analogRead(0));
    vImag[i] = 0.0;
  }

  delay(1000);
}

Take the current value of micros and substract the previous value of micros. The result of that substraction is a valid number, even during a rollover. It is the only way that always works.

The line to calculate the sample period has too many things in it that can go wrong. These lines:

unsigned int sampling_period_us;
sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));

In my example, I set the intervalMicros to a certain number. Could you do that as well? When everything works, you can try to calculate it.

Added: The answer by Edgar Bonet is exactly the same. That is no surprise because there is no other way.

Jot
  • 3,276
  • 1
  • 14
  • 21
2

As long as you never compare timestamps, the rollover is not an issue. The idiom you used:

while (micros() < previous_timestamp + period) ...

is comparing two timestamps. This is incorrect, and it fails whenever micros() rolls over. However, subtracting timestamps to get a duration does work fine across rollover events. And comparing durations does always work. So the correct idiom is:

while (micros() - previous_timestamp < period) ...

Note also that the way you update microseconds is incorrect. You are incrementing it by the period you want plus the time taken by the CPU to execute some of the code in the loop. You should instead increment it by the requested period and no more. Here is my take at what you are trying to accomplish:

/* SAMPLING */
microseconds = micros();  // record initial time
for (int i = 0; i < SAMPLES; i++) {
    while (micros() - microseconds < sampling_period_us)
        ;  // wait for the right time to take the sample
    microseconds += sampling_period_us;  // update as per the period
    vReal[i] = analogRead(0);
    vImag[i] = 0;
}
Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81