SE3Binome2023-6

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

Projet : Audio Spectrum Analyzer (Analyseur de Spectre Audio)

Ce projet est fortement inspiré du projet https://wiki-se.plil.fr/mediawiki/index.php/I2L_2023_Groupe2 (leur dernière vidéo est ce que nous devrions avoir pour la fin mais sans le buzzer avec 3 bandes de LEDs au lieu de une) de l'année dernière. Leur projet était de faire un détecteur de bruit. Notre projet est similaire mais là où nous dérivons et que nous voulons juste faire des visualiseurs de sons donc des bandes de LEDs.

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és par l'AtMega pour être traités

- Traitement : L'AtMega 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. Notre microphone fonctionne différemment. Il s'agit d'un condensateur qui s'élargie ou se rétrécie dû à la membrane. Le principe de ces microphones est que ce condensateur a une armature fixe et une autre souple, permettant lorsque l'onde sonore arrive de modifier la distance entre ces deux armatures. Or la capacité d’un condensateur est définie par C = e S/ e où e est la permittivité, S la surface et e la distance entre les armatures, qui est variable dans notre cas. Or le courant i = C dU/dt où U est la tension aux bornes du condensateur. Ainsi l’onde sonore fait varier C, puis U, que l’on peut mesurer, après amplification car typiquement le signal en sortie d’un micro est de l’ordre de 0.1 mV à 10 mV. En pratique, la vibration est amplifiée électriquement par un transistor, dont le fonctionnement exige une alimentation électrique. La tension d’excitation nécessaire au fonctionnement d’un micro électret est de l’ordre de 3 à 5 volts continus.

Schéma montrant comment fonctionne le microphone electret mk1
Comment fonctionne un micro electret de manière physique

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. La remarque sur la sortie est de l'ordre de 0.1 mV à 10mV, il faut donc une très grande amplification. Au moins de l'ordre de 103 . Malheureusement, nous nous sommes trompés dans nos composants et avons pris deux résistances de 810Ω dans la précipitation. Dû à cette erreur nous n'avons qu'une pré amplification de 2. Nous ne savions pas quel microphone nous allions utiliser, car la nature de notre projet nécessite des composants assez importants pour les autres années de SE et nous ne savions donc pas quel micro nous allions utiliser. Nous aurions dû faire plus attention à la datasheet du micro et refaire nos calculs en conséquence. Nous aution pû nous en douter, ces microphones sont utilisés dans les téléphones de nos jours. Ils doivent être petits mais aussi très sensibles et par conséquences, nécessite une grande amplification.

Schématique KiCAD montrant comment fonctionne le microphone Electret MK1 avec une amplification Vs=2Ve

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). Par simplicité, nous utilisons un filtre passe-bas et passe-haut simple du 1er ordre.

Schématique KiCAD montrant le filtre LOW FREQUENCY de notre projet
Schématique KiCAD montrant le filtre MID FREQUENCY de notre projet
Schématique KiCAD montrant le filtre HIGH FREQUENCY de notre projet

Évolution des brasures de la carte :

Dû à un manque d'AOP, nous n'avions pu faire que l'amplification et les filtres pour les Hautes et Basses Fréquences

On notera la difficulté à souder des AOPs (c'est minuscules)

Carte branchée avec LED allumé.jpg
Audio EQ complet mais avec un AOP manquant

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 Final du visualiseur audio
Schématique du microcontroleur, de l'ISP et des cavaliers permettant le changement entre l'alimentation en batterie et sur secteur
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

EXPLICATIONS

Dans le bloc « prescaler » sert à 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, 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, 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.

Ce tableau, 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:

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

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. 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.

 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;
}

EN RÉSUMÉ

ADMUX

  • REFS1 et REFS0 : Permettent de choisir quelle la tension de référence utilisée pour les mesures.
  • ADLAR : Permet de décaler les bits vers la gauche au niveau des registres ADCL et ADCH (dans ce mode, ADCH contient les bits de poids les plus forts)
  • MUX0-4 : Permet de choisir sur quelle entrée aura lieu la conversion (AD0,ADC1 et ADC4).

