Comment obtenir le spectre d’un signal en temps réel ?

NIVEAU 4

Objectifs

  • Calculer la FFT d’un signal analogique à l’aide d’une carte Nucléo
  • Afficher la FFT à l’aide d’une sortie analogique

Pré-requis

Un exemple de projet complet est disponible à l’adresse suivante : https://os.mbed.com/users/villemejane/code/IeTI_FFT/

Coeur DSP sur Nucléo

DSP signifie Digital Signal Processor ou processeur de traitement de signaux numériques.

L’objectif principal d’un système embarqué, et donc d’un microcontrôleur qui en est souvent le coeur de calcul, est de pouvoir piloter certains éléments, par l’intermédiaire d’actionneurs, en fonction de données qu’il récupère en temps réel, via des capteurs par exemple. Pour pouvoir calculer les nouvelles valeurs des sorties, à partir de nouvelles acquisitions de grandeurs d’entrée, ces systèmes ont besoin d’unités de calcul rapides et spécialisées.

Les instructions de base de ces calculateurs peuvent être classifiées dans deux grandes catégories :

  • la manipulation de données
    • mouvement de données d’une zone mémoire à une autre (les convertisseurs analogiques/numériques et numériques/analogiques par exemple stockant les données converties ou à convertir dans des registres spécifiques de la mémoire) ;
    • tests conditionnels (si, sinon par exemple) ;
  • les calculs mathématiques, qui peuvent se décomposer plus simplement en :
    • addition, C <= A + B
    • multiplication, C <= A * B

Une structure de base pour cela est la suivante :

Les microcontroleurs STM32, qui constituent l’essentiel des cartes Nucléo, possèdent ce type de calculateur. Pour l’utiliser sous MBED, il faut faire appel à des fonctions de la bibliothèque nommée mbed-dsp. Elle est à votre disposition ici (ou ).

Téléchargez cette bibliothèque dans un répertoire de votre ordinateur et importez-la dans votre projet (Cliquez droit sur votre projet, puis Import Library / From Import Wizard, puis Upload, sélectionnez le fichier zip contenant la bibliothèque, puis Import / Import).

Calcul du spectre en temps réel

Code proposé par Gary FOURNEAU (Promo 2021) modifié par Julien VILLEMEJANE

Algorithme utilisé

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.

Code complet

Ce code réalise une FFT sur 256 échantillons à une période d’échantillonnage de 40µs (soit une fréquence d’échantillonnage de 25kHz) sur un signal analogique appliqué sur A0 (signal à valeurs uniquement positives) et donne le résultat sous forme d’un signal analogique (sortie analogique A2), visualisable à l’oscilloscope.

#include "mbed.h"
#include "arm_math.h"
#include "dsp.h"
#include "arm_common_tables.h"
#include "arm_const_structs.h"

#define SAMPLES                 512             
/* 256 real party and 256 imaginary parts */
#define FFT_SIZE                SAMPLES / 2     
/* FFT size is always the same size as we have samples, so 256 in our case */
 
float32_t Input[SAMPLES];
float32_t Output[FFT_SIZE];
bool      trig=0;         // sémaphore de blocage de l'échantillonnage
int       indice = 0;

DigitalOut myled(LED1);
AnalogIn   myADC(A0);
AnalogOut  myDAC(A2);
Serial     pc(USBTX, USBRX);
Ticker     timer;
 
void sample(){
    myled = 1;
    if(indice < SAMPLES){
        Input[indice] = myADC.read() - 0.5f;    
        //Real part NB removing DC offset
        Input[indice + 1] = 0;                  
        //Imaginary Part set to zero
        indice += 2;
    }
    else{ trig = 0; }
    myled = 0;
}
 
int main() {
    float maxValue;            // Max FFT value is stored here
    uint32_t maxIndex;         // Index in Output array where max value is

    while(1) {
        if(trig == 0){
            timer.detach();
            // Initialisation du calcul de la FFT
            // NB utilisation de fonctions prédéfinies arm_cfft_sR_f32_lenXXX, où XXX est le nombre d'échantillons, ici 256
            arm_cfft_f32(&arm_cfft_sR_f32_len256, Input, 0, 1);
 
            // Calcul de la FFT et stockage des valeurs dans le tableau Output
            arm_cmplx_mag_f32(Input, Output, FFT_SIZE);
            Output[0] = 0;
            // Calcul la valeur maximale du spectre - maxValue - et son indice - maxIndex
            arm_max_f32(Output, FFT_SIZE/2, &maxValue, &maxIndex);
        
            // Affichage du spectre sous forme d'un signal analogique entre 0 et 3V.
            myDAC=1.0f;     // Impulsion de synchronisation
            wait_us(20);    
            myDAC=0.0f; 
            // Affichage des différentes valeurs du spectre
            for(int i=0; i < FFT_SIZE / 2; i++){
                myDAC=(Output[i]) * 0.9;   // Mise à l'échelle de 90% de 3.3V
                wait_us(10);               // Durée de chaque palier
            }
            myDAC=0.0f;
            // Affichage de la valeur max et de son indice
            pc.printf("MAX = %lf, %d \r\n", maxValue, maxIndex);
            // Réinitialisation du sémaphore
            trig = 1;
            indice = 0;
            timer.attach_us(&sample,40);      //40us 25KHz sampling rate
        }
    }
}

Un peu de théorie…

La FFT permet de calculer un spectre discrétisé. La résolution est donnée par le nombre d’échantillons stockés pour le calcul et la fréquence d’échantillonnage utilisée.

Schéma de principe du calcul d’une transformée de Fourier discrète (type FFT) – Issu du cours de Hervé Sauer / Initiation au Calcul Scientifique (S6)

Les systèmes échantillonnés sont capables de restituer un signal analogique si ce dernier respecte le critère de Shanon. On sait alors que le spectre sera contenu dans l’intervalle de fréquence allant de 0 à \(F_e / 2\).

Sur cet intervalle, l’algorithme de FFT donnera N échantillons répartis de manière linéaire. Ainsi, la résolution obtenue est de \(F_e / N\).

Mise en œuvre pour l’audio

Dans l’exemple suivant, on réalise la transformée de Fourier numérique d’un signal sonore récupéré à l’aide d’un microphone à électret amplifié.

Le signal analogique entrant sur l’une des broches de la carte Nucléo doit être compris entre 0 et 3.3V. Il est indispensable alors de modifier la valeur moyenne du signal pour qu’elle ait une valeur de 1.65V (la moitié de 3.3V).

Schéma de cablage

Noémie MARQUET (Promo 2021)

MInE Prototyper Prototyper avec Nucleo et MBED

Nucleo – Obtenir le spectre d’un signal en temps réel / 4