цифровая электроника
вычислительная техника
встраиваемые системы

 
» » Быстрое преобразование Фурье для Arduino с помощью EasyFFT



Быстрое преобразование Фурье для Arduino с помощью EasyFFT

Автор: Mike(admin) от 7-08-2020, 09:05

Измерение частоты сигнала может быть сложной задачей, особенно для Arduino, поскольку он имеет меньшую вычислительную мощность. Существуют методы, позволяющие зафиксировать переход через ноль, при котором частота фиксируется путем проверки того, сколько раз сигнал пересекает нулевую линию за заданное время. Такой метод может не работать, если сигнал представляет собой комбинацию различных частот.


Быстрое преобразование Фурье для Arduino с помощью EasyFFT

Код для этого не так-то просто написать, если вы не продвинутый программист. Но будучи написанным, этот код может быть очень полезен для различных проектов, связанных с музыкой, анализом сигналов и т.п. Тем не менее, некоторые умельцы уже реализовали такой код, и выпустили библиотеку под названием EasyFFT.


Чтобы сделать вычисления дискретного преобразования Фурье (ДПФ) более быстрыми, Джеймс Кули и Джон Тьюки разработали алгоритм быстрого преобразования Фурье (БПФ). Этот алгоритм также считается одним из самых важных алгоритмов 20 века. Он разделяет сигнал на нечетную и четную упорядоченную часть, что снижает количество требуемых вычислений. С его помощью общее требуемое комплексное умножение может быть уменьшено до NlogN. что является значительным улучшением. Типичное выражение ДПФ требует комплексного умножения N * N для получения результатов, тогда как БПФ требует только N * logN, это существенное преимущество при большом количестве выборок.


Расчет БПФ принимает значения различных синусов и косинусов несколько раз. Встроенная функция Arduino работает недостаточно быстро и требует много времени, чтобы обеспечить необходимое значение. Это значительно замедляет код (удваивает время для 64 выборок). Чтобы решить эту проблему, в коде значение синуса от 0 до 90 градусов сохраняется как кратное 255. Это избавит от необходимости хранить числа как числа с плавающей запятой, и мы сможем сохранить их как байт, который занимает 1/4 пространство ячейки памяти Arduino. sine_data[] необходимо вставить в начало кода, чтобы объявить его как глобальную переменную.


Помимо sine_data, массив с именем f_peaks[] объявлен как глобальная переменная. После каждого запуска функции БПФ этот массив обновляется. Где f_peaks[0] – это самая доминирующая частота и дальнейшие значения в порядке убывания. Поскольку мы сохранили значение синуса от 0 до 90 градусов, можно вычислить любое значение синуса или косинуса.



byte sine_data [91]= {
0,  
4,    9,    13,   18,   22,   27,   31,   35,   40,   44, 
49,   53,   57,   62,   66,   70,   75,   79,   83,   87, 
91,   96,   100,  104,  108,  112,  116,  120,  124,  127,  
131,  135,  139,  143,  146,  150,  153,  157,  160,  164,  
167,  171,  174,  177,  180,  183,  186,  189,  192,  195,    
198,  201,  204,  206,  209,  211,  214,  216,  219,  221,  
223,  225,  227,  229,  231,  233,  235,  236,  238,  240,  
241,  243,  244,  245,  246,  247,  248,  249,  250,  251,  
252,  253,  253,  254,  254,  254,  255,  255,  255,  255
  };
float f_peaks[5];

БПФ может выполняться только для размера выборки 2, 4, 8, 16, 32, 64 и так далее. Если значение не 2 ^ n, то оно будет принимать меньшую сторону значения. Например, если мы выберем размер выборки 70, тогда будут учитываться только первые 64 выборки и пропущены остальные. Поэтому Всегда рекомендуется иметь размер выборки 2 ^ n. Впрочем, Два числа с плавающей запятой out_r и out_im займут много памяти, и для Arduino nano не будет работать для выборок числом выше 128 из-за нехватки доступной памяти.