ADCSRA

  • ADEN : Autorisation de la conversion
  • ADSP0-2 : Pré diviseurs utilisés pour éviter de faire des mesures trop rapides ou pas assez précises
  • ADSC : Activation de la conversion
  • ADATE Activation ou non du mode free-running

ADCSRB

  • ADTS2-0: Permet de choisir sur quelle entrée aura lieu le détection du free-running mode

INITIALISATION

Low Frequencies

Ceci est le code permettant d'initialiser l'AtMega pour lire la broche PF0(ADC0) et convertir la tension entrée en données numériques exploitables pour l'AtMega. Pour choisir la broche PF0(ADC0), il faut set ADMUX à 0x00.

void ad_init_LF(){    // Sélectionne un canal
  ADCSRA &= ~(1 << ADEN); // Convertisseur désactivé
  ADCSRB = 0x00;

  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) ;// | (1 << ADPS0)
  ADCSRA &= ~(1 << ADPS0); // Division de fréquence 64 => 125KHz
  // 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
  ADCSRA &= ~(1 << ADATE);                       // Mode conversion unique


  ADMUX &= 0x00;
  ADMUX |= (1 << REFS0) | (1 << ADLAR);             // Référence de mesure AVCC et de mettre les 1024 niveaux de tensions entre 0V et VCC+0.3V car REFS0 = 1. ADLAR = 1 donc on a 8 bits d'info
  ADMUX &= ~(1 << REFS1);
                                              // Selection du canal // Selection de ADC0 mais ensuite on séléctionnera ADC1 et ADC4 pour les autres passe-bandes

  ADCSRA |= (1 << ADEN);                      // Convertisseur activé
}

Mid Frequencies

void ad_init_MF(){    // Sélectionne un canal
  ADCSRA &= ~(1 << ADEN); // Convertisseur désactivé
  ADCSRB = 0x00;

  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) ;// | (1 << ADPS0)
  ADCSRA &= ~(1 << ADPS0); // Division de fréquence 64 => 125KHz
  // 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
  ADCSRA &= ~(1 << ADATE);                       // Mode conversion unique



  ADMUX &= 0x00;
  ADMUX |= (1 << REFS0) | (1 << ADLAR);             // Référence de mesure AVCC et de mettre les 1024 niveaux de tensions entre 0V et VCC+0.3V car REFS0 = 1. ADLAR = 1 donc on a 8 bits d'info
  ADMUX &= ~(1 << REFS1);
  // Selection du canal // Selection de ADC0 mais ensuite on séléctionnera ADC1 et ADC4 pour les autres passe-bandes
  ADMUX |=  0b00000010;

  ADCSRA |= (1 << ADEN);                      // Convertisseur activé
}

High Frequencies

void ad_init_HF(){    // Sélectionne un canal
  ADCSRA &= ~(1 << ADEN); // Convertisseur désactivé
  ADCSRB = 0x00;

  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) ;// | (1 << ADPS0)
  ADCSRA &= ~(1 << ADPS0); // Division de fréquence 64 => 125KHz
  // 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
  ADCSRA &= ~(1 << ADATE);                       // Mode conversion unique


  ADMUX &= 0x00;
  ADMUX |= (1 << REFS0) | (1 << ADLAR);             // Référence de mesure AVCC et de mettre les 1024 niveaux de tensions entre 0V et VCC+0.3V car REFS0 = 1. ADLAR = 1 donc on a 8 bits d'info
  ADMUX &= ~(1 << REFS1);             // Selection du canal // Selection de ADC0 mais ensuite on séléctionnera ADC1 et ADC4 pour les autres passe-bandes
  ADMUX |=  0b00000100;

  ADCSRA |= (1 << ADEN);                      // Convertisseur activé
}

Les 3 broches d'entrée de l'AtMega de chaque Bande de fréquences

