Getting FFT arduino code to run over 9+ hours, utilizing micros() - Arduino Stack Exchange most recent 30 from arduino.stackexchange.com 2019-06-18T11:31:36Z https://arduino.stackexchange.com/feeds/question/56219 http://www.creativecommons.org/licenses/by-sa/3.0/rdf https://arduino.stackexchange.com/q/56219 0 Getting FFT arduino code to run over 9+ hours, utilizing micros() Chris Do https://arduino.stackexchange.com/users/49643 2018-09-19T04:47:09Z 2018-09-19T08:28:51Z <p>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: </p> <pre><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&lt;SAMPLES; i++) { microseconds = micros(); //Overflows after around 70 minutes! vReal[i] = analogRead(0); vImag[i] = 0; while(micros() &lt; (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&lt;(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 </code></pre> <p>}</p> <p>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? </p> https://arduino.stackexchange.com/questions/56219/getting-fft-arduino-code-to-run-over-9-hours-utilizing-micros/56222#56222 2 Answer by Jot for Getting FFT arduino code to run over 9+ hours, utilizing micros() Jot https://arduino.stackexchange.com/users/30198 2018-09-19T08:10:09Z 2018-09-19T08:28:51Z <p>Chris Do, there is indeed a rollover problem when <code>micros</code> is used that way. When that happens, the samples are taken at full speed without interval. That will create bad data for the FFT.<br> The solution is in the <a href="https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay" rel="nofollow noreferrer">Blink Without Delay</a> example. The magic to avoid a rollover problem is the substraction with two <code>unsigned long</code> values.</p> <p>The <a href="https://github.com/kosme/arduinoFFT" rel="nofollow noreferrer">arduinoFFT library</a> is in the Library Manager of the Arduino IDE.<br> The example "<a href="https://github.com/kosme/arduinoFFT/blob/master/Examples/FFT_03/FFT_03.ino" rel="nofollow noreferrer">FFT_03.ino</a>" uses <code>micros</code> in the wrong way.</p> <p>The next sketch shows how to avoid the rollover problem:</p> <pre><code>// 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&lt;SAMPLES; i++) { // A while-statement to wait. // Note that the ';' at the end means 'do nothing'. while( micros() - previousMicros &lt; 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); } </code></pre> <p>Take the current value of <code>micros</code> 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.</p> <p>The line to calculate the sample period has too many things in it that can go wrong. These lines:</p> <pre><code>unsigned int sampling_period_us; sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY)); </code></pre> <p>In my example, I set the <code>intervalMicros</code> to a certain number. Could you do that as well? When everything works, you can try to calculate it.</p> <p>Added: The answer by Edgar Bonet is exactly the same. That is no surprise because there is no other way.</p> https://arduino.stackexchange.com/questions/56219/getting-fft-arduino-code-to-run-over-9-hours-utilizing-micros/56224#56224 3 Answer by Edgar Bonet for Getting FFT arduino code to run over 9+ hours, utilizing micros() Edgar Bonet https://arduino.stackexchange.com/users/7508 2018-09-19T08:11:19Z 2018-09-19T08:11:19Z <p>As long as you never compare timestamps, <a href="https://arduino.stackexchange.com/q/12587">the rollover is not an issue</a>. The idiom you used:</p> <pre><code>while (micros() &lt; previous_timestamp + period) ... </code></pre> <p>is comparing two timestamps. This is incorrect, and it fails whenever <code>micros()</code> 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:</p> <pre><code>while (micros() - previous_timestamp &lt; period) ... </code></pre> <p>Note also that the way you update <code>microseconds</code> is incorrect. You are incrementing it by the period you want <strong>plus</strong> 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:</p> <pre><code>/* SAMPLING */ microseconds = micros(); // record initial time for (int i = 0; i &lt; SAMPLES; i++) { while (micros() - microseconds &lt; 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; } </code></pre>