Table des matières Python

Émetteur et récepteur d'ultrasons à 40 kHz

1. Introduction

Ce document décrit un système complet permettant de faire des mesures de distance au moyen de salves d'ultrasons à 40 kHz. Le circuit fournissant la tension au transducteur émetteur utilise un pont de transistor L298. Le circuit de réception comporte un amplificateur, un détecteur d'enveloppe et un comparateur. L'ensemble est piloté par un microcontrôleur (Arduino) dont le logiciel détermine le temps de trajet des salves.

2. Commande de l'émetteur

emetteur-pontH.svgFigure pleine page

L'élément central de l'émetteur est un pont de transistors (pont en H) à transistors à jonctions. Nous utilisons le pont L298 à sa fréquence maximale de fonctionnement (40 kHz). Le fonctionnement de la commande par microntrôleur est expliqué en détail dans pont de transistor L298. La sortie du pont est branchée sur le bobinage primaire d'un transformateur. Le transducteur émetteur est branché directement aux bornes de la bobine secondaire. L'alimentation du pont fournit une tension source Vs pouvant varier entre 10 et 48 volts. Le tension e(t) en sortie du pont (entrée du transformateur) alterne entre +Vs et +Vs. Le découplage galvanique effectué par le transformateur permet de placer la masse sur une des bornes de la bobine secondaire, ce qui est nécessaire pour visualiser la tension s(t) ou bien pour utiliser ce transducteur aussi pour la réception. La tension s(t) pouvant atteindre plusieurs dizaines de volts, on place en sortie un potentiomètre qui permet d'obtenir une tension u(t) dix fois plus petite que s(t). Nous avons utilisé pour transformateur une bobine d'arrêt (Power Line Choke Coil) comportant deux bobinages primaire et secondaire identiques, d'auto-inductance 10mH:

choke

Avec ce transformateur, le rapport de transformation est 1:1, mais ce circuit pourrait fonctionner avec un transformateur de rapport 1:2 ou 1:3 dans le cas où il faudrait alimenter un transducteur nécessitant des tensions plus élevées pour fonctionner à pleine puissance.

3. Circuit de réception

detecteurUS.svgFigure pleine page

Le circuit est alimenté en 5 volts (alimentation simple). Il est connecté à cette alimentation, à l'Arduino et au transducteur de réception (qui peut être le même que le transducteur d'émission) par l'entrée INPUT.

Le premier bloc est un amplificateur constitué de deux amplificateurs inverseurs en série. Le premier réalise un gain de 10. Le second a un gain déterminé par la valeur de résistance du potentiomètre numérique U5 (MCP41100), variable de 1 à 100 kΩ. Le pont diviseur R4,R5 et le suiveur U1C permettent d'imposer une tension de référence égale à 2,5 volts : lorsque la tension en entrée est nulle, la tension en sortie est égale à 2,5 volts. L'ALI TLV2374 fonctionne à pleine échelle (rail to rail), ce qui permet d'avoir en sortie de l'amplificateur une tension alternative allant de 0 à 5 volts.

Les deux condensateurs C1 et C3 permettent de réaliser un amplificateur passe-bande, avec un maximum du gain très proche de 40kHz. Il est en effet important d'éliminer les ondulations à 50 Hz, qui ne manquent pas d'apparaître sans ce filtrage.

Voici le gain en décibel en focntion de la fréquence obtenu par une simulation LTSPice (avec un modèle de TLV2374) lorsque la résistance entre les bornes 6 et 7 vaut 50 (gain de 50 pour le second amplificateur).

import numpy as np
from matplotlib.pyplot import *

freq = []
GdB = []
phi = []

def lecture_fichier(nom,skiprows=1):
    #Lecture d'un fichier de réponse AC de LT spice
    liste_lignes = []
    with open(nom) as f:
        for line in f:
            line = line.strip()
            if skiprows>0:
                skiprows -= 1
            else:
                f,H=line.split("\t")
                freq.append(float(f))
                for s in ["(",")","dB","°"]:
                    H = H.replace(s,'')
                G,ph = H.split(",")
                GdB.append(float(G))
                phi.append(float(ph))