PF0 est ADC0 ==> MUX[0-3] = 0b0000 ; PF1 est ADC1 ==> MUX[0-3] = 0b0001 ; PF4 est ADC4 ==> MUX[0-3] = 0b0100 ;

Schématique KiCAD ATMega16u4. VsLF, VsMF, VsHF sont les entrées de chaque signal dans l'ATMEGA16u4

Capture et conversion en numérique

Ce code permet de capturer les données analogiques sur la PIN configurer dans les registres ADMUX et ADCSRA.

La fonction ad_capture() permet de convertir sur la broche préalablement configurée. Elle attend que ADSC soit mise à 1 grâce à bit_is_set() et renvoie la valeur stockée dans le registre ADCH.

Lors de l'utilisation des ADC, il est très importants de faire une conversion à la fois. C'est-à-dire que si plusieurs broches doivent être converties, il faut refaire une déclaration des registres avant de faire la conversions.

unsigned int ad_capture(void) {          // Acquisition de tension
  ADCSRA |= (1 << ADSC);                      // Début de conversion
  while (bit_is_set(ADCSRA, ADSC));        // Attente de la fin de conversion
  return ADCH;                            // Résultat sur 8 bits car ADLAR=1
}

Lecture du son numérique

Ce code lit les basses fréquences. La fonction ad_init_LF vient initialiser les registres et chercher le PIN sur lequel convertir l'entrée analogique. La fonction ad_capture() va capturer la tension pendant un durée définie par les bits ADSP de l'AtMega et la transformer en une valeur numérique basée sur 1024 niveaux définis par les bits REFS.

void read_sound_LF() {
  ad_init_LF();
  unsigned int db = MAX_INT - ad_capture(); //  MAX_INT = 256
  PORTB &= ~(0b00010000);
  PORTD &= ~(0b11010000);

  if(db > 20){
      PORTD |= 0b00010000;
  }
  if (db > 40){
    PORTD |= 0b01010000;
  }
  if (db > 60){
    PORTD |= 0b11010000;
  }
  if (db > 80){
    PORTD |= 0b11010000;
    PORTB |= 0b00010000;
  }
}

Si les db dépasse un certain certains seuils, la LED s'allume. Ces seuils sont arbitraires mais méritent d'être redesigner par rapport au circuit complet. Ce sont pas réellement des dB qui sortent de l'AtMega mais des "niveaux", il faut donc savoir quelles sont les valeurs maximales et minimales sortantes.

void read_sound_MF() {
  ad_init_MF();
  unsigned int db = MAX_INT - ad_capture();
  PORTC &= ~(0b11000000);
  PORTD &= ~(0b00000011);

  if(db > 140){
    PORTD |= 0b00000001;
  }
  if (db > 160){
    PORTD |= 0b00000011;
  }
  if (db > 170){
    PORTD |= 0b00000011;
    PORTC |= 0b01000000;
  }
  if (db > 180){
    PORTD |= 0b00000011;
    PORTC |= 0b11000000;
  }
}
void read_sound_HF() {
  ad_init_HF();
  unsigned int db = MAX_INT - ad_capture();
  PORTB &= ~(0b00100000);
  PORTF &= ~(0b11100000);

  if(db > 140){
    PORTF |= 0b00100000;
  }
  if (db > 160){
    PORTF |= 0b01100000;
  }
  if (db > 190){
    PORTF |= 0b11100000;
  }
  if (db > 240){
    PORTF |= 0b11100000;
    PORTB |= 0b00100000;
  }
}

Pour ce cas là, nous avons essayé d'autres seuils mais dû à un bruit en haute fréquences, les seuils étaient fortement dépassés.

Initialisation des PINs

Code dans le SetUpHardware() initialisant les PINs en IN ou OUT avec des Pull-ups ou non.

