Получение кода FFT arduino для работы более 9 часов с использованием micros()

Я новичок в Arduino, программировании и электротехнике (учусь в свободное время) и работаю над созданием ЭЭГ. Я отключил аппаратное обеспечение и сумел получить числовые данные от аналога (A0) Arduino. Следующим шагом является преобразование этих чисел для вывода доминирующей частоты, и мне удалось найти код, который использует БПФ для определения доминирующей частоты на входе A0. Ниже приведен код:

#include "arduinoFFT.h"

#define SAMPLES 128             //Должна быть степенью 2
#define SAMPLING_FREQUENCY 1000 //Гц, должно быть меньше 10000 из-за АЦП

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();    //Переполняется примерно через 70 минут!

    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);     //Распечатываем, какая частота является наиболее доминирующей.

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); //Просмотр только этой строки в последовательном плоттере для визуализации ячеек
}

delay(1000);  //Повторение процесса каждую секунду ИЛИ:
//пока(1); //Запускаем код один раз

У меня проблема со строкой microсекунды = micros(). В комментарии говорится, что это переполнится через 70 минут, и после нескольких дней исследований я вроде как понимаю, что под этим подразумевается. Однако мне нужно, чтобы эта программа работала более 9 часов, чтобы я мог регистрировать доминирующую частоту мозговых волн в течение ночи. Возможно ли это?

, 👍1

Обсуждение

Привет, интересный проект. Сколько памяти нужно?, @Mikael Patel

Извините, но я не уверен, что вы имеете в виду. Если я правильно понимаю, я пытаюсь брать доминирующую частоту каждые десять минут в течение ~9 часов. Это означало бы, что выход COM будет отображать 54 различных данных или запоминать их. (6 данных каждый час * 9 часов)., @Chris Do


2 ответа


3

Крис До, при таком использовании micros действительно возникает проблема опрокидывания. В этом случае пробы отбираются на полной скорости без перерыва. Это создаст неверные данные для БПФ.
Решение находится в примере Мигание без задержки. Волшебным способом избежать проблемы опрокидывания является вычитание двух значений unsigned long.

библиотека arduinoFFT находится в диспетчере библиотек Arduino IDE.
В примере "FFT_03.ino" используется micros неправильным образом.

Следующий скетч показывает, как избежать проблемы опрокидывания:

// Используйте micros() без проблем с опрокидыванием для фиксированной частоты дискретизации
// для определенного количества образцов.

#define SAMPLES 128

unsigned long previousMicros;
const unsigned long intervalMicros = 1000UL;   // 1000 Гц — это 1000 мкс

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

void setup() {
}

void loop() {

  previousMicros = micros();  // устанавливаем предыдущийМикрос непосредственно перед вводом оператора for
  for(int i=0; i<SAMPLES; i++) {

    // Оператор while для ожидания.
    // Обратите внимание, что ';' в конце означает «ничего не делать».
    while( micros() - previousMicros < intervalMicros);

    // «previousMicros» увеличивается на фиксированное число для фиксированной частоты дискретизации.
    previousMicros += intervalMicros;  // для следующего интервала

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

  delay(1000);
}

Возьмите текущее значение micros и вычтите предыдущее значение micros. Результатом этого вычитания является допустимое число даже во время ролловера. Это единственный способ, который всегда работает.

В строке расчета периода выборки слишком много вещей, которые могут пойти не так. Эти строки:

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

В моем примере я установил для intervalMicros определенное число. Не могли бы вы сделать то же самое? Когда все заработает, можно попробовать посчитать.

Добавлено: ответ Эдгара Боне точно такой же. Это неудивительно, потому что другого пути нет.

,

Re “The example "FFT\_03.ino" uses micros in the wrong way”: Я не знал, что OP скопировал плохой пример из самой библиотеки. Хороший улов! Я только что отправил [запрос на извлечение](https://github.com/kosme/arduinoFFT/pull/21) автору arduinoFFT. Надеюсь, он исправит это в upstream., @Edgar Bonet


2

Пока вы никогда не сравниваете временные метки, перенос не является проблема. Идиома, которую вы использовали:

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

сравнивает две временные метки. Это неверно, и это терпит неудачу всякий раз, когда micros() переворачивается. Однако вычитание временных меток для получения продолжительности отлично работает в событиях опрокидывания. И сравнение длительности делает всегда работайте. Итак, правильная идиома:

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

Обратите также внимание, что способ обновления микросекунд неверен. Ты увеличивая его на нужный период плюс на время, затраченное ЦП для выполнения части кода в цикле. Вместо этого вам следует увеличить его на запрошенный период и не более. Вот мой взгляд на чего вы пытаетесь достичь:

/* SAMPLING */
microseconds = micros();  // записываем начальное время
for (int i = 0; i < SAMPLES; i++) {
    while (micros() - microseconds < sampling_period_us)
        ;  // ждем подходящего момента для взятия образца
    microseconds += sampling_period_us;  // обновление по периоду
    vReal[i] = analogRead(0);
    vImag[i] = 0;
}
,

Эдгар Бонет и Джот, я очень ценю ваши ответы. Я думаю, что я наткнулся на тему ролловера во время исследования и увидел решение, но так и не понял его до сих пор. Просто для ясности, однако, проблема с использованием micros заключается в том, что он будет ролловером (другими словами, примерно через 4,26*10^9 микросекунд он вернется к 0 и снова начнет счет вверх). Решение состоит в том, чтобы использовать интервал, потому что численное значение theinterval будет тем же самым, даже если micros() ролловера. Это правильно? Я попробую этот подход. Большое спасибо за вашу помощь!, @Chris Do

@ChrisDo: Это верно. См. также [переполнение millis() ... плохая вещь?](https://www.gammon.com.au/millis), @Edgar Bonet

Привет, Эдгар, после нескольких дней экспериментов с кодом, который ты предоставил, кажется, что вывод FFT становится очень спорадическим через некоторое время. По сравнению с оригинальным кодом, который я нашел, вывод этого оригинального кода будет примерно таким: 23, 22, 23, 24, 25, 26, 30, 27, 25. С помощью предоставленного вами кода будет выведено что-то вроде 23, 22, 65, 102, 300, 450, 23, 34, 46, 89. Вы случайно не знаете, почему так происходит?, @Chris Do

@ChrisDo: Это не связано с проблемой ролловера. Понятия не имею, почему ваши данные выглядят так., @Edgar Bonet