lecture_fichier("amplificateur.txt") 
figure()
plot(freq,GdB)
xscale('log')
xlabel('f (Hz)')
ylabel('GdB')
grid()                
                 
gainAmpligainAmpli.pdf

Le gain à 40kHz vaut 52 dB, ce qui est très proche de la valeur maximale. La décroissance au-delà de 60kHz est due à la bande passante des ALI. La sortie de l'amplificateur est notée AMPL.

Le bloc de traitement placé après l'amplificateur est un détecteur d'enveloppe, dont le fonctionnement est détaillé dans détecteur d'enveloppe. Sa sortie est notée ENVEL. Ce signal constitue l'enveloppe de la salve reçue, avec une tension de référence toujours égale à 2,5 volts.

La dernière partie du circuit est un comparateur (LM293) qui bascule lorsque la tension ENVEL dépasse un seuil. Celui-ci est déterminé par un pont diviseur de tension constitué de la résistance R11 et du potentiomètre numérique U4. La sortie de ce comparateur est notée COMP. Il faut noter que la bascule du comparateur se fait en front descendant car son fonctionnement est beaucoup plus rapide en front descendant qu'en front montant. L'état de repos de COMP est donc l'état haut.

L'interface avec l'Arduino comporte le signal ENVEL (dirigée vers l'entrée analogique A0) et le signal numérique COMP, qui servira à détecter l'arrivée du paquet. Elle comporte aussi la commande SPI des deux potentiomètres numériques (SI,SCK) avec SC AMP et CS COMP pour sélectionner l'un ou l'autre.

4. Programme Arduino

Les tâches du programme Arduino sont les suivantes :

La détection du paquet reçu ne se fait effectivement qu'après un certain délai, nécessaire en cas de réflexion du paquet par un obstacle. Ce délai permet d'éviter le paquet transmis directement entre l'émetteur et le récepteur. Si le même transducteur joue le rôle d'émetteur et de récepteur, ce délai est nécessaire pour éviter la période pendant laquelle le transducteur vibre encore sous l'effet du signal d'émission.

Le programme fonctionne avec Arduino MEGA.

EmetteurRecepteurUS.ino
#include <Arduino.h>
#include <SPI.h>

#define EMISSION 2 // PE4, à l'état haut pendant l'émission
#define ENA 5 // commande ENABLE du L298
#define SIGNAL1 11 // PB5 (OC1A) commande IN1 du L298
#define SIGNAL2 12 // PB6 (OC1B) commande IN2 du L298
#define ADCSAMPLE 3 // PE5, signal de contrôle de l'échantillonnage
#define RECEPT 18 // INT3, sortie du comparateur (COMP)
#define POT_COMP 10 // CS COMP
#define POT_AMP 13 // CS AMP
#define BUFSIZE 255
#define PERIOD 25 // période du signal de commande en microsecondes
#define NBCYCLES 10 // nombre de cycles
#define RECEPTIME 3000 // délai (en us) entre l'émission et le début de la réception
#define SEUILREL 0.1 // valeur du seuil relativement à la valeur maximale de l'enveloppe
#define VCC_DETECT 5.0 // tension d'alimentation du détecteur d'enveloppe

uint16_t diviseur[6] = {0,1,8,64,256,1024};
uint32_t timer_top;
volatile uint32_t count, nbCount;
float periode_timer;
uint8_t d;
volatile uint8_t adc_buffer[BUFSIZE];
uint16_t buffer_index;
volatile uint8_t adcflag;
volatile uint8_t recept_wait;
volatile float temps_trajet;
uint32_t recept_count_min;
uint8_t potAmpli;
uint32_t max_count;
uint8_t tccr1a,tccr1b;            
             

La fonction suivante configure le Timer 1 pour la génération d'un paquet d'impulsions mais sans déclencher le Timer. period est la période en microsecondes, nombre est le nombre de cycles et ra,rb sont les rapports cycliques des signaux envoyés sur respectivement les sorties SIGNAL1 et SIGNAL2, qui commandes les entrées IN1 et IN2 du pont. Comme expliqué dans pont de transistor L298, ces deux rapports cycliques doivent être légèrement différents pour assurer le bon basculement du pont à la fréquence de 40kHz, qui est à la limite de ce qu'il est possible de faire avec ce pont. Une interruption est générée lorsque le compteur atteint sa valeur maximale (overflow interrupt). Après exécution de cette fonction, la variable globale periode_timer contient la période effective, qui peut être légèrement différente de la période demandée.

void config_train_impulsions(uint32_t period, int32_t nombre, float ra, float rb) {
  max_count = nombre+1;
  recept_count_min = RECEPTIME/period;
  cli();
  tccr1a = 0;
  tccr1b = 0;
  //phase and frequency correct PWM mode, top = ICR1 WGM3:0 = 1,0,0,0
  tccr1b |= (1 << WGM13);
  // COM1A1 = 1, COM1A0 = 0 : clear OC1A on compare match when up counting
  // COM1B1 = 1, COM1B0 = 1 : set OC1B on compare match when up counting
  tccr1a |= (1 << COM1A1) | (1 << COM1B1) | (1 << COM1B0);
  uint32_t icr = (F_CPU/1000000*period/2);
  int d = 1;
  while ((icr>0xFFFF)&&(d<5)) {
     d++;
     icr = (F_CPU/1000000*period/2/diviseur[d]);
   }
  timer_top = icr;
  periode_timer = icr*2*diviseur[d]*1000000/F_CPU;
  tccr1b |= d;
  ICR1 = icr;
  OCR1A = icr*ra;
  OCR1B = icr*(1.0-rb);
  TIMSK1 = (1 << TOIE1) ; // overflow interrupt enable
  TCNT1 = 0;
  count = 0;
  sei();
}                
                 

Le rôle de la fonction appelée lors des interruptions (Timer 1 Overflow) déclenchées par le Timer est de compter les impulsions et d'arrêter l'émission des deux signaux (OC1A et OC1B) par le Timer 1 lorsque le nombre d'impulsions demandé est atteint. Dans ce cas, elle remet aussi à zéro les sorties INPUT1 et INPUT2 (ports PB5 et PB6) et la sortie EMISSION (port PE4). Il faut noter que le Timer lui-même n'est pas arrêté car il est aussi utilisé pour l'échantillonnage de la conversion analogique-numérique. Le compteur de cycles (variable count) est toujours incrémenté après la fin de l'émission car il sert à déterminer le temps d'arrivée du paquet reçu (voir fonction ISR(INT3_vect) plus loin).

ISR(TIMER1_OVF_vect) {
  count++;
  if (count==max_count) {
    TCCR1A = 0; // arrêt de l'émission
    PORTB &= ~(1 << PORTB5);
    PORTB &= ~(1 << PORTB6);
    PORTE &= ~(1 << PORTE4);
  }
}                 
                 

Le fonction suivante démarre l'émission des impulsions par le Timer 1 et met la sortie EMISSION (port PE4) au niveau haut :

void start_impulsions() {
  cli();
  TCNT1 = 0;
  count = 0;
  TCCR1A = tccr1a;
  TCCR1B = tccr1b;
  buffer_index = 0;
  recept_wait = 1;
  PORTE |= (1 << PORTE4);
  sei();
}                
                 

La fonction suivante configure le convertisseur analogique-numérique pour la voie A0. L'échantillonnage est commandé par le Timer 1.

void config_adc() {
  ADMUX = 0; // voie 0
  ADCSRB = 0;
  ADMUX |= (1 << REFS0); // Voltage reference : AVCC with external capacitor at AREF pin
  ADMUX |= (1 << ADLAR);
  ADCSRA = (1 << ADEN); // enable ADC
  ADCSRA |= (1 << ADATE); // Auto Trigger Enable
  ADCSRA |= (1 << ADIE); // interrupt enable
  ADCSRB |= (1 << ADTS2)|(1 << ADTS1);// trigger source : timer 1 overflow
  ADCSRA |= 4; // prescaler
  buffer_index = 0;
}                
                 

À chaque cycle de Timer 1, la fonction d'interruption suivante est appelée. Elle est chargée de lire le résultat de la conversion analogique-numérique et de mettre la valeur dans un tampon. La conversion est faite en 8 bits pour plus de rapidité (au lieu de 10 bits). Le remplissage du tampon ne commence que lorsque le nombre de cycles a atteint recept_count_min (depuis le début de l'émission). À chaque conversion, la sortie ADCSAMPLE (port PE5) est basculée. Le signal sur cette sortie permet de contrôler le bon fonctionnement du convertisseur, c'est-à-dire de vérifier que la conversion se fait bien à la fréquence du Timer.

