Signal Processing / Real-time FFT

This section requires mastering the concepts of sampling and DSP.

Fast Fourier Transform vs Fourier transform

The interest in the Fourier transform lies in its ability to analyze and manipulate signals in various domains. By breaking down a signal into its constituent frequencies, it enables analysis and processing in various fields such as signal processing, communications, and image processing.

The Fast Fourier Transform (FFT) is an algorithm that computes the Discrete Fourier Transform (DFT) of a sampled signal.

FFT transforms a signal from its time (or spatial - for images) domain into the frequency domain. It accomplishes this by recursively dividing the DFT into smaller DFTs, significantly reducing computation time compared to the standard DFT calculation, making it a fundamental tool for spectral analysis and signal processing applications.

Fourier Transform calculation

The Fourier transform of a function \(f(t)\) is given by the following formula:

\[\hat{f}(\omega) = \mathcal{F}\{f(t)\}(\omega) = \int_{-\infty}^{\infty} f(t) \cdot e^{-i\omega t} \, dt\]

Fourier transforms have a lot of remarkable properties. These properties are not addressed in this document, but you can find a lot of ressources on the Internet.

DFT calculation

Based on this sampling, the DFT can be calculated by integrating

\[X[k] = \sum_{n=0}^{N-1} x[n] \cdot e^{-i\frac{2\pi}{N} kn}, \quad k = 0, 1, \ldots, N-1\]

Sampling and FFT algorithm

On cherche ici à calculer le spectre d’un signal analogique à l’aide d’une carte Nucléo.

Il est alors nécessaire d’utiliser une acquisition du signal d’entrée analogique, à un rythme régulier, compatible avec le critère de Shannon. La programmation de ces cartes par le biais du système d’exploitation embarqué MBED limite énormément les capacité d’échantillonnage (temps de conversion autour de 25us). Ici, on utilise un Ticker qui appelle la fonction sample à intervalle régulier.

Les éléments convertis, sur une période donnée, sont alors stockées dans un tableau. Ici, on récupère 256 éléments, dans le tableau Input à un rythme de 40us chacun (fréquence de 25 kHz). Pour optimiser les calculs, il est préférable d’utiliser un nombre d’éléments qui est une puissance de 2, l’ensemble des données et des éléments séquentiels d’un microcontroleur fonctionnant en binaire.

Vient ensuite la partie calculatoire où ici on utilise un algorithme particulier de calcul de la transformée de Fourier appelé FFT (Fast Fourier Transform). Cet algorithme est déjà implémenté dans la bibliothèque dsp.h de MBED. Il se fait dans la partie principale du code mais n’est exécuté que lorsque le sémaphore trig n’est pas bloqué par l’échantillonnage des N valeurs.

Enfin, l’affichage se fait par l’intermédiaire d’un convertisseur numérique-analogique à un rythme de 1 échantillon toutes les 10us environ. Une première impulsion à 3.3V de durée 20us permet de synchroniser l’affichage. Puis les différentes valeurs du spectre sont régulièrement converties par le CNA.

FFT with Nucleo

http://lense.institutoptique.fr/nucleo-obtenir-le-spectre-dun-signal-en-temps-reel-4/

Algorithm

Creating an MBED6 project

CMSIS-DSP library

Adding a library by right-click on the active project and select

../_images/keil_add_mbed_library.png

In the popup window, paste the GitHub link : https://github.com/ARM-software/CMSIS-DSP then click Next

../_images/keil_add_mbed_library_link.png

Select the version of the library then click Finish.

../_images/keil_add_mbed_library_version.png

The library is adding in the project.

../_images/keil_add_mbed_library_dl.png

Adding a .mbedignore file

Adding a new file by right-click on the active project and select New File.

Create a .mbedignore file.

