TP convertisseur analogique-numérique

É. Carry, J.-M Friedt

Questions sur ce TP :

  1. Quels sont les trois modes de déclenchement d’un convertisseur analogique-numérique ?

  2. L’ADC est un composant numérique qui doit donc être cadencé : quelle est la fréquence maximale de l’horloge alimentant un ADC d’Atmega32U4 ?

  3. La référence de tension est un point clé d’une conversion analogique-numérique : quel est son impact sur l’exactitude de la mesure ?

  4. ... quel est son impact sur la précision de la mesure ?

  5. Quelles sources de références de tension sont disponibles sur un Atmega32U4 ?

  6. Sur combien de bits sont codés les mots issus de l’ADC de l’Atmega32U4 ?

  7. Peut-on acquérir simultanément les tensions sur deux canaux d’ADC (e.g ADC0 et ADC1) d’un Atmega32U4 ? Justifier.

  8. Comment se nomme le registre qui permet de sélectionner la voie de conversion ? quel est l’effet d’écrire une valeur dans ce registre ? (consulter la datasheet).

Généralités sur le convertisseur analogique-numérique

Un convertisseur analogique-numérique fait le lien entre le monde des grandeurs continues (analogiques) et des grandeurs discrétisées (numériques). Il convertit une valeur VV comprise entre 0 et VrefV_{ref}, la tension de référence, en une valeur numérique b comprise entre 0 et 2N12^N-1 pour un encodage sur NN bits, selon b=VVref×(2N1)b=\frac{V}{V_{ref}}\times (2^N-1) \label{eq} Dans le cas de l’Atmega32U4, N=10N=10.

De nombreux microcontrôleurs annoncent une multitude de canaux de conversion analogique numérique, alors qu’en pratique un unique convertisseur voit ses entrées multiplexées : seule une conversion peut avoir lieu à un instant donné. Des conversions multiples de divers canaux (entrées) sont nécessairement séquentielles, sauf dans le cas particulier de plusieurs convertisseurs physiques (cas du STM32F1xx qui comporte deux convertisseurs par exemple).

Le convertisseur analogique-numérique équipant l’ATMega32U4 fonctionne par approximations successives. Son fonctionnement est détaillé dans le chapitre 24 de , tandis que la figure 24-1 de illustre l’architecture générale du convertisseur. On y notera en particulier la présence d’un convertisseur numérique-analogique (DAC) chargé de générer, par rapport à la tension de référence AREF, une tension qui se compare à la tension inconnue à identifier. Une méthode de dichotomie – naturelle pour trouver le mot binaire représentant la tension inconnue – permet de programmer successivement le DAC avec des versions de plus en plus précises du mot qui finira par être l’estimation de la tension (ADCH/ADCL). Noter aussi la présence du multiplexeur routant l’unique sortie vers les 15 entrées possibles, incluant la masse, une référence de tension ou une diode faisant office de capteur grossier de température.

La référence interne de température des microcontrôleurs est tout juste bonne à détecter une variation relative de température ou une sortie de la gamme de fonctionnement. Elle ne peut en aucun cas être utilisée à des fins métrologiques .

Affichage

Tout au long de ces exercices nous aurons besoin de formater un affichage d’une valeur codée sur 16 bits afin de l’afficher sur un terminal. Le nombre binaire v doit être convertir en chaı̂ne de caractères s qui sera affichée par la fonction fputs(char*, FILE*).

Une sonde de température LM335 1 est connectée, sur le circuit supportant les afficheurs 7-segments, à la broche A0 de la carte Olimex. Nous désirons mesurer sa température. La tension délivrée par ce composant est de 10 mV/C.

Mode polling