ISR(ADC_vect) {
  uint8_t x = ADCH;
  if (count < recept_count_min) return;
  if (buffer_index < BUFSIZE) {
    adc_buffer[buffer_index] = x;
    buffer_index += 1;
  }
  if (adcflag==0) {
      adcflag = 1;
      PORTE |= (1 << PORTE5);
  }
  else {
      adcflag = 0;
      PORTE &= ~(1 << PORTE5);
  }
}                   
                    

La fonction suivante est appelée lors d'une interruption (INT3) déclenchée par un front descendant sur RECEPT (qui est reliée à la sortie COMP du circuit). Ce front correspond à la détection du paquet reçu par le comparateur. Elle calcule l'intervalle de temps (temps_trajet) entre le début de l'émission des impulsions de commande du pont et le front descendant sur l'entrée RECEPT. Pour faire ce calcul, on tient compte du nombre de cycles comptés (count) et de la valeur du compteur du Timer 1 (TCNT1).

ISR(INT3_vect) {
  uint32_t tcn = TCNT1;
  if (count < recept_count_min) return;
  if (recept_wait) {
      recept_wait = 0;
      temps_trajet = periode_timer*(count+tcn*0.5/timer_top);
  }
  
}              
                    

Les deux fonctions suivantes permettent de modifier les résistances des potentiomètres numériques :

