0

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?

  • Hi, interesting project. How much memory is necessary? – Mikael Patel Sep 19 '18 at 7:04
  • I apologize, but I'm not sure what you mean by that. If I understand correctly, I am attempting to take the dominant frequency every ten minutes within the course of ~9 hours. That would mean the COM output would be displaying 54 different data or memorizing it. (6 data every hour * 9hours). – Chris Do Sep 19 '18 at 11:13
3

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 and jot, I greatly appreciate your answers. I believe I did come over the rollover topic while researching and saw the solution but never comprehended until now. Just to be clear however, the problem with using micros is that it will rollover (In other words, after approximately 4.26*10^9 microseconds, it will rollover back to 0 and restart counting upward again). The solution is to use interval because the numerical value of theinterval will be the same even if micros() rollover. Is that correct? I will try this approach. Thank you very much for your help! – Chris Do Sep 19 '18 at 11:32
  • @ChrisDo: That's correct. See also millis() overflow ... a bad thing? – Edgar Bonet Sep 19 '18 at 11:53
  • Hey Edgar, after experimenting with the code that you provided for a few days, it seems that the output of the FFT gets very sporadic after a while. Compared to the original code that I found, the output of that original code would be something like 23, 22, 23, 24, 25, 26, 30, 27, 25. With the code that you have provided, it would output something like 23, 22, 65, 102, 300, 450, 23, 34, 46, 89. By any chance do you happen to know why this would be? – Chris Do Sep 29 '18 at 5:34
  • @ChrisDo: This is unrelated to the rollover issue. I have no idea why your data looks like that. – Edgar Bonet Sep 29 '18 at 9:22
2

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.

  • Re “The example "FFT_03.ino" uses micros in the wrong way”: I wasn't aware that the OP copied the bad example from the library itself. Good catch! I just submitted a pull request to the author of arduinoFFT. Hopefully he will get this fixed upstream. – Edgar Bonet Sep 19 '18 at 10:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.