// POUR ALLUMER TOUTES LES LEDS
	MCUCR |= (1<<JTD);
	MCUCR |= (1<<JTD);
	CLKSEL0 = 0b00010101;   // sélection de l'horloge externe
	CLKSEL1 = 0b00001111;   // minimum de 8Mhz
	CLKPR = 0b10000000;     // modification du diviseur d'horloge (CLKPCE=1)
	CLKPR = 0;              // 0 pour pas de diviseur (diviseur de 1)


	//Les PORTS F
	DDRF  |= 0b11100000; //Bit 7,6 et 5 sont des LEDs
	DDRF  &= 0b11110000; //On force les Bits 0,1 et 4 à zéro car ce sont des PINS IN
	PORTF |= 0b11101011;
    PORTF &= 0b00001011;

	//Les PORTS D
	DDRD  &= 0b11010011;  //Bit 7,6,4,1 et 0 sont des LEDs
	

    //PORT B
    DDRB  &= 0b00110000; //Bit 5 et 4 sont des LEDs
	

    //PORT C
    DDRC  &= 0b11000000; //Bit 7 et 6 sont des LEDs
LED Seuil d'allumage
1 20dB
2 40dB
3 60dB
4 80dB

BOUCLE MAIN

Tous les 500 itérations, on regarde ce qu'il se trouve sur les PINs pour éviter de surcharger l'AtMega et les autres ressources électroniques. Dans notre cas, nous n'avions pas d'AOP pour les MF donc nous n'avons pas besoin de regarder le son pour ces fréquences.

int main(void)
{

	SetupHardware();
	GlobalInterruptEnable();

    int i = 0;

	for (;;)
	{
      i++;
      if(i>CYCLES){
        read_sound_LF();
        // read_sound_MF();
        read_sound_HF();
        i = 0;
      }
		USB_USBTask();
        // USB_IN_PointManagement();
        // USB_OUT_PointManagement();
	}
}

PROBLÈMES RENCONTRÉS

Nous avons eu quelques difficultés concernant le micro electret MK1. En effet, nous avons vu sur un oscilloscope que notre circuit crée un signal/bruit à très hautes fréquences et un autre signal dans les environs de 50Hz. C'est pour cela que nous voyons sur notre PCB les lumières allumées sur les HF(droite) et LF(gauche).

Carte avec code complet mais avec quelques problèmes...

De plus, d'après certains anciens (Monsieur Albin MOUTON) élèves de SE, les micro electret MK1 ont de gros problèmes au niveau du bruit électronique et de la sensibilité de la capture mais ce détail peut être réglé avec de la jugeote. Le vrai problème est l'amplification avec les AOPs. Dû à la mauvaise amplification, les variations de tension/son ne sont pas visibles. Or, les LEDs sur la gauche sont les LEDs des Low Frequencies et captent le bruit vers les 50Hz. Si nous changeons les valeurs des seuils dans la fonction void read_sound_LF(), les LEDs s'éteignent si les valeurs sont trop hautes prouvant que le code et notre système fonctionne partiellement. En revanche, l'origine du bruit en haute fréquence nous est inconnue.

CONCLUSION et AXE D'AMÉLIORATIONS

Ce projet nous a permis d'apprendre ÉNORMÉMENT sur les architectures Arduino et AtMega comme par exemple tous les registres ADC et ADMUX qui sont essentiels pour la création de PCB permettant d'utiliser des capteurs. Elle nous a aussi appris, de manière empirique et très maladroite, à DEVOIR faire les dimensionnements avant de faire les brasures, le code et de chercher les datasheets plutôt que compter sur sa bonne foi et penser que tout va bien se passer sans y rémedier.

Quelques pistes d'amélioration possibles (dans le cas d'une V2 voir un produit fini et fonctionnel) :

  • Correction du PCB et meilleure routage pour éviter de trop grandes pistes
  • Trouver un moyen d'utiliser la batterie (nous avons arraché notre piste USB nous ne pouvons plus la recharger)
  • Régler les bruits électroniques perturbant les mesures de l'AtMega et amplifier correctement le micro (et avoir un micro plus gros donc nécessitant une moindre amplification)
  • Créer une sortie des PINs sur un écran pour avoir un vrai visualiseur audio

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/

FICHIER ZIP

Fichier:KiCAD Audio Spectrum Analyzer LG PC.zip