void potentiometre_comp(int v) {
  digitalWrite(POT_COMP, LOW);
  delayMicroseconds(500);
  SPI.transfer(0x00);
  SPI.transfer(v);
  digitalWrite(POT_COMP, HIGH);
}

void potentiometre_amp(int v) {
  digitalWrite(POT_AMP, LOW);
  delayMicroseconds(100);
  SPI.transfer(0x00);
  SPI.transfer(v);
  digitalWrite(POT_AMP, HIGH);
}          
                    

La fonction setup configure les ports d'entrée-sortie, attribue une interruption à un front descendant sur RECEPT, attribue les valeurs initiales aux potentimètres, configure le convertisseur analogique-numérique et enfin configure le Timer 1. Les rapports cycliques pour les signaux sortant sur SIGNAL1 et SIGNAL2 (respectivement IN1 et IN2 du pont L298) sont choisis de manière à donner un basculement du pont sans points morts, avec un rapport cyclique de 1/2 (voir pont de transistor L298). Par défaut, la période est 25 microsecondes et il y a 10 cycles générés (voir constantes définies au début).

void setup() {
      Serial.begin(115200);
      SPI.begin();
      SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
      pinMode(POT1,OUTPUT);
      pinMode(POT2,OUTPUT);
      digitalWrite(POT_COMP,HIGH);
      digitalWrite(POT_AMP,HIGH);
      pinMode(ADCSAMPLE,OUTPUT);
      pinMode(SIGNAL1,OUTPUT);
      pinMode(SIGNAL2,OUTPUT);
      pinMode(EMISSION,OUTPUT);
      PORTB &= ~(1 << PORTB5);
      PORTB &= ~(1 << PORTB6);
      pinMode(ENA,OUTPUT);
      digitalWrite(ENA,HIGH);
      EICRA |= (1<<ISC31); // interruption INT3 (pin 18) programmée sur le front descendant
      EIMSK |= (1<<INT3); // activation de INT3
      adcflag = 0;
      delay(100);
      potentiometre1(110); // potentiomètre de réglage du seuil du comparateur
      potAmpli = 50;
      potentiometre2(potAmpli); // potentiomètre de réglage du gain de l'amplificateur
      config_adc();
      float R=0.5;
      float r2=R-6e-2;
      float r1=1-R-6e-2;
      config_train_impulsions(PERIOD,NBCYCLES,r1,r2);
}                      
                      

La fonction emission_reglage effectue les opérations suivantes :

Il est bien sûr possible de désactiver ces réglages automatique du gain de l'amplificateur et du seuil du comparateur.

