LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Analizator widma z FFT na STM32 z Cortex-M4

Widma częstotliwościowe prostych sygnałów

Tworzenie aplikacji analizatora widma najlepiej rozpocząć od wykonania czegoś prostego. Ponieważ interesuje nas widmo częstotliwościowe to warto sprawdzić jak wygląda ono dla kilku prostych sygnałów. W tym celu możemy wygenerować cztery sygnały, które zawierają od jednej do czterech składowych harmonicznych:

if(mode == 1){
  for(i=0; i<512; ++i){
    buffer_input[i] = (float32_t) 30*sin(2*PI*freq*i*dt);
  }
}
else if(mode == 2){
  for(i=0; i<512; ++i){
    buffer_input[i] = (float32_t) 20*sin(2*PI*freq*i*dt) +
                                  10*sin(2*PI*2*freq*i*dt);
  }
}
else if(mode == 3){
  for(i=0; i<512; ++i){
    buffer_input[i] = (float32_t) 15*sin(2*PI*freq*i*dt) +
                                  10*sin(2*PI*2*freq*i*dt) +
                                  5*sin(2*PI*3*freq*i*dt);
  }
}
else if(mode == 4){
  for(i=0; i<512; ++i){
    buffer_input[i] = (float32_t) 15*sin(2*PI*freq*i*dt) +
                                  10*sin(2*PI*2*freq*i*dt) +
                                  3*sin(2*PI*3*freq*i*dt) +
                                  2*sin(2*PI*4*freq*i*dt);
  }
}

Generowanych jest 512 próbek sygnału i liczba ta wynika z wymagań narzuconych przez funkcję wyznaczającą widmo i chęci przedstawienia jak najdłuższego przebiegu na wyświetlaczu (wykorzystane będą tylko pierwsze 256). Wartość zmiennej freq w toku działania aplikacji demonstracyjnej jest zmieniana od 1 do 1024, a zmienna dt jest wirtualnym okresem próbkowania, który wynosi 0.001. Dalej można wyświetlić wygenerowane sygnały:

// Usunięcie poprzedniego przebiegu sygnału
LCD_SetForegroundColor(Black);
for(i=0; i<255; ++i){
  x_LCD_DrawLine_2(i + 32, (uint16_t)(buffer_input_copy[i] + 50),
                   i + 33, (uint16_t)(buffer_input_copy[i+1] + 50));
}
// Rysowanie nowego przebiegu sygnału
LCD_SetForegroundColor(Yellow);
for(i=0; i<255; ++i){
  x_LCD_DrawLine_2(i + 32, (uint16_t)(buffer_input[i] + 50),
                   i + 33, (uint16_t)(buffer_input[i+1] + 50));
}

// Wyświetlenie informacji o częstotliwościach składowych harmonicznych sygnału
if(mode == 1){
  sprintf(text,"F=%4d [1]",freq);
  x_LCD_DrawText(32, 100, text, LeftJustify, NormalOrientation);
}
else if(mode == 2){
  sprintf(text,"F1=%4d [1], F2=%4d [1]",freq,freq*2);
  x_LCD_DrawText(32, 100, text, LeftJustify, NormalOrientation);
}
else if(mode == 3){
  sprintf(text,"F1=%4d [1], F2=%4d [1]",freq,freq*2);
  x_LCD_DrawText(32, 100, text, LeftJustify, NormalOrientation);
  sprintf(text,"F3=%4d [1]",freq*3);
  x_LCD_DrawText(32, 110, text, LeftJustify, NormalOrientation);
}
else if(mode == 4){
  sprintf(text,"F1=%4d [1], F2=%4d [1]",freq,freq*2);
  x_LCD_DrawText(32, 100, text, LeftJustify, NormalOrientation);
  sprintf(text,"F3=%4d [1], F4=%4d [1]",freq*3,freq*4);
  x_LCD_DrawText(32, 110, text, LeftJustify, NormalOrientation);
}

// Zachowanie kopii pierwszej połowy próbek aktualnego sygnału
for(i=0; i<256; ++i){
	buffer_input_copy[i] = buffer_input[i];
	buffer_output_mag_copy[i] = buffer_output_mag[i];
}

Mając przygotowany sygnał wykorzystujemy trzy funkcje z biblioteki DSP i wyznaczamy najpierw transformatę Fouriera danego sygnału, a następnie obliczamy wartości modułów dla każdej pary liczb rzeczywistej i urojonej. Na koniec możemy wyszukać składową sygnału (częstotliwość), która ma największą amplitudę i przeskalować tablicę modułów do zakresu nadającego się do wyświetlenia:

// Wyznaczenie transformaty Fouriera
arm_rfft_f32(&S, buffer_input, buffer_output);
// Obliczenie modułów
arm_cmplx_mag_f32(buffer_output, buffer_output_mag, 512);
// Znalezienie składowej harmonicznej sygnału o największej amplitudzie
arm_max_f32(buffer_output_mag, 512, &maxvalue, &maxvalueindex);
// Skalowanie wartości modułów
for(i=0; i<512; ++i){
	buffer_output_mag[i] = 100*buffer_output_mag[i]/maxvalue;
}

Pierwsza funkcja przyjmuje trzy argumenty: wskaźnik do struktury typu arm_rfft_instance_f32 opisującej sposób działania transformaty FFT, wskaźnik do buforu wejściowego z wartościami próbek sygnału oraz wskaźnik do buforu wyjściowego w którym będą zapisane zespolone wartości transformaty sygnału. Ogólną ideę funkcjonowania tej funkcji pokazano na rysunku 9. Ważne jest aby w buforze wejściowym były zapisane rzeczywiste wartości (UWAGA: zostaną one zmodyfikowane po wykonaniu się funkcji), czyli:

{real[0], real[1], real[2], real[3], ..}

Natomiast w buforze wyjściowym, który powinien być dwa razy większy od wejściowego, oprócz wartości rzeczywistych będą znajdować się również wartości urojone:

{real[0], imag[0], Real[1], imag[1], ...}

W przypadku struktury przekazywanej w pierwszym argumencie nie ma potrzeby ręcznego uzupełniania jej, gdyż można do tego celu wykorzystać funkcję arm_rfft_init_f32():

// Wystarczą deklaracje dwóch struktur
arm_rfft_instance_f32 S;
arm_cfft_radix4_instance_f32 S_CFFT;

// oraz inicjalizacja, gdzie:
//  arg#3 – liczba próbek w buforze wejściowym; możliwe wartości to: 128, 512, 2048
//  arg#4 – kierunek transformacji: 0 – prosta, 1 – odwrotna
//  arg#5 – uporządkowanie wartości w buforze wyjściowym: 0 – odwrócone, 1 - normalne
arm_rfft_init_f32(&S, &S_CFFT, 512, 0, 1);

Rys. 9. Idea działania funkcji arm_rfft_f32()

Rys. 9. Idea działania funkcji arm_rfft_f32()

 

Druga funkcja przyjmuje również trzy argumenty: wskaźnik do buforu wejściowego z wartościami par liczb rzeczywistych i urojonych, wskaźnik do buforu wyjściowego do którego zostaną zapisane wartości rzeczywiste oraz ostatni parametr – ilość liczb zespolonych. Funkcja oblicza moduł dla każdej pary wartości liczby rzeczywistej i urojonej zgodnie ze wzorem:

Trzecia funkcja znajduje maksymalną wartość w tablicy buffer_output_mag o długości 512 elementów i zapisuje ją do zmiennej maxvalue, a indeks komórki w jakiej się ona znajduje jest zapisywany w zmiennej maxvalueindex.

 

 Fot. 10. Sygnał o jednej składowej harmonicznej i jego widmo

Fot. 10. Sygnał o jednej składowej harmonicznej i jego widmo

 

Fot. 11. Sygnał o dwóch składowych harmonicznych i jego widmo

Fot. 11. Sygnał o dwóch składowych harmonicznych i jego widmo

 

Fot. 12. Sygnał o trzech składowych harmonicznych i jego widmo

Fot. 12. Sygnał o trzech składowych harmonicznych i jego widmo

 

Fot. 13. Sygnał o czterech składowych harmonicznych i jego widmo

Fot. 13. Sygnał o czterech składowych harmonicznych i jego widmo

 

Na koniec, po przeskalowaniu wartości modułów, można wyświetlić otrzymane wyniki (fotografie 10…13). Wystarczy przedstawić pierwsze 256 elementów tablicy buffer_output_mag, gdyż druga połowa stanowi lustrzane odbicie pierwszej:

// Usunięcie poprzedniego widma
LCD_SetForegroundColor(Black);
for(i=0; i<256; ++i){
  x_LCD_DrawLine_2(i + 32,
                   130 + 100,
                   i + 32,
                   130 + (uint16_t)(100 - (uint16_t)buffer_output_mag_copy[i+1]));
}
// Rysowanie nowego widma
LCD_SetForegroundColor(Green);
for(i=0; i<256; ++i){
  x_LCD_DrawLine_2(i + 32,
                   130 + 100,
                   i + 32,
                   130 + (uint16_t)(100 - (uint16_t)buffer_output_mag[i+1]));
}
Autor: Jan Szemiet