unsigned int data[13]={1,2,4,8,16,32,64,128,256,512,1024,2048};
int a,c1,f,o,x;
a=N;  
                                 
      for(int i=0;i<12;i++)                 //calculating the levels
         { if(data[i]<=a){o=i;} }
      
int in_ps[data[o]]={};
float out_r[data[o]]={};
float out_im[data[o]]={};

Дальнейший поток кода выглядит следующим образом. Код генерирует битовый обратный порядок для заданного размера выборки. Вводятся данные, упорядоченные в соответствии с генерированным порядком. Затем выполненяется БПФ. Рассчитывается амплитуда комплексного числа. Пики обнаруживаются и упорядочиваются в порядке убывания. В итоге Результаты можно получить из массива f_peaks[].


Для доступа к другим данным (кроме пиковой частоты) код должен быть изменен, чтобы локальную переменную можно было скопировать в некоторую предопределенную глобальную переменную.


Для проверки работы кода будем обрабатывать звуковой сигнал, полученный с микрофона.


Быстрое преобразование Фурье для Arduino с помощью EasyFFT

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



//---------------------------------------------------------------------------//
byte sine_data [91]=
 {
0,  
4,    9,    13,   18,   22,   27,   31,   35,   40,   44, 
49,   53,   57,   62,   66,   70,   75,   79,   83,   87, 
91,   96,   100,  104,  108,  112,  116,  120,  124,  127,  
131,  135,  139,  143,  146,  150,  153,  157,  160,  164,  
167,  171,  174,  177,  180,  183,  186,  189,  192,  195,       //Paste this at top of program
198,  201,  204,  206,  209,  211,  214,  216,  219,  221,  
223,  225,  227,  229,  231,  233,  235,  236,  238,  240,  
241,  243,  244,  245,  246,  247,  248,  249,  250,  251,  
252,  253,  253,  254,  254,  254,  255,  255,  255,  255
  };
float f_peaks[5]; // top 5 frequencies peaks in descending order
//---------------------------------------------------------------------------//


void setup() 
        {
        Serial.begin(250000);           
        }

        
void loop() {

/*
//example
FFT(data,64,100);        //to get top five value of frequencies of X having 64 sample at 100Hz sampling
Serial.println(f_peaks[0]);
Serial.println(f_peaks[1]);
delay(99999);
*/


/* 
after ruing above FFT(), frequencies available at f_peaks[0],f_peaks[1],f_peaks[2],f_peaks[3],f_peaks[4],
*/           
            }



//-----------------------------FFT Function----------------------------------------------//