../_images/keil_add_mbed_library_mbedignore.png
cmsis-dsp/Examples/*
cmsis-dsp/PythonWrapper/*
cmsis-dsp/Scripts/*
cmsis-dsp/Testing/*
cmsis-dsp/ComputeLibrary/*

cmsis-dsp/Source/BasicMathFunctions/BasicMathFunctions.c
cmsis-dsp/Source/BasicMathFunctions/BasicMathFunctionsF16.c
cmsis-dsp/Source/BayesFunctions/BayesFunctions.c
cmsis-dsp/Source/BayesFunctions/BayesFunctionsF16.c
cmsis-dsp/Source/CommonTables/CommonTables.c
cmsis-dsp/Source/CommonTables/CommonTablesF16.c
cmsis-dsp/Source/ComplexMathFunctions/ComplexMathFunctions.c
cmsis-dsp/Source/ComplexMathFunctions/ComplexMathFunctionsF16.c
cmsis-dsp/Source/ControllerFunctions/ControllerFunctions.c
cmsis-dsp/Source/DistanceFunctions/DistanceFunctions.c
cmsis-dsp/Source/DistanceFunctions/DistanceFunctionsF16.c
cmsis-dsp/Source/FastMathFunctions/FastMathFunctions.c
cmsis-dsp/Source/FastMathFunctions/FastMathFunctionsF16.c
cmsis-dsp/Source/FilteringFunctions/FilteringFunctions.c
cmsis-dsp/Source/FilteringFunctions/FilteringFunctionsF16.c
cmsis-dsp/Source/InterpolationFunctions/InterpolationFunctions.c
cmsis-dsp/Source/InterpolationFunctions/InterpolationFunctionsF16.c
cmsis-dsp/Source/MatrixFunctions/MatrixFunctions.c
cmsis-dsp/Source/MatrixFunctions/MatrixFunctionsF16.c
cmsis-dsp/Source/QuaternionMathFunctions/QuaternionMathFunctions.c
cmsis-dsp/Source/StatisticsFunctions/StatisticsFunctions.c
cmsis-dsp/Source/StatisticsFunctions/StatisticsFunctionsF16.c
cmsis-dsp/Source/SVMFunctions/SVMFunctions.c
cmsis-dsp/Source/SVMFunctions/SVMFunctionsF16.c
cmsis-dsp/Source/TransformFunctions/TransformFunctions.c
cmsis-dsp/Source/TransformFunctions/TransformFunctionsF16.c
cmsis-dsp/Source/WindowFunctions/WindowFunctions.c

Caution

Be careful to let a blank line at the end of this file.

Main program

Sampling application

 1#include "mbed.h"
 2
 3DigitalOut myled(D3);
 4AnalogIn   myADC(A1);
 5AnalogOut  myDAC(D13);
 6Ticker     timer;
 7
 8void sample(){
 9        myled = !myled;
10        float val_in = myADC.read();
11        myDAC.write(val_in);
12}
13
14int main() {
15        timer.attach(&sample, 40us);    //40us 25KHz sampling rate
16
17        while(1) {
18                thread_sleep_for(10);
19        }
20}

Final code

 1#include "mbed.h"
 2#include "arm_math.h"
 3#include "arm_common_tables.h"
 4#include "arm_const_structs.h"
 5
 6#define SAMPLES                 512
 7/* 256 real party and 256 imaginary parts */
 8#define FFT_SIZE                SAMPLES / 2
 9/* FFT size is always the same size as we have samples, so 256 in our case */
10#define OUTPUT_GAIN             10.0
11/* Gain on the output values - for better display */
12
13float32_t Input[SAMPLES];
14float32_t Output[FFT_SIZE];
15
16bool      trig=0;         // sampling blocking semaphore
17int       indice = 0;
18
19DigitalOut myled(D3);
20AnalogIn   myADC(A1);
21AnalogOut  myDAC(D13);
22Ticker     timer;
23
24void sample(){
25        myled = 1;
26        if(indice < SAMPLES){
27                Input[indice] = myADC.read() - 0.5f;
28                // Real part NB removing DC offset
29                Input[indice + 1] = 0;
30                // Imaginary Part set to zero
31                indice += 2;
32        }
33        else{ trig = 0; }
34        myled = 0;
35}
36
37int main() {
38        float maxValue;            // Max FFT value is stored here
39        uint32_t maxIndex;         // Index in Output array where max value is
40
41        while(1) {
42                if(trig == 0){
43                        timer.detach();
44                        // arm_cfft_sR_f32_lenXXX, where XXX is the samples number, here 256
45                        arm_cfft_f32(&arm_cfft_sR_f32_len256, Input, 0, 1);
46
47                        // FFT calculation and storage of the magnitude of the complex FFT values in the Output array
48                        arm_cmplx_mag_f32(Input, Output, FFT_SIZE);
49                        Output[0] = 0;
50
51                        // Analog display of the FFT
52                        myDAC=1.0f;     // Sync pulse
53                        wait_us(10);
54                        myDAC=0.0f;
55                        // Display all the values
56                        for(int i=0; i < FFT_SIZE; i++){
57                                myDAC.write(OUTPUT_GAIN * Output[i]/FFT_SIZE);
58                        }
59                        myDAC=0.0f;
60
61                        // Restart sampling
62                        trig = 1;
63                        indice = 0;
64                        timer.attach(&sample,40us);    //40us 25KHz sampling rate
65                }
66        }
67}