Le mode le plus simple du convertisseur est de lancer une conversion, attendre que la conversion s’achève, et lire le résultat dans le registre approprié. Dans cet exemple, nous configurons la tension de référence comme étant la tension d’alimentation. Il s’agit d’un choix confortable mais potentiellement peu judicieux en terme de stabilité de la mesure. En effet, une tension d’alimentation mal régulée induira une variation de bb dans Eq. [eq] si VrefV_{ref} varie et ce, même en l’absence de fluctuation de VV. L’alternative est d’utiliser une référence interne de 2,56 V – de stabilité indépendante de la qualité de l’alimentation mais de dynamique plus faible, ou une tension de référence externe fournie sur la broche AREF (42). Comme dans la majorité des microcontrôleurs actuels, une des voies du multiplexeur d’ADC (MUX=0x27 ) permet de lire une indication de température, ici en sélectionnant aussi la tension de référence interne de 2,56 V.

// potentiometre entre GND/3.3V pour les extremes et ADC0=A5
//   ou sonde de temperature de la carte 7-segments (LM235)
//http://maxembedded.com/2011/06/20/the-adc-of-the-avr/

#include <avr/io.h> //E/S ex PORTB 
#define F_CPU 16000000UL
#include <util/delay.h>  // _delay_ms
#include "affiche.h"
#include "VirtualSerial.h"

extern USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface;
extern FILE USBSerialStream;

void adc_init()
{   DIDR0=0xff; // http://www.openmusiclabs.com/learning/digital/atmega-adc
    ADMUX = (1<<REFS0); // AREF = AVcc, 2.5 V si (1<<REFS0|1<<REFS1)
    // ADC Enable and prescaler of 128 : 16 MHz/128=125 kHz
    ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

unsigned short adc_read(uint8_t ch)
{
  ch &= 0x07;              // ch\in[0..7]
  ADMUX=(ADMUX & 0xF8)|ch; // clears the bottom 3 bits before ORing
 
  ADCSRA |= (1<<ADSC);       // start single convertion
  while(ADCSRA & (1<<ADSC)); // poll jusqu'a fin de conversion
  return (ADC);
}

int main(void){
  unsigned short res=0;
  char s[9];
  SetupHardware();
  CDC_Device_CreateStream(&VirtualSerial_CDC_Interface, &USBSerialStream);
  GlobalInterruptEnable();

  DDRB |=1<<PORTB5;
  DDRE |=1<<PORTE6;
  PORTB |= 1<<PORTB5;
  PORTE &= ~1<<PORTE6;
  
  adc_init();
  s[0]='0';s[1]='x';s[6]='\r';s[7]='\n';s[8]=0;

  while (1){
    PORTB^=1<<PORTB5;PORTE^=1<<PORTE6;
    _delay_ms(500); 
    res=adc_read(0);
    affiche(res,&s[2]); 
    fputs(s, &USBSerialStream);
//  les 3 lignes ci-dessous pour accepter les signaux venant du PC 
    CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);
    CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
    USB_USBTask();
  }
  return 0;
}
  1. Proposer le Makefile qui exploite la fonction affiche() pour afficher le résultat de la conversion.

  2. Le choix de la référence de tension est un point crucial de la résolution d’un ADC. En effet, puisque valeurV/Vrefvaleur\propto V/V_{ref}, une source de référence VrefV_{ref} instable se traduit par une valeurvaleur fluctuante même si VV reste stable. Une source est une référence interne, stable mais de valeur réduite, particulièrement appropriée pour la mesure de petits signaux. Une alternative pour accéder à une gamme de mesure plus large – nécessaire dans les exemples qui vont suivre – est d’utiliser la tension d’alimentation comme référence.
    Quelle est la référence de tension utilisée pour la mesure ? ce choix est-il judicieux ?

    image

  3. Quel est le canal d’ADC mesuré ? Comment activer la lecture sur le canal 1 qui est connecté au jack qui va à la carte son ?

Mesure de température par la sonde LM335 (gauche) et par la sonde interne à l’Atmega32U4 (droite).
Mesure de température par la sonde LM335 (gauche) et par la sonde interne à l’Atmega32U4 (droite).

Une fois le convertisseur configuré, nous avons uniquement à lancer un ordre de début de conversion (bit ADSCADC Start Conversion) en prenant soin d’avoir défini le canal sur lequel se fait la mesure, puis d’attendre le message de fin de conversion.

