« SE3Binome2023-6 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
Ligne 302 : Ligne 302 :


https://zestedesavoir.com/articles/2106/arduino-les-secrets-de-lanalogique/
https://zestedesavoir.com/articles/2106/arduino-les-secrets-de-lanalogique/
https://www.arnabkumardas.com/arduino-tutorial/adc-register-description/

Version du 11 juin 2024 à 23:10

Projet : Audio Spectrum Analyzer (Analyseur de Spectre Audio)

Fonctionnement du système:

- Capture du son : Le microphone convertit le son en signal électrique

- Pré-amplification : On amplifie le signal pour le traiter

- Filtres passe-bande : On utilise des filtres pour diviser le signal en plusieurs bande de fréquences

- Échantillonnage : Les signaux sont numérisé pour être traités

- Traitement : Le microcontrôleur analyse les signaux

- Contrôle des LEDs : Utilisation d'une matrice LED pour afficher l'amplitude aux différentes bandes de fréquence

Comment fonctionne un microphone ?

Un microphone est composé d'un diaphragme, d'un aimant en forme de E allongé et d'une bobine de cuivre attaché autour du diaphragme. Lorsqu'un son est émis, le diaphragme se "compresse" et fait bouger la bobine de haut en bas créer ainsi un courant induit dans le circuit.

Créer un amplificateur audio :

Il faut d'abord créer un ampli audio (AOP ?) pour utiliser le signal électrique, créé par le diaphragme et la bobine, dan notre circuit. Le micro étant très petit, le signal électrique est très faible et peu exploitable. Il faut donc l'amplifier pour que l'interface audio puisse reconnaître plus efficacement les fluctuations des tensions

Choix des filtres :

Pour les filtres passe-bande on utilise des filtres actifs pour pouvoir les intégrés facilement dans un PCB. L'utilisation d'AOP sera obligatoire pour faciliter la création des filtres. Sans les AOP, nous serions obligés d'utiliser des self-inductances dans notre PCB, ce qui peut poser problèmes. Les AOP sont limités en hautes fréquences mais pas pour l'utilisation audio. Seulement les fréquences de 20Hz à 20kHz sont utilisées alors que la limite est de l'ordre du MHz.

Nous utilisons des AOP sous forme de cellules Sallen & key pour créer des filtres passe-bande d'ordre 2. Ils se comptent au nombre de 3 : un filtre Basse-Fréquence (20Hz-7kHz), un Mid-Fréquence(7kHz-14kHz) et un Haute-fréquence(14kHz-20kHz).

Évolution des brasures de la carte :

Carte branchée avec LED allumé.jpg

KiCAD :

Pour analyser le signal audio, on le sépare en trois signaux filtré sur des bandes de fréquences spécifiques, pour cela on utilise trois filtres.

Pour chacun des filtres, on met à la suite simplement un filtre passe-bas et un filtre passe-haut.

Pour le premier filtre on conserve les fréquences allant de 20Hz à 7kHz environ.

Le second : 7kHz-14kHz

Le troisième: 14kHz-20kHz.

Il est à noté que que l'on peut changer facilement nos bandes de fréquences en choisissant de nouvelle résistance / condensateur

PCB audio spectrum.png
ATMEGA.png


Filtre pour les basses fréquences composé d'un filtre passe-bas, passe-haut et d'une amplification
Filtre pour les fréquences moyennes composé d'un filtre passe-bas, passe-haut et d'une amplification


Filtre pour les hautes fréquences composé d'un filtre passe-bas, passe-haut et d'une amplification
Affichage LED des fréquences audio
USB / Batterie / Chargeur LiPo
PDF du schematic complet du visualizer









Code C et AtMega

Vous vous rappelez peut-être du bloc « prescaler » de la partie précédente, je vous avais dit qu’elle servait à « configurer » l’ADC ; plus précisément, le prescaler va diviser la fréquence de fonctionnement de l’AtMega (16 MHz sur un Arduino), par un facteur de division donné, afin de laisser le temps à analogRead d’obtenir une mesure fiable, ainsi que pour avoir une bonne précision au niveau de la valeur lue. Ce prescaler est configuré au niveau du code d’Arduino afin d’obtenir un rapport de division de 128, ce qui est un rapport très important, la raison de ce choix est que les Arduino tournent aujourd’hui pour la plupart à 16MHz, mais le code d’Arduino doit être portable, et afin de tourner sur des fréquences aussi faibles de 1MHz en conservant la même fréquence pour l’ADC (afin d’en conserver la précision), il est impératif de mettre un rapport de division bien plus important pour les grandes fréquences ; voici un morceau du code de configuration d’analogRead sur Arduino : // Si la fréquence du processeur est de 16MHz

#if F_CPU >= 16000000 // 16 MHz / 128 = 125 KHz
  sbi(ADCSRA, ADPS2);
  sbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);

Dans notre cas F_CPU = 8000000 donc / 64

#if F_CPU >= 8000000 // 8 MHz / 64 = 125 KHz
  sbi(ADCSRA, ADPS2);
  sbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);

Comme vous pouvez le voir dans ce morceau de code, au niveau de l’AtMega, le rapport de division du prescaler est configuré par trois bits (ADPS[0-2]) du registre ADCSRA (nous l’avions mentionné sur notre schéma récapitulatif), ces trois bits fonctionnent de la façon suivante :