void emission_reglage() {
      start_adc();
      start_impulsions();
      delay(100);
      uint8_t envmax = 0;
      for (int i=0; i<BUFSIZE; i++) {
        if (adc_buffer[i] > envmax) envmax = adc_buffer[i];
      }
      Serial.print("Max = "); Serial.println(envmax);
      float Umax = (envmax-128)*5.0/255; // max de l'enveloppe en V, par rapport à 2.5 V
      Serial.print("Umax = "); Serial.println(Umax);
      float seuil = Umax*SEUILREL + 2.5; // tension de seuil du comparateur
      Serial.print("Seuil = "); Serial.println(seuil);
      int p = 127.0*(VCC_DETECT / seuil -1);
      Serial.print("potComp = "); Serial.println(p);
      potentiometre_comp(p);
      if ((Umax > 1.4)&&(potAmpli>0)) {potAmpli -=1; potentiometre_amp(potAmpli);}
      else if ((Umax < 1.4)&&(potAmpli <= 128)) {potAmpli +=1; potentiometre_amp(potAmpli);}
      Serial.print("potAmpli = "); Serial.println(potAmpli);
      Serial.print("temps trajet = "); Serial.println(temps_trajet);
}                                           
                      

La fonction emission_mesure effectuer l'émission du signal de commande du pont et la mesure du temps de trajet, sans aucune modification des réglages des potentiomètres. Dans ce cas, l'ADC est désactivé car il n'est pas utilisé.

void emission_mesure() {
  stop_adc();
  start_impulsions();
  delay(100);
  Serial.print("--temps trajet = "); Serial.println(temps_trajet);
}                      
                       

Dans la fonction loop, on répète de manière cyclique des opérations de réglage et des opérations de mesure, par exemple :

void loop() {
    for (int k=0; k<5; k++) emission_reglage();
    for (int k=0; k<5; k++) emission_mesure(); 
}                       
                       

5. Exemples de signaux

5.a. Émetteur et récepteur distincts

L'émetteur et le récepteur sont deux transducteurs distincts placés à une distance de 4 cm l'un de l'autre et pointant vers le sol, situé à 77 cm. Il s'agit donc de recevoir l'écho réfléchi par le sol.

Le front montant sur la sortie EMISSION sert à déclencher l'oscilloscope. L'instant t=0 correspond donc au début des impulsions de commande du pont.