float FFT(int in[],byte N,float Frequency)
{
/*
Code to perform FFT on arduino,
setup:
paste sine_data [91] at top of program [global variable], paste FFT function at end of program
Term:
1. in[]     : Data array, 
2. N        : Number of sample (recommended sample size 2,4,8,16,32,64,128...)
3. Frequency: sampling frequency required as input (Hz)

If sample size is not in power of 2 it will be clipped to lower side of number. 
i.e, for 150 number of samples, code will consider first 128 sample, remaining sample  will be omitted.
For Arduino nano, FFT of more than 128 sample not possible due to mamory limitation (64 recomended)
For higher Number of sample may arise Mamory related issue,
Code by ABHILASH
Contact: abhilashpatel121@gmail.com 
Documentation:https://www.instructables.com/member/abhilash_patel/instructables/
*/

unsigned int data[13]={1,2,4,8,16,32,64,128,256,512,1024,2048};
int a,c1,f,o,x;
a=N;  
                                 
      for(int i=0;i<12;i++)                 //calculating the levels
         { if(data[i]<=a){o=i;} }
      
int in_ps[data[o]]={};     //input for sequencing
float out_r[data[o]]={};   //real part of transform
float out_im[data[o]]={};  //imaginory part of transform
           
x=0;  
      for(int b=0;b<o;b++)                     // bit reversal
         {
          c1=data[b];
          f=data[o]/(c1+c1);
                for(int j=0;j<c1;j++)
                    { 
                     x=x+1;
                     in_ps[x]=in_ps[j]+f;
                    }
         }

 
      for(int i=0;i<data[o];i++)            // update input array as per bit reverse order
         {
          if(in_ps[i]<a)
          {out_r[i]=in[in_ps[i]];}
          if(in_ps[i]>a)
          {out_r[i]=in[in_ps[i]-a];}      
         }


int i10,i11,n1;
float e,c,s,tr,ti;

    for(int i=0;i<o;i++)                                    //fft
    {
     i10=data[i];              // overall values of sine/cosine  :
     i11=data[o]/data[i+1];    // loop with similar sine cosine:
     e=360/data[i+1];
     e=0-e;
     n1=0;

          for(int j=0;j<i10;j++)
          {
          c=cosine(e*j);
          s=sine(e*j);    
          n1=j;
          
                for(int k=0;k<i11;k++)
                 {
                 tr=c*out_r[i10+n1]-s*out_im[i10+n1];
                 ti=s*out_r[i10+n1]+c*out_im[i10+n1];
          
                 out_r[n1+i10]=out_r[n1]-tr;
                 out_r[n1]=out_r[n1]+tr;
          
                 out_im[n1+i10]=out_im[n1]-ti;
                 out_im[n1]=out_im[n1]+ti;          
          
                 n1=n1+i10+i10;
                  }       
             }
     }

/*
for(int i=0;i<data[o];i++)
{
Serial.print(out_r[i]);
Serial.print("\t");                                     // un comment to print RAW o/p    
Serial.print(out_im[i]); Serial.println("i");      
}
*/


//---> here onward out_r contains amplitude and our_in conntains frequency (Hz)
    for(int i=0;i<data[o-1];i++)               // getting amplitude from compex number
        {
         out_r[i]=sqrt(out_r[i]*out_r[i]+out_im[i]*out_im[i]); // to  increase the speed delete sqrt
         out_im[i]=i*Frequency/N;
         /*
         Serial.print(out_im[i]); Serial.print("Hz");
         Serial.print("\t");                            // un comment to print freuency bin    
         Serial.println(out_r[i]); 
         */    
        }




x=0;       // peak detection
   for(int i=1;i<data[o-1]-1;i++)
      {
      if(out_r[i]>out_r[i-1] && out_r[i]>out_r[i+1]) 
      {in_ps[x]=i;    //in_ps array used for storage of peak number
      x=x+1;}    
      }


s=0;
c=0;
    for(int i=0;i<x;i++)             // re arraange as per magnitude
    {
        for(int j=c;j<x;j++)
        {
            if(out_r[in_ps[i]]<out_r[in_ps[j]]) 
                {s=in_ps[i];
                in_ps[i]=in_ps[j];
                in_ps[j]=s;}
        }
    c=c+1;
    }



    for(int i=0;i<5;i++)     // updating f_peak array (global variable)with descending order
    {
    f_peaks[i]=out_im[in_ps[i]];
    }



}
    

float sine(int i)
{
  int j=i;
  float out;
  while(j<0){j=j+360;}
  while(j>360){j=j-360;}
  if(j>-1   && j<91){out= sine_data[j];}
  else if(j>90  && j<181){out= sine_data[180-j];}
  else if(j>180 && j<271){out= -sine_data[j-180];}
  else if(j>270 && j<361){out= -sine_data[360-j];}
  return (out/255);
}

float cosine(int i)
{
  int j=i;
  float out;
  while(j<0){j=j+360;}
  while(j>360){j=j-360;}
  if(j>-1   && j<91){out= sine_data[90-j];}
  else if(j>90  && j<181){out= -sine_data[j-90];}
  else if(j>180 && j<271){out= -sine_data[270-j];}
  else if(j>270 && j<361){out= sine_data[j-270];}
  return (out/255);
}



© digitrode.ru


Теги: Arduino, преобразование Фурье




Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Комментарии:

Оставить комментарий