ADPS2 ADPS1 ADPS0 Facteur de division
1 1 1 128
1 1 0 64
1 0 1 32
1 0 0 16
0 1 1 8
0 1 0 4
0 0 1 2
0 0 0 2

En pratique, le temps entre deux mesures est compris entre 16 et 20 µs, soit un peu plus que la théorie ; afin de ne pas vous encombrer avec la formule ou des mesures expérimentales, je vous propose de tableau résumant les temps et fréquences théoriques et pratiques obtenus sur un Arduino Uno : (Pour un F_CPU de 16 Mhz

Rapport de division Temps théorique Temps min. Temps max. Fréquence moyenne
128 104 µs 112 µs 116 µs 8.7 kHz
64 52 µs 54 µs 64 µs 17.2 kHz
32 26 µs 32 µs 36 µs 30.3 kHz
16 13 µs 16 µs 20 µs 55.5 kHz
8 6.5 µs 12 µs 20 µs 71.4 kHz
4 3 µs 8 µs 12 µs 100 kHz
2 1.5 µs 4 µs 12 µs 125 kHz

Il faut toutefois mettre l’accent sur quelque chose d’important : plus la vitesse de l’ADC est élevée, plus la précision est faible, la datasheet garantit une vitesse maximale de 76 kHz (cf. §24.1), mais, pour une précision maximale, la vitesse maximale est de 15 kHz, un rapport de 64 fait donc déjà perdre en précision.

Cette image, en plus du registre ADCSRA, introduit un autre registre que nous n’avons pas encore vu, il s’agit de ADCSRB, qui lui est complémentaire, ce registre est grandement inutilisé :

7 6 5 4 3 2 1 0
/ ACME / / MUX51 ADTS2 ADTS1 ADTS0

Nous ne verrons pas le bit ACME ici, pour la simple raison qu’il n’est pas utile pour l’ADC – nous le traiterons dans la partie sur le comparateur. Mais les trois bits ADTS[2-0] vont grandement nous intéresser, puisqu’ils permettent de sélectionner par quoi sera déclenché le convertisseur ; nous verrons ici simplement le cas où ADTS = 0b000, puisque les autres se réfèrent à des notions trop éloignées du sujet de l’article. Ce cas est celui où le convertisseur sera déclenché par ADIF, c’est-à-dire le registre activé en fin de conversion, permettant à notre ADC de boucler pour toujours, lisant continuellement la valeur de notre pin. Bien, il est l’heure de passer à la pratique, voici un morceau de code permettant de lire la valeur d’un pin en utilisant le free-running mode :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void setup() {
  // Désactivons l'ADC pour l'instant
  cbi(ADCSRA, ADEN);

  // Activation du free-running mode
  ADCSRB = 0x00;
  // On sélectionne notre pin (A0)
  ADMUX |= 0 & 0x07;
  // Important : préciser la référence de tension (ici, équivalent de DEFAULT)
  ADMUX |= (1 << REFS0);

  // Choix de la division du prescaler (ici, facteur 8)
  cbi(ADCSRA, ADPS2);
  sbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);

  // Ce bit doit être passé à 1 pour prendre en compte le free-running
  sbi(ADCSRA, ADATE);

  // Demande d'une interruption à la fin de la conversion
  sbi(ADCSRA, ADIE);

  // Réactivons l'ADC
  sbi(ADCSRA, ADEN);
  // On lance la première conversion
  sbi(ADCSRA, ADSC);

  // Ne pas oublier d'activer les interruptions matérielles
  sei();
}

void loop() {
  // Code à exécuter lors d'une mesure analogique
  int value = (ADCH << 8) | ADCL;
}

Voilà, rien de bien compliqué hormis que la valeur n’est pas lue en passant par analogRead, mais directement depuis les registres de sortie. Ce code fonctionne bien, mais il est possible de l’améliorer en réalisant un code asynchrone, c’est-à-dire que le code de votre application va s’exécuter normalement, et vous aurez en parallèle un code qui s’exécutera dès qu’une nouvelle valeur analogique aura été lue ; pour faire ceci, ce n’est pas compliqué, il faut tout d’abord ajouter en fin de setup un sei();, c’est une fonction qui va activer les interruptions matérielles, et nous permettre d’exécuter du code asynchrone ; ensuite, utilisez votre void loop comme vous le souhaitez, et utilisez le code suivant pour détecter une nouvelle mesure de l’entrée analogique :

1
2
3
4
ISR(ADC_vect) {
  // Code à exécuter lors d'une mesure analogique
  int value = (ADCH << 8) | ADCL;
}

Cette syntaxe est probablement nouvelle pour vous, mais elle ne doit pas vous déstabiliser, voyez simplement ça comme une fonction qui serait déclenchée par l’AtMega ; notez qu’il ne faut pas la mettre dans votre setup ou votre loop, mais bien comme si vous déclariez une nouvelle fonction globale.

GIT

https://archives.plil.fr/lgrevin/projet-se3-lilian-pierre

BIBLIOGRAPHIE et SOURCES

https://makezine.com/projects/audio-spectrum-analyzer/

https://zestedesavoir.com/articles/2106/arduino-les-secrets-de-lanalogique/

https://www.arnabkumardas.com/arduino-tutorial/adc-register-description/