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()
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. 11. Sygnał o dwóch 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
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]));
}

Technologie End of Life i bezpieczeństwo sieci – wyzwania Europy związane z tzw. długiem technologicznym
Najczęstsze błędy firm przy wyborze dostawcy energii i jak ich uniknąć
Fotorezystor, czyli czujnik światła dwojakiego działania. Przykład innowacji w automatyce i elektronice możliwej dzięki technologii fotooporników 



