Table des matières:
- Étape 1: Installation de la bibliothèque
- Étape 2: Concepts de la transformée de Fourier et de la FFT
- Étape 3: Simulation d'un signal
- Étape 4: Analyse d'un signal simulé - Codage
- Étape 5: Analyse d'un signal simulé - Résultats
- Étape 6: Analyse d'un signal réel - Câblage de l'ADC
- Étape 7: Analyse d'un signal réel - Codage
- Étape 8: Analyse d'un signal réel - Résultats
- Étape 9: Qu'en est-il d'un signal sinusoïdal écrêté ?
Vidéo: Analyseur de spectre FFT 1024 échantillons à l'aide d'un Atmega1284 : 9 étapes
2024 Auteur: John Day | [email protected]. Dernière modifié: 2024-01-30 09:06
Ce tutoriel relativement simple (compte tenu de la complexité de ce sujet) vous montrera comment créer un analyseur de spectre très simple de 1024 échantillons à l'aide d'une carte de type Arduino (1284 Narrow) et du traceur série. N'importe quel type de carte compatible Arduino fera l'affaire, mais plus elle a de RAM, plus vous obtiendrez la meilleure résolution de fréquence. Il faudra plus de 8 Ko de RAM pour calculer la FFT avec 1024 échantillons.
L'analyse du spectre est utilisée pour déterminer les principales composantes fréquentielles d'un signal. De nombreux sons (comme ceux produits par un instrument de musique) sont composés d'une fréquence fondamentale et de quelques harmoniques dont la fréquence est un multiple entier de la fréquence fondamentale. L'analyseur de spectre vous montrera toutes ces composantes spectrales.
Vous voudrez peut-être utiliser cette configuration comme compteur de fréquence ou pour vérifier tout type de signaux que vous soupçonnez d'apporter du bruit dans votre circuit électronique.
Nous nous concentrerons ici sur la partie logicielle. Si vous souhaitez créer un circuit permanent pour une application spécifique, vous devrez amplifier et filtrer le signal. Ce pré-conditionnement est totalement dépendant du signal que vous souhaitez étudier, en fonction de son amplitude, son impédance, sa fréquence maximale etc… Vous pouvez consulter
Étape 1: Installation de la bibliothèque
Nous utiliserons la bibliothèque ArduinoFFT écrite par Enrique Condes. Puisque nous voulons économiser autant que possible de la RAM, nous utiliserons la branche develop de ce référentiel qui permet d'utiliser le type de données float (au lieu de double) pour stocker les données échantillonnées et calculées. Nous devons donc l'installer manuellement. Ne vous inquiétez pas, téléchargez simplement l'archive et décompressez-la dans votre dossier de bibliothèque Arduino (par exemple sur la configuration par défaut de Windows 10: C:\Users\_your_user_name_\Documents\Arduino\libraries)
Vous pouvez vérifier que la bibliothèque est correctement installée en compilant l'un des exemples fournis, comme "FFT_01.ino".
Étape 2: Concepts de la transformée de Fourier et de la FFT
Attention: si vous ne supportez pas de voir une notation mathématique, vous pouvez passer à l'étape 3. Quoi qu'il en soit, si vous ne comprenez pas tout, considérez simplement la conclusion à la fin de la section.
Le spectre de fréquence est obtenu grâce à un algorithme de transformée de Fourier rapide. La FFT est une implémentation numérique qui se rapproche du concept mathématique de la transformée de Fourier. Sous ce concept, une fois que vous obtenez l'évolution d'un signal suivant un axe temporel, vous pouvez connaître sa représentation dans un domaine fréquentiel, composé de valeurs complexes (réelles + imaginaires). Le concept est réciproque, donc lorsque vous connaissez la représentation du domaine fréquentiel, vous pouvez la retransformer dans le domaine temporel et récupérer le signal exactement comme avant la transformation.
Mais qu'allons-nous faire de cet ensemble de valeurs complexes calculées dans le domaine temporel ? Eh bien, la majeure partie sera laissée aux ingénieurs. Pour nous, nous appellerons un autre algorithme qui transformera ces valeurs complexes en données de densité spectrale: c'est une valeur de magnitude (= intensité) associée à chaque bande de fréquence. Le nombre de bande de fréquence sera le même que le nombre d'échantillons.
Vous connaissez certainement le concept d'égaliseur, comme celui-ci Retour aux années 80 avec l'égaliseur graphique. Bon, on obtiendra le même genre de résultats mais avec 1024 bandes au lieu de 16 et beaucoup plus de résolution d'intensité. Lorsque l'égaliseur donne une vision globale de la musique, l'analyse spectrale fine permet de calculer précisément l'intensité de chacune des 1024 bandes.
Un concept parfait, mais:
- Puisque la FFT est une version numérisée de la transformée de Fourier, elle se rapproche du signal numérique et perd certaines informations. Donc, à proprement parler, le résultat de la FFT s'il était retransformé avec un algorithme FFT inversé ne donnerait pas exactement le signal d'origine.
- De plus, la théorie considère un signal qui n'est pas fini, mais qui est un signal constant éternel. Étant donné que nous ne le numériserons que pour une certaine période de temps (c'est-à-dire des échantillons), d'autres erreurs seront introduites.
-
Enfin, la résolution de la conversion analogique-numérique aura un impact sur la qualité des valeurs calculées.
En pratique
1) La fréquence d'échantillonnage (notée fs)
Nous allons échantillonner un signal, c'est-à-dire mesurer son amplitude, toutes les 1/fs de seconde. fs est la fréquence d'échantillonnage. Par exemple, si nous échantillonnons à 8 KHz, l'ADC (convertisseur analogique-numérique) qui est à bord de la puce fournira une mesure tous les 1/8000 de secondes.
2) Le nombre d'échantillons (noté N ou échantillons dans le code)
Puisque nous devons obtenir toutes les valeurs avant d'exécuter la FFT, nous devrons les stocker et nous limiterons donc le nombre d'échantillons. L'algorithme FFT a besoin d'un nombre d'échantillons qui est une puissance de 2. Plus nous avons d'échantillons, mieux c'est mais cela prend beaucoup de mémoire, d'autant plus que nous aurons également besoin de stocker les données transformées, qui sont des valeurs complexes. La bibliothèque Arduino FFT économise de l'espace en utilisant
- Un tableau nommé "vReal" pour stocker les données échantillonnées puis la partie réelle des données transformées
- Un tableau nommé "vImag" pour stocker la partie imaginaire des données transformées
La quantité de RAM nécessaire est égale à 2 (tableaux) * 32 (bits) * N (échantillons).
Donc dans notre Atmega1284 qui a un joli 16 Ko de RAM nous stockerons un maximum de N = 16000*8 / 64 = 2000 valeurs. Puisque le nombre de valeurs doit être une puissance de 2, nous allons stocker un maximum de 1024 valeurs.
3) La résolution de fréquence
La FFT calculera des valeurs pour autant de bandes de fréquences que le nombre d'échantillons. Ces bandes s'étendront de 0 Hz à la fréquence d'échantillonnage (fs). La résolution fréquentielle est donc:
Frésolution = fs / N
La résolution est meilleure lorsqu'elle est inférieure. Donc pour une meilleure résolution (inférieure) nous voulons:
- plus d'échantillons, et/ou
- un fs inférieur
Mais…
4) fs minimale
Puisque nous voulons voir beaucoup de fréquences, certaines d'entre elles étant beaucoup plus élevées que la "fréquence fondamentale", nous ne pouvons pas régler fs trop bas. En fait, il existe le théorème d'échantillonnage de Nyquist-Shannon qui nous oblige à avoir une fréquence d'échantillonnage bien supérieure au double de la fréquence maximale que nous aimerions tester.
Par exemple, si nous souhaitons analyser tout le spectre de 0 Hz à disons 15 KHz, qui est approximativement la fréquence maximale que la plupart des humains peuvent entendre distinctement, nous devons régler la fréquence d'échantillonnage à 30 KHz. En fait, les électroniciens la fixent souvent à 2,5 (voire 2,52) * la fréquence maximale. Dans cet exemple, ce serait 2,5 * 15 KHz = 37,5 KHz. Les fréquences d'échantillonnage habituelles en audio professionnel sont de 44,1 KHz (enregistrement de CD audio), 48 KHz et plus.
Conclusion:
Les points 1 à 4 mènent à: nous voulons utiliser autant d'échantillons que possible. Dans notre cas avec un périphérique de 16 Ko de RAM, nous considérerons 1024 échantillons. Nous voulons échantillonner à la fréquence d'échantillonnage la plus basse possible, tant qu'elle est suffisamment élevée pour analyser la fréquence la plus élevée que nous attendons dans notre signal (2,5 * cette fréquence, au moins).
Étape 3: Simulation d'un signal
Pour notre premier essai, nous allons légèrement modifier l'exemple TFT_01.ino donné dans la librairie, pour analyser un signal composé de
- La fréquence fondamentale, réglée sur 440 Hz (la musical)
- 3ème harmonique à la moitié de la puissance du fondamental ("-3 dB")
- 5ème harmonique au 1/4 de la puissance du fondamental ("-6 dB)
Vous pouvez voir dans l'image ci-dessus le signal résultant. Cela ressemble en effet beaucoup à un signal réel que l'on peut parfois voir sur un oscilloscope (je l'appellerais "Batman") en situation d'écrêtage d'un signal sinusoïdal.
Étape 4: Analyse d'un signal simulé - Codage
0) Inclure la bibliothèque
#include "arduinoFFT.h"
1. Définitions
Dans les sections des déclarations, nous avons
octet const adcPin = 0; // A0
const uint16_t échantillons = 1024; // Cette valeur DOIT TOUJOURS être une puissance de 2 const uint16_t samplingFrequency = 8000; // Affectera la valeur maximale du timer dans timer_setup() SYSCLOCK/8/samplingFrequency doit être un entier
Étant donné que le signal a une 5ème harmonique (fréquence de cette harmonique = 5 * 440 = 2200 Hz), nous devons régler la fréquence d'échantillonnage au-dessus de 2,5 * 2200 = 5500 Hz. Ici, j'ai choisi 8000 Hz.
Nous déclarons également les tableaux où nous allons stocker les données brutes et calculées
float vReal[échantillons];
float vImag[samples];
2) Instanciation
Nous créons un objet ArduinoFFT. La version de développement d'ArduinoFFT utilise un modèle afin que nous puissions utiliser le type de données float ou double. Float (32 bits) est suffisant en ce qui concerne la précision globale de notre programme.
ArduinoFFT FFT = ArduinoFFT (vReal, vImag, échantillons, fréquence d'échantillonnage);
3) Simuler le signal en remplissant le tableau vReal, au lieu de le remplir de valeurs ADC.
Au début de la boucle, nous remplissons le tableau vReal avec:
cycles flottants = (((samples) * signalFrequency) / samplingFrequency); //Nombre de cycles de signal que l'échantillonnage va lire
for (uint16_t i = 0; i < échantillons; i++) { vReal = float((amplitude * (sin((i * (TWO_PI * cycles)) / échantillons))));/* Construire des données avec valeurs négatives*/ vReal += float((amplitude * (sin((3 * i * (TWO_PI * cycles)) / samples))) / 2.0);/* Construire des données avec des valeurs positives et négatives*/ vReal += float((amplitude * (sin((5 * i * (TWO_PI * cycles)) / échantillons))) / 4.0);/* Construire des données avec des valeurs positives et négatives*/ vImag = 0.0; //La partie imaginaire doit être mise à zéro en cas de bouclage pour éviter les mauvais calculs et les débordements }
On ajoute une numérisation de l'onde fondamentale et des deux harmoniques de moindre amplitude. Ensuite, nous initialisons le tableau imaginaire avec des zéros. Étant donné que ce tableau est rempli par l'algorithme FFT, nous devons l'effacer à nouveau avant chaque nouveau calcul.
4) Calcul FFT
Ensuite, nous calculons la FFT et la densité spectrale
FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward);
FFT.calculer(FFTDirection::Forward); /* Calculer FFT */ FFT.complexToMagnitude(); /* Calculer les grandeurs */
L'opération FFT.windowing(…) modifie les données brutes car nous exécutons la FFT sur un nombre limité d'échantillons. Le premier et le dernier échantillons présentent une discontinuité (il n'y a "rien" d'un côté). C'est une source d'erreur. L'opération de "fenêtrage" tend à réduire cette erreur.
FFT.compute(…) avec la direction "Forward" calcule la transformation du domaine temporel vers le domaine fréquentiel.
Ensuite, nous calculons les valeurs de magnitude (c'est-à-dire d'intensité) pour chacune des bandes de fréquences. Le tableau vReal est maintenant rempli de valeurs de magnitudes.
5) Dessin de traceur en série
Imprimons les valeurs sur le traceur série en appelant la fonction printVector(…)
PrintVector(vReal, (échantillons >> 1), SCL_FREQUENCY);
Il s'agit d'une fonction générique qui permet d'imprimer des données avec un axe de temps ou un axe de fréquence.
Nous imprimons également la fréquence de la bande qui a la valeur de magnitude la plus élevée
float x = FFT.majorPeak();
Serial.print("f0="); Serial.print(x, 6); Serial.println("Hz");
Étape 5: Analyse d'un signal simulé - Résultats
Nous voyons 3 pics correspondant à la fréquence fondamentale (f0), les 3e et 5e harmoniques, avec la moitié et 1/4 de la magnitude f0, comme prévu. On peut lire en haut de la fenêtre f0= 440,430114 Hz. Cette valeur n'est pas exactement de 440 Hz, pour toutes les raisons expliquées ci-dessus, mais elle est très proche de la valeur réelle. Il n'était pas vraiment nécessaire de montrer autant de décimales insignifiantes.
Étape 6: Analyse d'un signal réel - Câblage de l'ADC
Puisque nous savons comment procéder en théorie, nous aimerions analyser un signal réel.
Le câblage est très simple. Connectez les masses ensemble et la ligne de signal à la broche A0 de votre carte via une résistance en série d'une valeur de 1 KOhm à 10 KOhm.
Cette résistance série protégera l'entrée analogique et évitera la sonnerie. Il doit être aussi élevé que possible pour éviter la sonnerie et aussi faible que possible pour fournir suffisamment de courant pour charger rapidement l'ADC. Reportez-vous à la fiche technique du MCU pour connaître l'impédance attendue du signal connecté à l'entrée ADC.
Pour cette démo, j'ai utilisé un générateur de fonction pour alimenter un signal sinusoïdal de fréquence 440 Hz et d'amplitude autour de 5 volts (il est préférable que l'amplitude soit comprise entre 3 et 5 volts pour que l'ADC soit utilisé presque à pleine échelle), via une résistance de 1,2 KOhm.
Étape 7: Analyse d'un signal réel - Codage
0) Inclure la bibliothèque
#include "arduinoFFT.h"
1) Déclarations et instanciation
Dans la section de déclaration, nous définissons l'entrée ADC (A0), le nombre d'échantillons et la fréquence d'échantillonnage, comme dans l'exemple précédent.
octet const adcPin = 0; // A0
const uint16_t échantillons = 1024; // Cette valeur DOIT TOUJOURS être une puissance de 2 const uint16_t samplingFrequency = 8000; // Affectera la valeur maximale du timer dans timer_setup() SYSCLOCK/8/samplingFrequency doit être un entier
Nous créons l'objet ArduinoFFT
ArduinoFFT FFT = ArduinoFFT (vReal, vImag, échantillons, fréquence d'échantillonnage);
2) Configuration de la minuterie et de l'ADC
Nous réglons le temporisateur 1 pour qu'il cycle à la fréquence d'échantillonnage (8 KHz) et déclenche une interruption lors de la comparaison de sortie.
void timer_setup(){
// réinitialiser le temporisateur 1 TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TCCR1B = bit (CS11) | bits (WGM12); // CTC, prescaler de 8 TIMSK1 = bit (OCIE1B); OCR1A = ((1600000 / 8) / fréquence d'échantillonnage) -1; }
Et réglez l'ADC pour qu'il
- Utilise A0 comme entrée
- Déclenche automatiquement sur chaque sortie de la minuterie 1 comparer match B
- Génère une interruption lorsque la conversion est terminée
L'horloge ADC est réglée à 1 MHz, en pré-échelonnant l'horloge système (16 MHz) de 16. Étant donné que chaque conversion prend environ 13 horloges à pleine échelle, les conversions peuvent être réalisées à une fréquence de 1/13 = 0,076 MHz = 76 KHz. La fréquence d'échantillonnage doit être nettement inférieure à 76 KHz pour laisser le temps à l'ADC d'échantillonner les données. (nous avons choisi fs = 8 KHz).
void adc_setup() {
ADCSRA = bit (ADEN) | peu (ADIE) | bits (ADIF); // activer l'ADC, vouloir interrompre à la fin ADCSRA |= bit (ADPS2); // Préscaler de 16 ADMUX = bit (REFS0) | (adcPin & 7); // réglage de l'entrée ADC ADCSRB = bit (ADTS0) | bits (ADTS2); // Timer/Counter1 Comparer la source de déclenchement Match B ADCSRA |= bit (ADATE); // activer le déclenchement automatique }
Nous déclarons le gestionnaire d'interruption qui sera appelé après chaque conversion ADC pour stocker les données converties dans le tableau vReal et effacer l'interruption
// ADC complet ISR
ISR (ADC_vect) { vReal[resultNumber++] = ADC; if(resultNumber == échantillons) { ADCSRA = 0; // désactiver l'ADC } } EMPTY_INTERRUPT (TIMER1_COMPB_vect);
Vous pouvez avoir une explication exhaustive sur la conversion ADC sur l'Arduino (analogRead).
3) Configuration
Dans la fonction de configuration, nous effaçons la table de données imaginaires et appelons les fonctions de configuration de la minuterie et de l'ADC
zéroI(); // une fonction qui met à 0 toutes les données imaginaires - expliqué dans la section précédente
timer_setup(); adc_setup();
3) Boucle
FFT.dcRemoval(); // Supprime la composante continue de ce signal puisque l'ADC est référencé à la masse
FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward); // Peser les données FFT.compute(FFTDirection::Forward); // Calculer FFT FFT.complexToMagnitude(); // Calcul des magnitudes // impression du spectre et de la fréquence fondamentale f0 PrintVector(vReal, (samples >> 1), SCL_FREQUENCY); float x = FFT.majorPeak(); Serial.print("f0="); Serial.print(x, 6); Serial.println("Hz");
On supprime la composante continue car l'ADC est référencé à la masse et le signal est centré autour de 2,5 volts environ.
Ensuite, nous calculons les données comme expliqué dans l'exemple précédent.
Étape 8: Analyse d'un signal réel - Résultats
En effet on ne voit qu'une seule fréquence dans ce simple signal. La fréquence fondamentale calculée est de 440,118194 Hz. Ici encore, la valeur est une approximation très proche de la fréquence réelle.
Étape 9: Qu'en est-il d'un signal sinusoïdal écrêté ?
On overdrive maintenant un peu le CAN en augmentant l'amplitude du signal au dessus de 5 volts, il est donc écrêté. Ne poussez pas trop la bouillie pour ne pas détruire l'entrée ADC !
On voit apparaître des harmoniques. L'écrêtage du signal crée des composants à haute fréquence.
Vous avez vu les principes fondamentaux de l'analyse FFT sur une carte Arduino. Vous pouvez maintenant essayer de changer la fréquence d'échantillonnage, le nombre d'échantillons et le paramètre de fenêtrage. La bibliothèque ajoute également des paramètres pour calculer la FFT plus rapidement avec moins de précision. Vous remarquerez que si vous réglez la fréquence d'échantillonnage trop bas, les magnitudes calculées apparaîtront totalement erronées à cause du repliement spectral.
Conseillé:
Comment faire un analyseur de spectre audio LED : 7 étapes (avec photos)
Comment faire un analyseur de spectre audio LED : L'analyseur de spectre audio LED génère le beau motif d'éclairage en fonction de l'intensité de la musique. Analyseur utilisant NeoPixe
Comment bricoler un analyseur de spectre de musique audio LED 32 bandes en utilisant Arduino Nano à la maison #arduinoproject: 8 étapes
Comment bricoler un analyseur de spectre musical audio LED 32 bandes à l'aide d'Arduino Nano à la maison doit être connecté devant la résistance 100k, sinon le bruit du spea
Analyseur de spectre acrylique de grande taille : 7 étapes (avec photos)
Analyseur de spectre acrylique de grande taille : Pourquoi voudriez-vous regarder ces petits écrans à LED ou ces petits écrans LCD si vous pouvez le faire en grand ? Ceci est une description étape par étape sur la façon de construire votre propre analyseur de spectre de taille géante. En utilisant des carreaux acryliques et bandes led pour construire une pièce remplissant lumi
Analyseur de spectre audio DIY FFT : 3 étapes
Analyseur de spectre audio FFT DIY : L'analyseur de spectre FFT est un équipement de test qui utilise l'analyse de Fourier et des techniques de traitement du signal numérique pour fournir une analyse de spectre. En utilisant l'analyse de Fourier, il est possible pour une valeur dans, par exemple, le domaine de temps continu à conver
Analyseur d'échantillons de roche : 4 étapes
Analyseur d'échantillons de roche : l'analyseur d'échantillons de roche est utilisé pour identifier et analyser les types d'échantillons de roches à l'aide de la technique de vibration par martelage doux. Il s'agit d'une nouvelle méthode d'identification des échantillons de roche. Si une météorite ou un échantillon de roche inconnu s'y trouve, on peut estimer le