Afin d’afficher des séquences de mesures stockées en format hexadécimal (que Matlab et GNU/Octave ne savent pas lire par load, nous pourrons par exemple utiliser f=fopen("fichier.dat"); d=fscanf(f,"%x"); (Fig. 2).


Acquisition d’un signal rapide  : utilisation de la carte son

Une carte son de PC est un générateur idéal de signaux périodiques, facilement accessible. La seule subtilité consiste à transposer le signal en tension puisque l’ADC de l’Atmega32U4 n’accepte que des tensions positives. Le circuit de la Fig. 4 propose une solution certes peu élégante, mais fonctionnelle.

.

Le danger lors de l’utilisation d’un ADC est de ne pas tenir compte de sa faible impédance : nous informe que l’impédance équivalente du convertisseur analogique-numérique est ZADC10Z_{ADC}\simeq 10 kΩ\Omega. Nous devons donc prendre soin de ne pas effondrer la source de courant d’un capteur présentant une impédance plus élevée que ZADCZ_{ADC} (donc incapable de fournir suffisamment de courant, puisque Z=U/IZ=U/I). Par ailleurs, notre montage de T de polarisation est (volontairement) naı̈f car n’inclut aucun circuit d’adaptation d’impédance. Le cas idéal serait de considérer R1R1 et R2R2 comme pont diviseur de tension. Dans le cas idéal, le pont diviseur de tension tire la tension de sortie de la carte son, protégée contre le courant associé au potentiel continu par CC, de R2R1+R2\frac{R2}{R1+R2}. Le cas pratique n’est pas aussi simple, d’une part parceque l’ADC présente une impédance finie qui se place en parallèle de R2R2, et d’autre part parceque le condensateur qui bloque le retour de courant continu dans la carte son présente en alternatif une impédance de module 1Cω\frac{1}{C\omega}. Ainsi, dans l’équation du pont diviseur, R1R1 devient R1jCωR1-\frac{j}{C\omega} et R2R2 devient R2ZADCR2+ZADC\frac{R2\cdot Z_{ADC}}{R2+Z_{ADC}}.

Un extrait du programme utilisé pour générer les courbes de la Fig. 4 (droite) est proposé ci-dessous. Il se base sur l’exemple en mode polling de gestion de l’ADC. On notera que la carte son est connectée à l’entrée ADC1.

[...]
int main(void){
  unsigned short res[256];
  int k;
  char s[7];

[...]
  while (1){
    PORTB^=1<<PORTB5;PORTE^=1<<PORTE6;
    _delay_ms(100); 
    for (k=0;k<256;k++) res[k]=adc_read(1);
    for (k=0;k<256;k++) {affiche(res[k],s); fputs(s, &USBSerialStream); }
  }
  return 0;
}

En cas de dysfonctionnement, vérifier au moyen de alsamixer que l’amplitude du signal de sortie n’est pas au minimum et que la sortie audiofréquence est active. On pourra aussi s’affranchir de l’interface graphique de gnuradio-companion pour générer le signal audiofréquence sur la carte son en utilisant la commande play -n synth 3 sin 440 pour générer continument une sinusoı̈de de fréquence 440 Hz.

Ce mode de fonctionnement – attendre la fin de conversion en sondant l’état d’un bit de statut (polling) – gaspille des ressources de calcul en bouclant pour attendre que la conversion prenne fin, alors que d’autres opérations pourraient prendre place entre temps, surtout avec un convertisseur aussi lent que celui annonçant un débit de 15 kS/s (ou 13 cycles d’horloge de l’ADC cadencé entre 50 et 200 kHz – 200/13=15 kS/s). Nous allons donc nous intéresser au cas où nous lançons un ordre de conversion puis vaquer à nos occupations, le microcontrôleur nous prévenant par interruption de la disponibilité de la donnée.

Mode interruption

Plus efficace, au lieu de boucler en sondant le registre de statut du convertisseur analogique-numérique, nous sommes informés par une interruption de la fin de conversion. Ce mode de fonctionnement permet en particulier d’endormir le microcontrôleur en attendant la fin de conversion (économie d’énergie), ou de lancer d’autres tâches pendant que le convertisseur effectue son travail.

// http://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-adc
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>  // _delay_ms
#include "affiche.h"
#include "VirtualSerial.h"

extern USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface;
extern FILE USBSerialStream;

#define N 256

volatile char flag=0;

ISR(ADC_vect) {flag=1;}

void adc_init()
{ADMUX  = (1<<REFS0); // Set ADC reference to AVCC
 ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // no MUX for ADC0
 ADCSRA |= (1 << ADIE); // Enable ADC Interrupt
}

int main (void)
{volatile unsigned short res[N],indice=0;
 char s[9];
 int k;
 s[0]='0';s[1]='x';s[6]='\r';s[7]='\n';s[8]=0;

 SetupHardware();
 CDC_Device_CreateStream(&VirtualSerial_CDC_Interface, &USBSerialStream);
 GlobalInterruptEnable();

 adc_init();
 sei();	
 ADMUX|=0x01; // ADC1
 ADCSRA |= (1<<ADSC);          // start single conversion
 while (1)
   {if (flag!=0) 
      {res[indice]=ADC;flag=0; // fin de conversion par interrupt
       indice++;
       if (indice==N) 
          {for (k=0;k<N;k++) 
             {affiche(res[k],&s[2]);fputs(s, &USBSerialStream);
// est-ce malin de mettre USB dans la boucle plutot que de remplir 
//   le descripteur de fichier et envoyer en une fois ?
              CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
              USB_USBTask(); //  communication avec le PC 
             }
           indice=0;
          }
       ADCSRA |= (1<<ADSC);  // start single conversion
      }
//  _delay_ms(100); 
   }
}

Nous prenons soin de déclencher les interruptions en général (sei()), et de rapatrier la donnée lue dans le registre ADC (10 bits) lorsqu’un drapeau est transféré du gestionnaire d’interruption au programme principal indiquant la disponibilité de la donnée.

Comme pour la lecture sur une carte son, il est courant de charger de façon automatique un tableau de données et d’informer le programme principal de la disponibilité de cette masse de données plutôt qu’interrompre l’exécution du programme principal pour chaque nouvelle donnée acquise.

Échantillonnage périodique sur timer

L’ADC peut être déclenché de façon périodique par un timer : ce mode de fonctionnement permet de respecter une condition fondamentale du traitement spectral du signal, à savoir l’acquisition périodique des échantillons. Diverses sources de déclenchement sont possibles, telles que décrites sur l’extrait de datasheet de la Fig. 5.

Sources de déclenchement de l’ADC – notamment liées aux divers timers.
Sources de déclenchement de l’ADC – notamment liées aux divers timers.
// http://www.avrfreaks.net/forum/atmega32u4-starting-adc-conversion-timer4
#include <avr/io.h> //E/S ex PORTB 
#include <avr/wdt.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#include <avr/interrupt.h>
#include <util/delay.h>  // _delay_ms
#include "affiche.h"
#include "VirtualSerial.h"

extern USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface;
extern FILE USBSerialStream;

EMPTY_INTERRUPT(TIMER4_OVF_vect);

volatile unsigned short adc,flag;

ISR(ADC_vect)
{adc = ADC;
 flag = 1;
}

void adc_init()
{ ADMUX = (1<<REFS0);   // quelle tension de reference ? quelle canal ?
  ADCSRB = (1<<ADTS3); // quelle source de declenchement de la mesure ?
  ADCSRA = (1<<ADEN) | (1<<ADSC) | (1<<ADATE) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
}

int main(void)
{ char s[9];
  s[0]='0';s[1]='x'; s[6]='\r'; s[7]='\n'; s[8]=0;
  
  SetupHardware();
  CDC_Device_CreateStream(&VirtualSerial_CDC_Interface, &USBSerialStream);
  GlobalInterruptEnable();
 
  TCCR4B = (1<<CS43) | (1<<CS42) | (1<<CS41) | (1<<CS40); // quel prescaler ?
  TIMSK4 = (1<<TOIE4); // quel taux de raffraichissement de l'interruption timer ?
  TC4H = 3; OCR4C = 0xFF; // Set TOP (overflow limit) to 0x3FF (= 1023)
 
  adc_init();
  sei();

  while (1)
   {
    if (flag) 
      {flag = 0;
       affiche(adc,&s[2]); fputs(s, &USBSerialStream);
//  les 3 lignes ci-dessous pour accepter les signaux venant du PC 
       CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);
       CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
       USB_USBTask();
      }
   }
}
  1. Calculer et valider la fréquence d’échantillonnage périodique de la conversion. Pour ce faire, répondre aux diverses questions parsemant les commentaires du code.

  2. Quelle est la source de déclenchement de la mesure (configuration de ADCSRA – quel timer et selon quelle configuration) ?

  3. Modifier la fréquence de déclenchement du timer et vérifier l’impact sur la mesure.

    image

    En particulier, configurer l’ADC pour échantillonner à 1 kHz et exciter une sinusoı̈de périodique à 100 Hz sur la carte son.

  4. Quelle est la fréquence maximale accessible par cette
    méthode ?