Voici la tension de commande IN1 du pont (sortie SIGNAL1 de l'Arduino) et la tension aux bornes du transducteur émetteur. La tension d'alimentation du pont L298 est Vs=15V.

signaux-U-IN1signaux-U-IN1.pdf

On constate que la vibration de l'émetteur, et en conséquence la variation de tension à ses bornes, se poursuit au delà de la série des 10 impulsions de commande. Voici une vue sur une durée plus longue :

signaux-U-IN1-2signaux-U-IN1-2.pdf

Le retour au repos de l'émetteur se fait à environ 1ms du début de l'émission.

Voici les signaux AMPL (sortie de l'amplificateur) et ENVEL (sortie du détecteur d'enveloppe) lorsque l'ajustement automatique du gain et du seuil est terminé :

signaux-AMPL-ENVELsignaux-AMPL-ENVEL.pdf

La première partie est le paquet correspondant à l'onde acoustique transmise directement entre l'émetteur et le récepteur (qui a parcouru 4 cm). La partie qui nous intéresse (l'écho sur le sol) démarre à 4,5ms. Dans cet exemple, le temps d'attente avant de débuter la détection de l'écho doit être d'au moins 2 ms (la valeur configurée dans le programme est 3000 microsecondes). Voici une vue agrandie de l'écho :

signaux-AMPL-ENVEL-bsignaux-AMPL-ENVEL-b.pdf

La variation tension en sortie de l'amplificateur atteint une amplitude de 2,5 volts mais la tension en sortie du détecteur d'enveloppe ne dépasse pas 4 volts.

Voici la sortie sur la console série, correspondant au dernier appel de emission_reglage et aux 10 appels emission_mesure :

Max = 200
Umax = 1.41
Seuil = 2.64
potComp = 113
potAmpli = 60
temps trajet = 4578.94
--temps trajet = 4579.44
--temps trajet = 4579.19
--temps trajet = 4579.12
--temps trajet = 4579.12
--temps trajet = 4579.19
--temps trajet = 4578.87
--temps trajet = 4578.87
--temps trajet = 4579.12
--temps trajet = 4578.81
--temps trajet = 4579.00
            

Le potentiomètre de l'amplificateur est à peu près à mis-course donc le gain est environ la moitié du gain maximal. La tension maximale atteinte par ENVEL par rapport à la référence est 1,41V. Le seuil du comparateur est 2,64V. Ce seuil est déterminé par rapport à la valeur maximale (constante SEUILREL).

Le temps de trajet, c'est-à-dire la durée écoulée entre le début du signal de commande du pont et le front descendant en sortie du comparateur, est égal à 4579μs. Étant donné la vitesse du son dans l'air à la température du test (344m⋅s-1), cela correspond à une distance parcourue de 157,5cm, à comparer au double de la distance entre les transducteurs et le sol (154cm).

Voici la tension ENVEL et la tension COMP (sortie du comparateur, reliée à l'entrée RECEPT) :

signaux-COMP-ENVELsignaux-COMP-ENVEL.pdf

La durée entre le début de l'émission et le front descendant de COMP relevée à l'oscilloscope est 4,575ms, en bon accord avec les valeurs données par le logiciel mais légèrement inférieur de quelques microsecondes. Il est probable que cette différence soit en grande partie due au délai entre le front descendant et l'appel de la fonction ISR(INT3_vect) par interruption.

Il est intéressant de comparer le signal en sortie de l'amplificateur (AMPL) à la sortie du comparateur :

signaux-COMP-AMPLsignaux-COMP-AMPL.pdf

Le retard du front descendant de COMP par rapport à ce qui semble être le début du paquet reçu est de l'ordre de deux cycles, soit 50 microsecondes, ce qui correspond à une distance parcourue de 1,7cm.

5.b. Un seul transducteur

Le transducteur émetteur sert aussi de récepteur. Il est donc branché à la fois aux bornes du bobinage secondaire du transformateur et à l'entrée de l'amplificateur. Pour cette configuration, le découplage galvanique effectué par le transformateur est indispensable puisque l'entrée de l'amplificateur doit se fait par rapport à la masse.

signaux-COMP-AMPL-2signaux-COMP-AMPL-2.pdf

Voici la sortie de la console :

Max = 147
Umax = 0.37
Seuil = 2.54
potComp = 123
potAmpli = 129
temps trajet = 4625.50
--temps trajet = 4625.62
--temps trajet = 4626.06
--temps trajet = 4627.12
--temps trajet = 4626.37
--temps trajet = 4625.56
--temps trajet = 4625.37
--temps trajet = 4625.81
--temps trajet = 4626.25
--temps trajet = 4625.00
--temps trajet = 4626.44
             

L'amplitude du paquet correspondant à l'écho est beaucoup plus faible que précédemment, bien que le gain de l'amplificateur soit à son maximum (129). Sans doute le transducteur utilisé n'est-il pas optimisé pour la réception. Il est possible d'augmenter l'amplitude en augmentant la tension d'alimentation Vs, à condition de ne pas dépasser la limite de tension acceptable par le transducteur.

Le temps de trajet calculé est plus grand d'environ 50 microsecondes.

Voici le résultat lorsque une boîte d'épaisseur 100mm est posée sur le sol, ce qui a pour effet de racourcir le trajet de 200mm :

Max = 168
Umax = 0.78
Seuil = 2.58
potComp = 119
potAmpli = 129
temps trajet = 4026.69
--temps trajet = 4026.75
--temps trajet = 4027.00
--temps trajet = 4025.69
--temps trajet = 4027.06
--temps trajet = 4027.31
--temps trajet = 4027.00
--temps trajet = 4028.75
--temps trajet = 4027.56
--temps trajet = 4027.56
--temps trajet = 4027.31            
             

La différence des temps de trajet est de 598±2μs , ce qui correspond à un trajet de 206mm pour une vitesse du son de 344m⋅s-1.

Creative Commons LicenseTextes et figures sont mis à disposition sous contrat Creative Commons.