Nous constatons cependant que la fréquence maximale accessible est 8 kHz. Dans l’exemple ci-dessous, nous avons choisi CS43=0, CS42=0, CS41=1 et CS40=1 pour obtenir une fréquence d’échantillonnage de 4 kHz, qui se traduit bien par 9 points/période lors de l’acquisition d’un signal représentant une sinusoı̈de à 440 Hz.

Compte tenu de l’instabilité de la bibliothèque de communication par USB, on sauvera les données dans le fichier toto au moyen de minicom -C toto.

Au lieu de lire une unique valeur dans un scalaire adc, remplir dans l’interruption un tableau de valeurs, qui sera lu par le programme principal une fois plein. Cette opération a pour vocation de s’affranchir de la limitation sur la fréquence d’acquisition induite par le temps de communication.

Bonus  : passer le microcontrôleur en mode veille entre deux conversions analogique-numériques afin de réduire la consommation électrique du microcontrôleur.

image

Pour ce faire, on s’inspirera de la documentation disponible à http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html.

Attention : l’interruption du timer 4 permet de sortir du mode veille mais entre en conflit avec la communication par USB. Penser, dans ce mode, à communiquer au travers de l’USART (port RS232).

Appendice : réglage du niveau sonore

L’utilisation de la carte son implique d’activer la sortie audio et de régler le niveau sonore. L’outil pour ce faire est alsamixer. Si pulseaudio est le pilote audio utilisé, il faut passer en mode alsa en sélectionnant la carte son par F6. On vérifiera en particulier que les sorties ne sont pas inhibées (Mute).

  image image

1 Atmel, 8-bit Microcontroller with 16/32K Bytes of ISP Flash and USB Controller – ATmega16U4 & ATmega32U4, disponible à www.atmel.com/Images/Atmel-7766-8-bit-AVR-ATmega16U4-32U4_Datasheet.pdf (version Atmel-7766J-USB-ATmega16U4/32U4-Datasheet_04/2016)

http://samuelkerr.com/?p=390

ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // prescalar = 64 ADMUX |= (1 << REFS0); // reference voltage from internal source ADMUX |= (1 << ADLAR); // 8 bits, instead of 10 ADCSRA |= (1 << ADATE); // free running ADCSRA |= (1 << ADEN); // enable ADC ADCSRA |= (1 << ADSC); // start A2D conversions

reduire le prescale augmente la freq d;echantillonage (cf x4 pour passer de 8 a 34 kHz sur un signal a 440 Hz)

retirer le USB_Task() pour atteindre 5 kHz d’echantillonnage quand on alterne acquisition et emission


  1. www.ti.com/lit/ds/symlink/lm335.pdf